[mod.sources] v09i004: ELM Mail System, Part04/19

sources-request@mirror.TMC.COM (03/09/87)

Submitted by: Dave Taylor <hplabs!taylor>
Mod.sources: Volume 9, Issue 4
Archive-name: elm2/Part04

#! /bin/sh
# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
# If this archive is complete, you will see the message:
#		"End of archive 4 (of 19)."
# Contents:  Overview filter/filter.c filter/utils2.c src/aliaslib.c
#   src/edit.c src/file_utils.c src/mailtime.c src/opt_utils.c
#   src/sort.c src/syscall.c
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo shar: Extracting \"Overview\" \(5333 characters\)
if test -f Overview ; then 
  echo shar: Will not over-write existing file \"Overview\"
else
sed "s/^X//" >Overview <<'END_OF_Overview'
X			An Overview of the Elm Mail System
X			----------------------------------
X
XIntroduction
X
X	This file discusses the functionality of the Elm mail system
Xand explains some of the motivation behind the creation and of various
Xfeatures.
X
X1. What is Elm?
X
X	Currently on Unix, there seems to be a preponderence of line-oriented 
Xsoftware.  This is most unfortunate as most of the software on Unix tends to
Xbe pretty darn hard to use!  I believe that there is more than a slight
Xcorrelation between the two, and, since I was myself having problems using
X"mailx" with high-volume mail, I created a new mail system.
X
X	In the lingo of the mail guru, Elm is a "User Agent" system,  it's
Xdesigned to run with "sendmail" or "/bin/rmail" (according to what's on
Xyour system) and is a full replacement of programs like "/bin/mail" and
X"mailx".  The system is more than just a single program, however, and
Xincludes programs like "from" to list a 'table of contents' of your
Xmail, "printmail" to quickly paginate mail files (to allow 'clean'
Xprintouts), and "autoreply", a systemwide daemon that can autoanswer
Xmail for people while they're on vacation without having multiple
Xcopies spawned on the system.
X
X2. What's New about Elm?
X
X	The most significant difference between Elm and earlier mail
Xsystems is that Elm is screen-oriented.  Upon further use, however,
Xusers will find that Elm is also quite a bit easier to use, and quite
Xa bit more "intelligent" about sending mail and so on.   For example,
Xsay you're on "usenet" and receive a message from someone on the
XARPANET.  The sender also "cc'd" another person on ARPA.  With Elm
Xyou can simply G)roup reply and it will build the correct return
Xaddresses.
X
X	There are lots of subtleties like that in the program, most of
Xwhich you'll probably find when you need them.
X
X3. What systems does it work on?
X
X	Elm was originally written on HP-UX, HP's proprietary version
Xof Bell system V, with a little BSD thrown in.  Since then, it has been
Xported to Bell, Berkeley, Sun, UTS and the Pyramid and should run on 
Xall these systems without any modifications (if there turn out to be 
Xmodifications, please notify the author as soon as possible).
X
X	Some people have expressed interest in porting the mail system
Xto Xenix.  If it is indeed 100% system V compatible it should be rather
Xtrivial...
X
X4. Does it obey existing mail standards?
X
X	Yes!  That's another of the basic reasons the program was 
Xoriginally written!  To ensure that the date field, the "From:" line
Xand so on were all added in the correct format.  The program is 100%
Xcorrect according to the RFC-822 electronic mail header protocol
Xguide.
X
X5. What were the main motivating factors?
X
X	The first two I've already mentioned, but here's a (somewhat
Xpartial) list;
X
X	-  To have a mail system that exploited the CRT instead of
X	   assuming I'm on a teletype.
X
X	- To have a mailer that was 100% correct when dealing with	 
X	  network mail (ie RFC-822).
X
X	- To create a system that needed no documentation for the
X	  casual user, but was still powerful enough and sophisticated
X	  enough for a mail expert.
X
X	- To write a "significant" piece of software as a learning
X	  experience (I admit it!)
X
X	- To find out how reasonable it is to try to modify a program
X	  to meet the expectations of the users, rather than vice-versa.
X
X	- To basically correct some of the dumb things that the current
X	  mailers do, like letting you send mail to addresses that it
X	  could trivially figure out are going to result in 'dead.letter'
X
X	- To tie in intimately with the pathalias program output, and
X	  allow users to specify machine!user or user@machine and have
X	  the COMPUTER do the work of figuring out addresses...
X
X6. Is it reliable?
X
X	The mailer, in various incarnations, has logged literally
Xthousands upon thousands of hours without any problems that aren't
Xnow corrected.  As new problems arise they're dealt with in as
Xrapid a manner as possible...
X
X7. What should I do now?
X
X	The first step would be to install the mail system and have
Xthe "elm" mailbox/alias expand to my email address (hplabs!taylor).
XThen, once it's all up and running, drop me a line letting me know
Xthat your site is running the system (bookkeeping) and what you and
Xyour site think of it.
X
X	REMEMBER: The product is evolving so if you'd like to have a
Xsomething change, or have something new added, LET ME KNOW!!!  I'd 
Xmuch rather make the change myself than start getting change reports
Xmailed from around the world!!
X
X8. Disclaimers 
X
X	The author of this program will deny all liability for any
Xdamages, either real or imagined, due to the execution of this program
Xor anything related to either the software or the system.  Furthermore,
Xthe entire system and all source within, including the presentation
Xscreens and commands, are legally copyrighted by the author, and while
Xthey can be used, and abused for public domain systems, will be in 
Xviolation of the law if used in systems or programs sold for profit.
X
X	By installing the mailer or even extracting it from the network,
Xyou are agreeing to the above disclaimer.
X
X9. Finally
X
X	I think it's a good program, and I can cite at least 75 people
Xwho would (begrudgingly, I'm sure) agree.  You should most certainly
Xinstall the program and try it!!
X
X
X				-- Dave Taylor
X				
X				hplabs!taylor
X
XMarch 13th, 1986
END_OF_Overview
if test 5333 -ne `wc -c <Overview`; then
    echo shar: \"Overview\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"filter/filter.c\" \(5430 characters\)
if test -f filter/filter.c ; then 
  echo shar: Will not over-write existing file \"filter/filter.c\"
else
sed "s/^X//" >filter/filter.c <<'END_OF_filter/filter.c'
X/**			filter.c			**/
X
X/** This program is used as a filter within the users ``.forward'' file
X    and allows intelligent preprocessing of mail at the point between
X    when it shows up on the machine and when it is actually put in the
X    mailbox.
X
X    The program allows selection based on who the message is FROM, TO, or
X    what the subject is.  Acceptable actions are for the program to DELETE
X    the message, SAVE the message in a specified folder, FORWARD the message
X    to a specified user, SAVE the message in a folder, but add a copy to the
X    users mailbox anyway, or simply add the message to the incoming mail.
X
X    Filter also keeps a log of what it does as it goes along, and at the
X    end of each `quantum' mails a summary of actions, if any, to the user.
X
X    Uses the files: $HOME/.filter for instructions to this program, and
X    $HOME/.filterlog for a list of what has been done since last summary.
X
X   (C) Copyright 1986, Dave Taylor
X**/
X
X
X#include <stdio.h>
X#include <pwd.h>
X#include <ctype.h>
X#ifdef BSD
X# include <sys/time.h>
X#else
X# include <time.h>
X#endif
X#include <fcntl.h>
X
X#include "defs.h"
X
X#define  MAIN_ROUTINE			/* for the filter.h file, of course! */
X#include "filter.h"
X
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X	FILE   *fd;				/* for output to temp file! */
X	struct passwd  *passwd_entry, 
X		       *getpwuid();		/* for /etc/passwd          */
X	char filename[SLEN],			/* name of the temp file    */
X	     buffer[LONG_SLEN];			/* input buffer space       */
X	int  in_header = TRUE,			/* for header parsing       */
X	     in_to     = FALSE,			/* are we on 'n' line To: ? */
X	     c;					/* var for getopt routine   */
X
X	/* first off, let's get the info from /etc/passwd */ 
X	
X	if ((passwd_entry = getpwuid(getuid())) == NULL) 
X	  leave("Cannot get password entry for this uid!");
X
X	strcpy(home, passwd_entry->pw_dir);
X	strcpy(username, passwd_entry->pw_name);
X
X	gethostname(hostname, sizeof(hostname));
X
X	/* now parse the starting arguments... */
X
X	while ((c = getopt(argc, argv, "aclnrSsv")) > 0) {
X	  switch (c) {
X	    case 'a' : audible = TRUE;				break;
X	    case 'c' : clear_logs = TRUE;			break;
X	    case 'l' : log_actions_only = TRUE;			break;
X	    case 'r' : if (get_filter_rules() == -1)
X		         fprintf(stderr,"Couldn't get rules!\n");
X		       else
X		         print_rules();
X		       exit(0);
X	    case 's' : if (get_filter_rules() == -1) exit(1);
X	  	       show_summary(); 				exit(0);
X	
X	    case 'S' : long_summary = TRUE;	
X	  	       show_summary(); 				exit(0);
X
X	    case 'n' : show_only = TRUE;			break;
X	    case 'v' : verbose = TRUE;				break;
X	  }
X	}
X
X	if (c < 0) {
X	  fprintf(stderr, "Usage: | filter [-nrv]\n\   or: filter [-c] -s\n");
X          exit(1);
X	}
X
X	/* next, create the tempfile and save the incoming message */
X
X	sprintf(filename, "%s.%d", filter_temp, getpid());
X
X	if ((fd = fopen(filename,"w")) == NULL)
X	  leave("Cannot open temporary file!");
X
X	while (gets(buffer) != NULL) {
X	  if (in_header) {
X
X	    if (! whitespace(buffer[0])) 
X		in_to = FALSE;
X
X	    if (the_same(buffer, "From ")) 
X	      save_from(buffer);
X	    else if (the_same(buffer, "Subject:")) 
X	      save_subject(buffer);
X	    else if (the_same(buffer, "To:")) {
X	      in_to++;
X	      save_to(buffer);
X	    }
X	    else if (the_same(buffer, "X-Filtered-By:")) 
X	      already_been_forwarded++;	/* could be a loop here! */
X	    else if (strlen(buffer) < 2) 
X	      in_header = 0;
X	    else if (whitespace(buffer[0]) && in_to)
X	      strcat(to, buffer);
X	  }
X	
X          fprintf(fd, "%s\n", buffer);	/* and save it regardless! */
X	  fflush(fd);
X	  lines++;
X	}
X
X	fclose(fd);
X
X	/** next let's see if the user HAS a filter file, and if so what's in
X            it (and so on) **/
X
X	if (get_filter_rules() == -1)
X	  mail_message(username);
X	else {
X	  switch (action_from_ruleset()) {
X
X	    case DELETE : if (verbose)
X			    printf("%sfilter (%s): Message deleted\n",
X				    BEEP, username);
X			  log(DELETE);					break;
X
X	    case SAVE   : if (save_message(rules[rule_choosen].argument2)) {
X			    mail_message(username);
X			    log(FAILED_SAVE);
X			  }
X			  else
X		 	    log(SAVE);					break;
X
X	    case SAVECC : if (save_message(rules[rule_choosen].argument2))
X			    log(FAILED_SAVE);
X			  else
X		            log(SAVECC);					
X			  mail_message(username);			break;
X
X	    case FORWARD: mail_message(rules[rule_choosen].argument2);
X			  log(FORWARD);					break;
X
X	    case EXEC   : execute(rules[rule_choosen].argument2);
X			  log(EXEC);					break;
X
X	    case LEAVE  : mail_message(username);
X			  log(LEAVE);					break;
X	  }
X	}
X
X	(void) unlink(filename);	/* remove the temp file, please! */
X	exit(0);	
X}
X
Xsave_from(buffer)
Xchar *buffer;
X{
X	/** save the SECOND word of this string as FROM **/
X
X	register int i, j;
X
X	for (i=0; buffer[i] != ' '; i++) 	;	/* get to word     */
X
X	for (i++, j=0; buffer[i] != ' ' && i < strlen(buffer); i++) 
X	  from[j++] = buffer[i];			/* copy it and     */
X
X	from[j++] = '\0';				/* Null terminate! */
X}
X
Xsave_subject(buffer)
Xchar *buffer;
X{
X	/** save all but the word "Subject:" for the subject **/
X
X	register int skip = 8;  /* skip "Subject:" initially */
X
X	while (buffer[skip] == ' ') skip++;
X
X	strcpy(subject, (char *) buffer + skip);
X}
X
Xsave_to(buffer)
Xchar *buffer;
X{
X	/** save all but the word "To:" for the to list **/
X
X	register int skip = 3;	/* skip "To:" initially */
X
X	while (buffer[skip] == ' ') skip++;
X
X	strcpy(to, (char *) buffer + skip);
X}
END_OF_filter/filter.c
if test 5430 -ne `wc -c <filter/filter.c`; then
    echo shar: \"filter/filter.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"filter/utils2.c\" \(4993 characters\)
if test -f filter/utils2.c ; then 
  echo shar: Will not over-write existing file \"filter/utils2.c\"
else
sed "s/^X//" >filter/utils2.c <<'END_OF_filter/utils2.c'
X/**			utils2.c			**/
X
X/** More miscellanous utilities for the filter program and such...
X
X    (C) Copyright 1986, Dave Taylor
X**/
X
X#include <stdio.h>
X
X#ifdef BSD
X# include <pwd.h>
X#endif
X
X#ifdef NEED_GETHOSTNAME
X#  include <sys/utsname.h>
X#endif
X
X#ifdef NEED_GETHOSTNAME
X
Xgethostname(hostname,size) /* get name of current host */
Xint size;
Xchar *hostname;
X{
X	/** Return the name of the current host machine.  UTS only **/
X
X	/** This routine compliments of Scott McGregor at the HP
X	    Corporate Computing Center **/
X     
X	int uname();
X	struct utsname name;
X
X	(void) uname(&name);
X	(void) strncpy(hostname,name.nodename,size-1);
X	hostname[size] = '\0';
X
X}
X
X#endif
X
X#ifdef BSD
X
X/** some supplementary string functions for Berkeley Unix systems **/
X
Xstrspn(source, keys)
Xchar *source, *keys;
X{
X	/** This function returns the length of the substring of
X	    'source' (starting at zero) that consists ENTIRELY of
X	    characters from 'keys'.  This is used to skip over a
X	    defined set of characters with parsing, usually. 
X	**/
X
X	register int loc = 0, key_index = 0;
X
X	while (source[loc] != '\0') {
X	  key_index = 0;
X	  while (keys[key_index] != source[loc])
X	    if (keys[key_index++] == '\0')
X	      return(loc);
X	  loc++;
X	}
X
X	return(loc);
X}
X
Xstrcspn(source, keys)
Xchar *source, *keys;
X{
X	/** This function returns the length of the substring of
X	    'source' (starting at zero) that consists entirely of
X	    characters NOT from 'keys'.  This is used to skip to a
X	    defined set of characters with parsing, usually. 
X	    NOTE that this is the opposite of strspn() above
X	**/
X
X	register int loc = 0, key_index = 0;
X
X	while (source[loc] != '\0') {
X	  key_index = 0;
X	  while (keys[key_index] != '\0')
X	    if (keys[key_index++] == source[loc])
X	      return(loc);
X	  loc++;
X	}
X
X	return(loc);
X}
X
Xchar *strtok(source, keys)
Xchar *source, *keys;
X{
X	/** This function returns a pointer to the next word in source
X	    with the string considered broken up at the characters 
X	    contained in 'keys'.  Source should be a character pointer
X	    when this routine is first called, then NULL subsequently.
X	    When strtok has exhausted the source string, it will 
X	    return NULL as the next word. 
X
X	    WARNING: This routine will DESTROY the string pointed to
X	    by 'source' when first invoked.  If you want to keep the
X	    string, make a copy before using this routine!!
X	 **/
X
X	register int  last_ch;
X	static   char *sourceptr;
X		 char *return_value;
X
X	if (source != NULL)
X	  sourceptr = source;
X	
X	if (*sourceptr == '\0') 
X	  return(NULL);		/* we hit end-of-string last time!? */
X
X	sourceptr += strspn(sourceptr, keys);	/* skip leading crap */
X	
X	if (*sourceptr == '\0') 
X	  return(NULL);		/* we've hit end-of-string */
X
X	last_ch = strcspn(sourceptr, keys);	/* end of good stuff */
X
X	return_value = sourceptr;		/* and get the ret   */
X
X	sourceptr += last_ch;			/* ...value 	     */
X
X	if (*sourceptr != '\0')		/* don't forget if we're at END! */
X	  sourceptr++;			   /* and skipping for next time */
X
X	return_value[last_ch] = '\0';		/* ..ending right    */
X	
X	return((char *) return_value);		/* and we're outta here! */
X}
X
Xchar *strchr(buffer, ch)
Xchar *buffer, ch;
X{
X	/** Returns a pointer to the first occurance of the character
X	    'ch' in the specified string or NULL if it doesn't occur **/
X
X	char *address;
X
X	address = buffer;
X
X	while (*address != ch) {
X	  if (*address == '\0')
X	    return (NULL);
X	  address++;
X	}
X
X	return ( (char *) address);
X}
X
X#endif
X
X#ifndef NULL
X# define NULL		0
X#endif
X
X#define DONE		0
X#define ERROR		-1
X
Xchar *optional_arg;			/* optional argument as we go */
Xint   opt_index;			/* argnum + 1 when we leave   */
X
Xint  _indx = 1, _argnum = 1;
X
Xint
Xgetopt(argc, argv, options)
Xint argc;
Xchar *argv[], *options;
X{
X	/** Returns the character argument next, and optionally instantiates 
X	    "argument" to the argument associated with the particular option 
X	**/
X	
X	char        *word, *strchr();
X
X	if (_argnum >= argc) {	/* quick check first - no arguments! */
X	  opt_index = argc;
X	  return(DONE);
X	}
X
X	if (_indx >= strlen(argv[_argnum]) && _indx > 1) {
X	  _argnum++;
X	  _indx = 1;		/* zeroeth char is '-' */
X	}
X
X	if (_argnum >= argc) {
X	  opt_index = _argnum; /* no more args */
X	  return(DONE);
X	}
X
X	if (argv[_argnum][0] != '-') {
X	  opt_index = _argnum;
X	  return(DONE);
X	}
X
X        word = strchr(options, argv[_argnum][_indx++]);
X
X	if (word == NULL)
X	  return(ERROR);		/* Sun compatibility */
X
X	if (word == NULL || strlen(word) == 0) 
X	  return(ERROR);
X	
X	if (word[1] == ':') {
X
X	  /** Two possibilities - either tailing end of this argument or the 
X	      next argument in the list **/
X
X	  if (_indx < strlen(argv[_argnum])) { /* first possibility */
X	    optional_arg = (char *) (argv[_argnum] + _indx);
X	    _argnum++;
X	    _indx = 1;
X	  }
X	  else {				/* second choice     */
X	    if (++_argnum >= argc) 
X	      return(ERROR);			/* no argument!!     */
X
X	    optional_arg = (char *) argv[_argnum++];
X	    _indx = 1;
X	  }
X	}
X
X	return((int) word[0]);
X}
END_OF_filter/utils2.c
if test 4993 -ne `wc -c <filter/utils2.c`; then
    echo shar: \"filter/utils2.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"src/aliaslib.c\" \(4735 characters\)
if test -f src/aliaslib.c ; then 
  echo shar: Will not over-write existing file \"src/aliaslib.c\"
else
sed "s/^X//" >src/aliaslib.c <<'END_OF_src/aliaslib.c'
X/**			aliaslib.c			**/
X
X/** Library of functions dealing with the alias system...
X
X    (C) Copyright 1986 Dave Taylor
X **/
X
X#include "headers.h"
X
Xchar *expand_group(), *get_alias_address(), *expand_system();
Xchar *get_token(), *strpbrk();
Xlong lseek();
X
Xchar *get_alias_address(name, mailing, depth)
Xchar *name;
Xint   mailing, depth;
X{
X	/** return the line from either datafile that corresponds 
X	    to the specified name.  If 'mailing' specified, then
X	    fully expand group names.  Depth specifies the nesting
X	    depth - the routine should always initially be called
X	    with this equal 0.  Returns NULL if not found   **/
X
X	static char buffer[VERY_LONG_STRING];
X	int    loc;
X
X	if (strlen(name) == 0)
X	  return( (char *) NULL);
X
X	if (! read_in_aliases) {
X	  read_alias_files();
X	  read_in_aliases = TRUE;
X	}
X
X	if (user_files) 
X	  if ((loc = find(name, user_hash_table, MAX_UALIASES)) >= 0) {
X	    lseek(user_data, user_hash_table[loc].byte, 0L);
X	    get_line(user_data, buffer);
X	    if (buffer[0] == '!' && mailing)
X	      return(expand_group(buffer, depth));
X	    else if (strpbrk(buffer,"!@:") != NULL)	/* has a hostname */
X#ifdef DONT_TOUCH_ADDRESSES
X	      return((char *) buffer);
X#else
X	      return(expand_system(buffer, TRUE));
X#endif
X	    else
X	      return((char *) buffer);
X	  }
X	 
X	if (system_files) 
X	  if ((loc = find(name, system_hash_table, MAX_SALIASES)) >= 0) {
X	    lseek(system_data, system_hash_table[loc].byte, 0L);
X	    get_line(system_data, buffer);
X	    if (buffer[0] == '!' && mailing)
X	      return(expand_group(buffer, depth));
X	    else if (strpbrk(buffer,"!@:") != NULL)	/* has a hostname */
X#ifdef DONT_TOUCH_ADDRESSES
X	      return((char *) buffer);
X#else
X	      return(expand_system(buffer, TRUE));
X#endif
X	    else
X	      return((char *) buffer);
X	  }
X	
X	return( (char *) NULL);
X}
X
Xchar *expand_system(buffer, show_errors)
Xchar *buffer;
Xint   show_errors;
X{
X	/** This routine will check the first machine name in the given path 
X	    (if any) and expand it out if it is an alias...if not, it will 
X	    return what it was given.  If show_errors is false, it won't 
X	    display errors encountered...
X	**/
X
X	dprint2(6, "expand_system(%s, show-errors=%s)\n", buffer,
X		onoff(show_errors));
X	findnode(buffer, show_errors);
X
X	return( (char *) buffer);
X}
X	      
Xchar *expand_group(members, depth)
Xchar *members;
Xint  depth;
X{
X	/** Given a group of names separated by commas, this routine
X	    will return a string that is the full addresses of each
X	    member separated by spaces. Depth is an internal counter
X	    that keeps track of the depth of nesting that the routine
X	    is in...it's for the get_token routine!  **/
X
X	static char buffer[VERY_LONG_STRING];
X	char   buf[LONG_STRING], *word, *address, *bufptr;
X	char   *strcpy();
X
X	strcpy(buf, members); 			/* parameter safety! */
X	if (depth == 0) buffer[0] = '\0';	/* nothing in yet!   */
X	bufptr = (char *) buf;			/* grab the address  */
X	depth++;				/* one deeper!       */
X
X	while ((word = get_token(bufptr, "!, ", depth)) != NULL) {
X	  if ((address = get_alias_address(word, 1, depth)) == NULL) {
X	    if (! valid_name(word)) {
X	      dprint2(3, "Encountered illegal address %s (%s)\n",
X			word, "expand_group");
X	      error1("%s is an illegal address!", word);
X	      return( (char *) NULL);
X	    }
X	    else if (strcmp(buffer, word) != 0)
X	      sprintf(buffer, "%s%s%s", buffer,
X		    (strlen(buffer) > 0)? ", ":"", word);
X	  }
X	  else if (strcmp(buffer, address) != 0)
X	    sprintf(buffer,"%s%s%s", buffer, 
X		    (strlen(buffer) > 0)? ", ":"", address);
X
X	  bufptr = NULL;
X	}
X
X	return( (char *) buffer);
X}
X
Xint
Xfind(word, table, size)
Xchar *word;
Xstruct alias_rec table[];
Xint size;
X{
X	/** find word and return loc, or -1 **/
X	register int loc;
X	
X	if (strlen(word) > 20) {
X	  dprint2(3, "Too long alias name entered [%s] (%s)\n", word, "find");
X	  error1("Bad alias name: %s.  Too long.\n", word);
X	  return(-1);
X	}
X
X	loc = hash_it(word, size);
X
X	while (strcmp(word, table[loc].name) != 0) {
X	  if (table[loc].name[0] == '\0')
X	    return(-1);
X	  loc = (loc + 1) % size; 
X	}
X
X	return(loc);
X}
X
Xint
Xhash_it(string, table_size)
Xchar *string;
Xint   table_size;
X{
X	/** compute the hash function of the string, returning
X	    it (mod table_size) **/
X
X	register int i, sum = 0;
X	
X	for (i=0; string[i] != '\0'; i++)
X	  sum += (int) string[i];
X
X	return(sum % table_size);
X}
X
Xget_line(fd, buffer)
Xint fd;
Xchar *buffer;
X{
X	/* Read from file fd.  End read upon reading either 
X	   EOF or '\n' character (this is where it differs 
X	   from a straight 'read' command!) */
X
X	register int i= 0;
X	char     ch;
X
X	while (read(fd, &ch, 1) > 0)
X	  if (ch == '\n' || ch == '\r') {
X	    buffer[i] = 0;
X	    return;
X	  }
X	  else
X	    buffer[i++] = ch;
X}
END_OF_src/aliaslib.c
if test 4735 -ne `wc -c <src/aliaslib.c`; then
    echo shar: \"src/aliaslib.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"src/edit.c\" \(5274 characters\)
if test -f src/edit.c ; then 
  echo shar: Will not over-write existing file \"src/edit.c\"
else
sed "s/^X//" >src/edit.c <<'END_OF_src/edit.c'
X/**			edit.c			**/
X
X/** This routine is for allowing the user to edit their current mailbox
X    as they wish. 
X
X    (C) Copyright 1986 Dave Taylor
X**/
X
X#include "headers.h"
X#include <errno.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X
Xextern int errno;
X
Xchar   *error_name(), *error_description(), *strcpy();
Xlong   bytes();
Xunsigned long sleep();
X
Xedit_mailbox()
X{
X	/** Allow the user to edit their mailbox, always resynchronizing
X	    afterwards.   Due to intense laziness on the part of the
X	    programmer, this routine will invoke $EDITOR on the entire
X	    file.  The mailer will ALWAYS resync on the mailbox file
X	    even if nothing has changed since, not unreasonably, it's
X	    hard to figure out what occurred in the edit session...
X	
X	    Also note that if the user wants to edit their incoming
X	    mailbox they'll actually be editing the tempfile that is
X	    an exact copy.  More on how we resync in that case later
X	    in this code.
X	**/
X
X	FILE     *real_mailbox, *temp_mailbox;
X	char     filename[SLEN], buffer[LONG_SLEN], temp_infile[SLEN];
X	struct   stat stat_buffer;
X	int      loaded_stat_buffer = FALSE;
X
X	PutLine0(LINES-1,0,"invoking editor...");
X
X	if (mbox_specified == 0) {
X	  sprintf(filename, "%s%s", temp_mbox, username);
X	  chown(filename, userid, groupid);		/* make sure we can! */
X	}
X	else 
X	  strcpy(filename, infile);
X
X	/** now get and save the ownership and permissions... **/
X
X	if (stat(infile, &stat_buffer)) {
X	  error("Warning: couldn't 'stat' file, perms might get mangled");
X	  sleep(2);
X	}
X	else
X	  loaded_stat_buffer = TRUE;
X
X	sprintf(buffer, "%s %s", alternative_editor, filename);
X
X	Raw(OFF);
X
X	if (system_call(buffer, SH) != 0) {
X	  error1("Problems invoking editor %s!", alternative_editor);
X	  Raw(ON);
X	  sleep(2);
X	  return(0);
X	}
X
X	Raw(ON);
X
X	if (mbox_specified == 0) {	/* uh oh... now the toughie...  */
X
X	  sprintf(temp_infile, "%s%s.temp", mailhome, username);
X	  unlink(temp_infile);	/* remove it if it's there... */
X
X	  if (bytes(infile) != mailfile_size) {
X
X	     /* SIGH.  We've received mail since we invoked the editor
X		on the mailbox.  We'll have to do some strange stuff to
X	        remedy the problem... */
X
X	     PutLine0(LINES, 0, "Warning: new mail received...");
X	     CleartoEOLN();
X
X	     if ((temp_mailbox = fopen(filename, "a")) == NULL) {
X		dprint2(1, "Attempt to open %s to append failed! (%s)\n", 
X			filename, "edit_mailbox");
X	       set_error("Couldn't reopen tempfile.  Edit LOST!");
X	       return(1);
X	     }
X	     /** Now let's lock the mailbox up and stream the new stuff 
X		 into the temp file... **/
X
X	     lock(OUTGOING);	
X	     if ((real_mailbox = fopen(infile, "r")) == NULL) {
X	       dprint2(1, 
X		       "Attempt to open %s for reading new mail failed! (%s)\n",
X 		 	infile, "edit_mailbox");
X	       sprintf(buffer, "Couldn't open %s for reading!  Edit LOST!", 
X		       infile);
X	       set_error(buffer);
X	       unlock();
X	       return(1);
X	     }
X	     if (fseek(real_mailbox, mailfile_size, 0) != 0) {
X	       dprint2(1, "Couldn't seek to end of infile (offset %ld) (%s)\n",
X			mailfile_size, "edit_mailbox");
X	       set_error("Couldn't seek to end of mailbox.  Edit LOST!");
X	       unlock();
X	       return(1);
X	     }
X	
X	     /** Now we can finally stream the new mail into the tempfile **/
X
X	     while (fgets(buffer, LONG_SLEN, real_mailbox) != NULL)
X	       fprintf(temp_mailbox, "%s", buffer);
X
X	     fclose(real_mailbox);
X	     fclose(temp_mailbox);
X 	   }
X	   else
X	     lock(OUTGOING); /* create a lock file if we're replacing mailbox */
X
X	   /** link to the temporary mailbox in the mailhome directory... **/
X
X	   if (link(filename, temp_infile) != 0) 
X	     if (errno == EXDEV) {   /* attempt to link across file systems */
X   	       if (copy(filename, temp_infile) != 0) {
X	         error("Couldn't copy temp file to mailbox!");
X	         unlock();					/* ciao!*/
X	   	 emergency_exit();
X	       }
X	     }
X	     else {
X		Write_to_screen("\n\rCouldn't link %s to mailfile %s...\n\r",2,
X			filename, temp_infile);
X		Write_to_screen("** %s - %s **\n\r", 2,
X			error_name(errno), error_description(errno));
X	        emergency_exit();
X	     }
X	
X	   /***  G U L P ... let's remove the incoming mail file... ***/
X	     
X	   unlink(infile);
X
X	   /** and quickly now... **/
X
X	   if (link(temp_infile, infile) != 0) {
X	     Write_to_screen(
X		    "\n\rCouldn't internally link %s to mailfile %s...\n\r",
X		    2, temp_infile, infile);
X	     Write_to_screen(
X		    "\n\rYou'll need to check out %s for your mail...\n\r",
X		    1, temp_infile);
X	     Write_to_screen("** %s - %s **\n\r", 2,
X		    error_name(errno), error_description(errno));
X	     emergency_exit();
X	   }
X
X	   /** And let's remove the lock file!  We're DONE!!!  **/
X
X	   unlock();
X	   unlink(temp_infile);	/* remove the temp file too */
X	   unlink(filename);	/* remove the temp file too */
X	   error("edit changes incorporated into new mail...");
X	}
X	else
X	  error("Resynchronizing with new version of mailbox...");
X
X	sleep(2);
X	resync();
X
X	current = 1;		/* don't leave the user hanging! */
X
X	/** finally restore the permissions... **/
X
X	if (loaded_stat_buffer) {	/* if not, it's junk! */
X	  chown(infile, stat_buffer.st_uid, stat_buffer.st_gid);
X	  chmod(infile, stat_buffer.st_mode);
X	}
X
X	return(1);
X}
END_OF_src/edit.c
if test 5274 -ne `wc -c <src/edit.c`; then
    echo shar: \"src/edit.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"src/file_utils.c\" \(5169 characters\)
if test -f src/file_utils.c ; then 
  echo shar: Will not over-write existing file \"src/file_utils.c\"
else
sed "s/^X//" >src/file_utils.c <<'END_OF_src/file_utils.c'
X/**			file_utils.c			**/
X
X/** File oriented utility routines for ELM 
X
X    (C) Copyright 1986 Dave Taylor
X**/
X
X#include "headers.h"
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <ctype.h>
X#include <errno.h>
X
X#ifdef BSD
X# undef tolower
X#endif
X
X#include <signal.h>
X#include <errno.h>
X
X#ifdef BSD
X# include <sys/wait.h>
X#endif
X
Xextern int errno;		/* system error number */
X
Xchar *error_name(), *error_description(), *strcpy(), *getlogin();
X
Xlong
Xbytes(name)
Xchar *name;
X{
X	/** return the number of bytes in the specified file.  This
X	    is to check to see if new mail has arrived....  **/
X
X	int ok = 1;
X	extern int errno;	/* system error number! */
X	struct stat buffer;
X
X	if (stat(name, &buffer) != 0)
X	  if (errno != 2) {
X	    dprint2(1,"Error: errno %s on fstat of file %s (bytes)\n", 
X		     error_name(errno), name);
X	    Write_to_screen("\n\rError attempting fstat on file %s!\n\r",
X		     1, name);
X	    Write_to_screen("** %s - %s **\n\r", 2, error_name(errno),
X		  error_description(errno));
X	    emergency_exit();
X	  }
X	  else
X	    ok = 0;
X	
X	return(ok ? (long) buffer.st_size : 0L);
X}
X
Xint
Xcan_access(file, mode)
Xchar *file; 
Xint   mode;
X{
X	/** returns ZERO iff user can access file or "errno" otherwise **/
X
X	int the_stat = 0, pid, w; 
X	void _exit(), exit();
X#ifdef BSD
X	union wait status;
X#else
X	int status;
X#endif
X	register int (*istat)(), (*qstat)();
X	
X#ifdef NO_VM		/* machine without virtual memory!! */
X	if ((pid = fork()) == 0) {
X#else
X	if ((pid = vfork()) == 0) {
X#endif
X	  setgid(groupid);
X	  setuid(userid);		/** back to normal userid **/
X
X	  errno = 0;
X
X	  if (access(file, mode) == 0) 
X	    _exit(0);
X	  else 
X	    _exit(errno != 0? errno : 1);	/* never return zero! */
X	  _exit(127);
X	}
X
X	istat = signal(SIGINT, SIG_IGN);
X	qstat = signal(SIGQUIT, SIG_IGN);
X
X	while ((w = wait(&status)) != pid && w != -1)
X		;
X
X#ifdef BSD
X	the_stat = status.w_retcode;
X#else
X	the_stat = status;
X#endif
X
X	signal(SIGINT, istat);
X	signal(SIGQUIT, qstat);
X
X	return(the_stat);
X}
X
Xint
Xcan_open(file, mode)
Xchar *file, *mode;
X{
X	/** Returns 0 iff user can open the file.  This is not
X	    the same as can_access - it's used for when the file might
X	    not exist... **/
X
X	FILE *fd;
X	int the_stat = 0, pid, w; 
X	void _exit(), exit();
X#ifdef BSD
X	union wait status;
X#else
X	int status;
X#endif
X	register int (*istat)(), (*qstat)();
X	
X#ifdef NO_VM		/* machine without virtual memory!! */
X	if ((pid = fork()) == 0) {
X#else
X	if ((pid = vfork()) == 0) {
X#endif
X	  setgid(groupid);
X	  setuid(userid);		/** back to normal userid **/
X	  errno = 0;
X	  if ((fd = fopen(file, mode)) == NULL)
X	    _exit(errno);
X	  else {
X	    fclose(fd);		/* don't just LEAVE it! */
X	    _exit(0);
X	  }
X	  _exit(127);
X	}
X
X	istat = signal(SIGINT, SIG_IGN);
X	qstat = signal(SIGQUIT, SIG_IGN);
X
X	while ((w = wait(&status)) != pid && w != -1)
X		;
X
X#ifdef BSD
X	the_stat = status.w_retcode;
X#else
X	the_stat = status;
X#endif
X	
X	signal(SIGINT, istat);
X	signal(SIGQUIT, qstat);
X
X	return(the_stat);
X}
X
Xint
Xcopy(from, to)
Xchar *from, *to;
X{
X	/** this routine copies a specified file to the destination
X	    specified.  Non-zero return code indicates that something
X	    dreadful happened! **/
X
X	FILE *from_file, *to_file;
X	char buffer[VERY_LONG_STRING];
X	
X	if ((from_file = fopen(from, "r")) == NULL) {
X	  dprint1(1,"Error: could not open %s for reading (copy)\n", from);
X	  error1("could not open file %s", from);
X	  return(1);
X	}
X
X	if ((to_file = fopen(to, "w")) == NULL) {
X	  dprint1(1,"Error: could not open %s for writing (copy)\n", to);
X	  error1("could not open file %s", to);
X	  return(1);
X	}
X
X	while (fgets(buffer, VERY_LONG_STRING, from_file) != NULL)
X	  fputs(buffer, to_file);
X
X	fclose(from_file);
X	fclose(to_file);
X
X	return(0);
X}
X
Xint
Xappend(fd, filename)
XFILE *fd;
Xchar *filename;
X{
X	/** This routine appends the specified file to the already
X	    open file descriptor.. Returns non-zero if fails.  **/
X
X	FILE *my_fd;
X	char buffer[VERY_LONG_STRING];
X	
X	if ((my_fd = fopen(filename, "r")) == NULL) {
X	  dprint1(1,"Error: could not open %s for reading (append)\n", filename);
X	  return(1);
X	}
X
X	while (fgets(buffer, VERY_LONG_STRING, my_fd) != NULL)
X	  fputs(buffer, fd);
X
X	fclose(my_fd);
X
X	return(0);
X}
X
Xcheck_mailfile_size()
X{
X	/** Check to ensure we have mail.  Only used with the '-z'
X	    starting option. **/
X
X	char filename[SLEN], *getlogin();
X	struct stat buffer;
X
X	strcpy(username,getlogin());
X	if (strlen(username) == 0)
X	  cuserid(username);
X
X	sprintf(filename,"%s%s", mailhome, username);
X
X	if (stat(filename, &buffer) == -1) {
X	  printf(" You have no mail.\n");
X	  exit(0);
X	}
X	else if (buffer.st_size < 2) { 		/* maybe one byte??? */
X	  printf("You have no mail to read.\n");
X	  exit(0);
X	}
X}
X
Xcreate_readmsg_file()
X{
X	/** Creates the file ".current" in the users home directory
X	    for use with the "readmsg" program.
X	**/
X
X	FILE *fd;
X	char buffer[SLEN];
X
X	sprintf(buffer,"%s/%s", home, readmsg_file);
X
X	if ((fd = fopen (buffer, "w")) == NULL) {
X	  dprint3(1,"Error: couldn't create file %s - error %s (%s)\n",
X		 buffer, error_name(errno), "create_readmsg_file");
X	  return;	/* no error to user */
X	}
X
X	fprintf(fd, "%d\n", header_table[current-1].index_number);
X	fclose(fd);
X}
END_OF_src/file_utils.c
if test 5169 -ne `wc -c <src/file_utils.c`; then
    echo shar: \"src/file_utils.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"src/mailtime.c\" \(4749 characters\)
if test -f src/mailtime.c ; then 
  echo shar: Will not over-write existing file \"src/mailtime.c\"
else
sed "s/^X//" >src/mailtime.c <<'END_OF_src/mailtime.c'
X/**			mailtime.c			**/
X
X/** This set of routines is used to figure out when the user last read
X    their mail and to also figure out if a given message is new or not.
X
X    (C) Copyright 1986 Dave Taylor
X**/
X
X#include "headers.h"
X
X#include <sys/types.h>
X#include <sys/stat.h>
X#ifdef BSD
X#  ifndef BSD4.1
X#    include <sys/time.h>
X#  else
X#    include <time.h>
X#    include <sys/timeb.h>
X#  endif
X#else
X#  include <time.h>
X#endif
X
Xresolve_received(entry)
Xstruct header_rec *entry;
X{
X	/** Entry has the data for computing the time and date the 
X	    message was received.  Fix it and return **/
X
X	switch (tolower(entry->month[0])) {
X	  case 'j' : if (tolower(entry->month[1]) == 'a')
X		       entry->received.month = JANUARY;
X		     else if (tolower(entry->month[2]) == 'n')
X	               entry->received.month = JUNE;
X		     else
X	               entry->received.month = JULY;
X	             break;
X	  case 'f' : entry->received.month = FEBRUARY;
X	 	     break;
X	  case 'm' : if (tolower(entry->month[2]) == 'r')
X	               entry->received.month = MARCH;
X		     else
X		       entry->received.month = MAY;
X	             break;
X	  case 'a' : if (tolower(entry->month[1]) == 'p')
X	               entry->received.month = APRIL;
X	             else
X	               entry->received.month = AUGUST;
X		     break;
X	  case 's' : entry->received.month = SEPTEMBER;
X		     break;
X	  case 'o' : entry->received.month = OCTOBER;
X		     break;
X	  case 'n' : entry->received.month = NOVEMBER;
X	  	     break;
X	  case 'd' : entry->received.month = DECEMBER;
X		     break;
X	}
X
X	sscanf(entry->day, "%d", &(entry->received.day));
X
X	sscanf(entry->year, "%d", &(entry->received.year));
X	if (entry->received.year > 100) entry->received.year -= 1900;
X
X	sscanf(entry->time, "%d:%d", &(entry->received.hour),
X	       &(entry->received.minute));
X}
X
Xget_mailtime()
X{
X	/** Instantiate the values of the last_read_mail stat
X	    variable based on the file access time/date of the
X	    file mailtime_file.  IF the file doesn't exist,
X	    then assume all mail is new. **/
X
X	struct stat buffer;
X	struct tm   *timebuf;
X	char   filename[SLEN];
X#ifdef BSD
X	extern struct tm *localtime();
X#endif
X
X	sprintf(filename, "%s/%s", home, mailtime_file);
X
X	if (stat(filename, &buffer) == -1) {
X	  last_read_mail.month = 0;
X	  last_read_mail.day = 0;
X	  last_read_mail.year = 0;
X	  last_read_mail.hour = 0;
X	  last_read_mail.minute = 0;
X	}
X	else {	/* stat okay... */
X	  timebuf = (struct tm *) localtime(&(buffer.st_mtime));
X	  
X	  last_read_mail.month = timebuf->tm_mon;
X	  last_read_mail.day = timebuf->tm_mday;
X	  last_read_mail.year = timebuf->tm_year;
X	  last_read_mail.hour = timebuf->tm_hour;
X	  last_read_mail.minute = timebuf->tm_min;
X	}
X}
X
Xupdate_mailtime()
X{
X	/** This routine updates the last modified time of the 
X	    .last_read_mail file in the users home directory.
X	    If the file doesn't exist, it creates it!! **/
X
X	char filename[SLEN];
X
X#ifdef BSD
X# ifdef BSD4.1
X	struct timeb  loc_time;
X	time_t tval;
X# else
X	struct timeval	tval[2];
X	struct timezone tzone;
X# endif
X#endif
X	
X	sprintf(filename, "%s/%s", home, mailtime_file);
X
X#ifdef BSD
X# ifdef BSD4.1
X	tval = (time_t) time((long *) 0);
X	if (utime(filename, &tval) == -1)
X# else
X	gettimeofday(&tval[0], &tzone);
X	gettimeofday(&tval[1], &tzone);
X
X	if (utimes(filename, tval) == -1)	/* note the "S" */
X# endif
X#else
X	if (utime(filename, NULL) == -1)	/* note no "S"  */
X#endif
X	/** That's what I like about programming for BSD & USG - the easy
X	    portability between 'em.  Especially the section 2 calls!! **/
X
X	  (void) creat(filename, 0777);
X}
X
Xnew_msg(entry)
Xstruct header_rec entry;
X{
X	/** Return true if the current message is NEW.  This can be
X	    easily tested by seeing 1) if we're reading the incoming
X	    mailbox and then, if so, 2) if the received_on_machine
X	    date is more recent than the last_read_mail date.
X	**/
X
X	if (mbox_specified != 0) return(FALSE);		/* not incoming */
X	
X	/** Two tests - if received is OLDER than last read mail, then
X	    immediately return FALSE.  If received is NEWER than last
X	    read mail then immediately return TRUE **/
X
X	if (entry.received.year < last_read_mail.year) 
X	  return(FALSE);				
X	
X	if (entry.received.year > last_read_mail.year) 
X	  return(TRUE);				
X	
X	if (entry.received.month < last_read_mail.month)
X	  return(FALSE);			
X
X	if (entry.received.month > last_read_mail.month)
X	  return(TRUE);			
X
X	if (entry.received.day < last_read_mail.day)
X	  return(FALSE);
X
X	if (entry.received.day > last_read_mail.day)
X	  return(TRUE);
X
X	if (entry.received.hour < last_read_mail.hour)		
X	  return(FALSE);		
X	
X	if (entry.received.hour > last_read_mail.hour)		
X	  return(TRUE);		
X	
X	if (entry.received.minute < last_read_mail.minute)
X	  return(FALSE);	
X
X	return(TRUE);
X}
END_OF_src/mailtime.c
if test 4749 -ne `wc -c <src/mailtime.c`; then
    echo shar: \"src/mailtime.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"src/opt_utils.c\" \(5098 characters\)
if test -f src/opt_utils.c ; then 
  echo shar: Will not over-write existing file \"src/opt_utils.c\"
else
sed "s/^X//" >src/opt_utils.c <<'END_OF_src/opt_utils.c'
X/**			opt_utils.c			**/
X
X/** This file contains routines that might be needed for the various
X     machines that the mailer can run on.  Please check the Makefile
X     for more help and/or information. 
X
X     (C) Copyright 1986 Dave Taylor
X**/
X
X#include <stdio.h>
X#include "headers.h"
X
X#ifdef BSD
X# include <pwd.h>
X#endif
X
X#ifdef NEED_GETHOSTNAME
X#  include <sys/utsname.h>
X#endif
X
X#ifdef UTS
X# include <sys/tubio.h>
X# define  TTYIN		0		/* standard input */
X#endif
X
X#ifdef NEED_GETHOSTNAME
X
Xgethostname(hostname,size) /* get name of current host */
Xint size;
Xchar *hostname;
X{
X	/** Return the name of the current host machine.  UTS only **/
X
X	/** This routine compliments of Scott McGregor at the HP
X	    Corporate Computing Center **/
X     
X	int uname();
X	struct utsname name;
X
X	(void) uname(&name);
X	(void) strncpy(hostname,name.nodename,size-1);
X	hostname[size] = '\0';
X
X}
X
X#endif
X
X#ifdef UTS
X
Xint
Xisa3270()
X{
X	/** Returns TRUE and sets LINES and COLUMNS to the correct values
X	    for an Amdahl (IBM) tube screen, or returns FALSE if on a normal
X	    terminal (of course, next to a 3270, ANYTHING is normal!!) **/
X
X	struct tubiocb tubecb;
X
X	dprint0(3,"Seeing if we're a 3270...");
X
X	if (ioctl(TTYIN, TUBGETMOD, &tubecb) == -1) {
X	  dprint0(3,"We're not!\n");
X	  return(FALSE);	/* not a tube! */
X	}
X	
X	LINES   = tubecb->line_cnt - 1;
X	COLUMNS = tubecb->col_cnt;
X	
X	dprint2(3,"We are!  %d lines and %d columns!\n",
X		LINES, COLUMNS);
X	return(TRUE);
X}
X
X#endif /* def UTS */
X
X#ifdef BSD
X
Xcuserid(uname)
Xchar *uname;
X{
X	/** Added for compatibility with Bell systems, this is the last-ditch
X	    attempt to get the users login name, after getlogin() fails.  It
X	    instantiates "uname" to the name of the user...
X	**/
X
X	struct passwd *password_entry, *getpwuid();
X
X	password_entry = getpwuid(getuid());
X
X	strcpy(uname, password_entry->pw_name);
X}
X
X/** some supplementary string functions for Berkeley Unix systems **/
X
Xstrspn(source, keys)
Xchar *source, *keys;
X{
X	/** This function returns the length of the substring of
X	    'source' (starting at zero) that consists ENTIRELY of
X	    characters from 'keys'.  This is used to skip over a
X	    defined set of characters with parsing, usually. 
X	**/
X
X	register int loc = 0, key_index = 0;
X
X	while (source[loc] != '\0') {
X	  key_index = 0;
X	  while (keys[key_index] != source[loc])
X	    if (keys[key_index++] == '\0')
X	      return(loc);
X	  loc++;
X	}
X
X	return(loc);
X}
X
Xstrcspn(source, keys)
Xchar *source, *keys;
X{
X	/** This function returns the length of the substring of
X	    'source' (starting at zero) that consists entirely of
X	    characters NOT from 'keys'.  This is used to skip to a
X	    defined set of characters with parsing, usually. 
X	    NOTE that this is the opposite of strspn() above
X	**/
X
X	register int loc = 0, key_index = 0;
X
X	while (source[loc] != '\0') {
X	  key_index = 0;
X	  while (keys[key_index] != '\0')
X	    if (keys[key_index++] == source[loc])
X	      return(loc);
X	  loc++;
X	}
X
X	return(loc);
X}
X
Xchar *strtok(source, keys)
Xchar *source, *keys;
X{
X	/** This function returns a pointer to the next word in source
X	    with the string considered broken up at the characters 
X	    contained in 'keys'.  Source should be a character pointer
X	    when this routine is first called, then NULL subsequently.
X	    When strtok has exhausted the source string, it will 
X	    return NULL as the next word. 
X
X	    WARNING: This routine will DESTROY the string pointed to
X	    by 'source' when first invoked.  If you want to keep the
X	    string, make a copy before using this routine!!
X	 **/
X
X	register int  last_ch;
X	static   char *sourceptr;
X		 char *return_value;
X
X	if (source != NULL)
X	  sourceptr = source;
X	
X	if (*sourceptr == '\0') 
X	  return(NULL);		/* we hit end-of-string last time!? */
X
X	sourceptr += strspn(sourceptr, keys);	/* skip leading crap */
X	
X	if (*sourceptr == '\0') 
X	  return(NULL);		/* we've hit end-of-string */
X
X	last_ch = strcspn(sourceptr, keys);	/* end of good stuff */
X
X	return_value = sourceptr;		/* and get the ret   */
X
X	sourceptr += last_ch;			/* ...value 	     */
X
X	if (*sourceptr != '\0')		/* don't forget if we're at END! */
X	  sourceptr++;			   /* and skipping for next time */
X
X	return_value[last_ch] = '\0';		/* ..ending right    */
X	
X	return((char *) return_value);		/* and we're outta here! */
X}
X
Xchar *strpbrk(source, keys)
Xchar *source, *keys;
X{
X	/** Returns a pointer to the first character of source that is any
X	    of the specified keys, or NULL if none of the keys are present
X	    in the source string. 
X	**/
X
X	register int loc = 0, key_index = 0;
X
X	while (source[loc] != '\0') {
X	  key_index = 0;
X	  while (keys[key_index] != '\0')
X	    if (keys[key_index++] == source[loc])
X	      return((char *) (source + loc));
X	  loc++;
X	}
X	
X	return(NULL);
X}
X
Xchar *strchr(buffer, ch)
Xchar *buffer, ch;
X{
X	/** Returns a pointer to the first occurance of the character
X	    'ch' in the specified string or NULL if it doesn't occur **/
X
X	char *address;
X
X	address = buffer;
X
X	while (*address != ch) {
X	  if (*address == '\0')
X	    return (NULL);
X	  address++;
X	}
X
X	return ( (char *) address);
X}
X
X#endif
END_OF_src/opt_utils.c
if test 5098 -ne `wc -c <src/opt_utils.c`; then
    echo shar: \"src/opt_utils.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"src/sort.c\" \(4826 characters\)
if test -f src/sort.c ; then 
  echo shar: Will not over-write existing file \"src/sort.c\"
else
sed "s/^X//" >src/sort.c <<'END_OF_src/sort.c'
X/**			sort.c				**/
X
X/** Sort mailbox header table by the field specified in the global
X    variable "sortby"...if we're sorting by something other than
X    the default SENT_DATE, also put some sort of indicator on the
X    screen.
X
X    (C) Copyright 1986, Dave Taylor
X**/
X
X#include "headers.h"
X
Xchar *sort_name();
Xvoid   qsort();
X
Xsort_mailbox(entries, visible)
Xint entries, visible;
X{
X	/** Sort the header_table definitions... If 'visible', then
X	    put the status lines etc **/
X	
X	int last_index = -1;
X	int compare_headers();	/* for sorting */
X
X	dprint1(2,"\n** sorting mailbox by %s **\n\n", sort_name(FULL));
X
X	if (entries > 0)
X	  last_index = header_table[current-1].index_number;
X
X	if (entries > 30 && visible)  
X	  error1("sorting messages by %s", sort_name(FULL));
X	
X	qsort(header_table, (unsigned) entries, sizeof (struct header_rec), 
X	      compare_headers);
X
X	if (last_index > -1)
X	  find_old_current(last_index);
X
X	clear_error();
X}
X
Xint
Xcompare_headers(first, second)
Xstruct header_rec *first, *second;
X{
X	/** compare two headers according to the sortby value.
X
X	    Sent Date uses a routine to compare two dates,
X	    Received date is keyed on the file offsets (think about it)
X	    Sender uses the truncated from line, same as "build headers",
X	    and size and subject are trivially obvious!!
X	 **/
X
X	char from1[SLEN], from2[SLEN];	/* sorting buffers... */
X	int  sign = 1;
X	
X	if (sortby < 0)
X	  sign = -1;
X
X	switch (abs(sortby)) {
X
X	  case SENT_DATE : return( sign*compare_dates(first, second));
X
X	  case RECEIVED_DATE: return( sign*
X			              compare_parsed_dates(first->received, 
X				           second->received));
X
X	  case SENDER    : tail_of(first->from, from1, TRUE);
X			   tail_of(second->from, from2, TRUE);
X	  		   return( sign*strcmp(from1, from2));
X
X	  case SIZE      : return( sign*(first->lines - second->lines));
X
X	  case SUBJECT   : /* need some extra work 'cause of STATIC buffers */
X	                   strcpy(from1, shift_lower(first->subject));	
X			   return( 
X			     sign*strcmp(from1, shift_lower(second->subject)));
X
X	  case STATUS    : return( sign*(first->status - second->status));
X	}
X
X	return(0);	/* never get this! */
X}
X
Xchar *sort_name(type)
Xint type;
X{
X	/** return the name of the current sort option...
X	    type can be "FULL", "SHORT" or "PAD"
X	**/
X	int pad, abr;
X	
X	pad = (type == PAD);
X	abr = (type == SHORT);
X
X	if (sortby < 0) {
X	  switch (- sortby) {
X	    case SENT_DATE    : return( 
X		              pad?     "Reverse Date Mail Sent  " : 
X			      abr?     "Reverse-Sent" :
X				       "Reverse Date Mail Sent");
X	    case RECEIVED_DATE: return(
X			      abr?     "Reverse-Received":
X			               "Reverse Date Mail Rec'vd");
X	    case SENDER       : return(
X			      pad?     "Reverse Message Sender  " : 
X			      abr?     "Reverse-From":
X				       "Reverse Message Sender");
X	    case SIZE         : return(
X			      abr?     "Reverse-Lines" : 
X				       "Reverse Lines in Message");
X	    case SUBJECT      : return(
X			      pad?     "Reverse Message Subject " : 
X			      abr?     "Reverse-Subject" : 
X				       "Reverse Message Subject");
X	    case STATUS	      : return(
X			      pad?     "Reverse Message Status  " :
X			      abr?     "Reverse-Status":
X			               "Reverse Message Status");
X	  }
X	}
X	else {
X	  switch (sortby) {
X	    case SENT_DATE    : return( 
X		                pad?   "Date Mail Sent          " : 
X		                abr?   "Sent" : 
X				       "Date Mail Sent");
X	    case RECEIVED_DATE: return(
X	                        pad?   "Date Mail Rec'vd        " :
X	                        abr?   "Received" :
X	                               "Date Mail Rec'vd");
X	    case SENDER       : return(
X			        pad?   "Message Sender          " : 
X			        abr?   "From" : 
X				       "Message Sender");
X	    case SIZE         : return(
X	    			pad?   "Lines in Message        " :
X	    			abr?   "Lines" :
X	    			       "Lines in Message");
X	    case SUBJECT      : return(
X			        pad?   "Message Subject         " : 
X			        abr?   "Subject" : 
X				       "Message Subject");
X	    case STATUS	      : return(
X			        pad?   "Message Status          " :
X			        abr?   "Status" :
X			               "Message Status");
X	  }
X	}
X
X	return("*UNKNOWN-SORT-PARAMETER*");
X}
X
Xfind_old_current(index)
Xint index;
X{
X	/** Set current to the message that has "index" as it's 
X	    index number.  This is to track the current message
X	    when we resync... **/
X
X	register int i;
X
X	dprint1(2,"find-old-current(%d)\n", index);
X
X	for (i = 0; i < message_count; i++)
X	  if (header_table[i].index_number == index) {
X	    current = i+1;
X	    dprint1(2,"\tset current to %d!\n", current);
X	    return;
X	  }
X
X	dprint1(2,"\tcouldn't find current index.  Current left as %d\n",
X		current);
X	return;		/* can't be found.  Leave it alone, then */
X}
END_OF_src/sort.c
if test 4826 -ne `wc -c <src/sort.c`; then
    echo shar: \"src/sort.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"src/syscall.c\" \(5245 characters\)
if test -f src/syscall.c ; then 
  echo shar: Will not over-write existing file \"src/syscall.c\"
else
sed "s/^X//" >src/syscall.c <<'END_OF_src/syscall.c'
X/**			syscall.c		**/
X
X/** These routines are used for user-level system calls, including the
X    '!' command and the '|' commands...
X
X    (C) Copyright 1986 Dave Taylor
X**/
X
X#include "headers.h"
X
X#include <signal.h>
X
X#ifdef BSD
X#  include <sys/wait.h>
X#endif
X
Xchar *argv_zero();	
Xvoid  _exit();
X
Xint
Xsubshell()
X{
X	/** spawn a subshell with either the specified command
X	    returns non-zero if screen rewrite needed
X	**/
X
X	char command[SLEN];
X	int  ret;
X
X	PutLine0(LINES-3,COLUMNS-40,"(use the shell name for a shell)");
X	PutLine0(LINES-2,0,"Shell Command: ");
X	command[0] = '\0';
X	(void) optionally_enter(command, LINES-2, 15, FALSE);
X	if (strlen(command) == 0) {
X	  MoveCursor(LINES-2,0);	CleartoEOLN();
X	  return(0);
X	}
X
X	MoveCursor(LINES,0); 	CleartoEOLN();
X	Raw(OFF);
X	if (cursor_control)  transmit_functions(OFF);
X	
X	ret = system_call(command, USER_SHELL);
X
X	PutLine0(LINES, 0, "\n\nPress <return> to return to ELM: ");
X
X	Raw(ON);
X	(void) getchar();
X	if (cursor_control)  transmit_functions(ON);
X
X	if (ret != 0) error1("Return code was %d", ret);
X	return(1);
X}
X
Xsystem_call(string, shell_type)
Xchar *string;
Xint   shell_type;
X{
X	/** execute 'string', setting uid to userid... **/
X	/** if shell-type is "SH" /bin/sh is used regardless of the 
X	    users shell setting.  Otherwise, "USER_SHELL" is sent **/
X
X	int stat = 0, pid, w;
X#ifdef BSD
X	union wait status;
X#else
X	int status;
X#endif
X	register int (*istat)(), (*qstat)();
X	
X	dprint2(2,"System Call: %s\n\t%s\n", shell_type == SH? "/bin/sh" : shell,
X		string);
X
X#ifdef NO_VM		/* machine without virtual memory! */
X	if ((pid = fork()) == 0) {
X#else
X	if ((pid = vfork()) == 0) {
X#endif
X	  setgid(groupid);	/* and group id		    */
X	  setuid(userid);	/* back to the normal user! */
X
X	  if (strlen(shell) > 0 && shell_type == USER_SHELL) {
X	    execl(shell, argv_zero(shell), "-c", string, (char *) 0);
X	  }
X	  else 
X	    execl("/bin/sh", "sh", "-c", string, (char *) 0);
X	  _exit(127);
X	}
X
X	istat = signal(SIGINT, SIG_IGN);
X	qstat = signal(SIGQUIT, SIG_IGN);
X
X	while ((w = wait(&status)) != pid && w != -1)
X		;
X
X#ifdef BSD
X	if (status.w_retcode != 0) stat = status.w_retcode;
X#else
X	if (w == -1) stat = status;
X#endif
X	
X	signal(SIGINT, istat);
X	signal(SIGQUIT, qstat);
X
X	return(stat);
X}
X
Xint
Xdo_pipe()
X{
X	/** pipe the tagged messages to the specified sequence.. **/
X
X	char command[SLEN], buffer[LONG_SLEN], message_list[SLEN];
X	register int  ret, tagged = 0, i;
X
X	message_list[0] = '\0';	/* NULL string to start... */
X
X	for (i=0; i < message_count; i++)
X	  if (ison(header_table[i].status, TAGGED)) {
X	    sprintf(message_list,"%s %d", message_list, 
X		    header_table[i].index_number);
X	    tagged++;
X	  }
X
X	if (tagged > 1)
X	  PutLine0(LINES-2,0,"Pipe tagged msgs to: ");
X	else if (tagged) 
X	  PutLine0(LINES-2,0,"Pipe tagged msg to : ");
X	else {
X	  PutLine0(LINES-2,0,"Pipe current msg to: ");
X	  sprintf(message_list,"%d", header_table[current-1].index_number);
X	}
X
X	command[0] = '\0';
X
X	(void) optionally_enter(command, LINES-2, 21, FALSE);
X	if (strlen(command) == 0) {
X	  MoveCursor(LINES-2,0);	CleartoEOLN();
X	  return(0);
X	}
X
X	MoveCursor(LINES,0); 	CleartoEOLN();
X	Raw(OFF);
X
X	if (cursor_control)  transmit_functions(OFF);
X	
X	sprintf(buffer, "%s -f %s -h %s | %s",
X		readmsg,
X		infile,
X		message_list,
X		command);
X	
X	ret = system_call(buffer, USER_SHELL);
X
X	PutLine0(LINES, 0, "\n\nPress <return> to return to ELM: ");
X	Raw(ON);
X	(void) getchar();
X	if (cursor_control)  transmit_functions(ON);
X
X	if (ret != 0) error1("Return code was %d", ret);
X	return(1);
X}
X
Xprintmsg()
X{
X	/** Print current message or tagged messages using 'printout' 
X	    variable.  Error message iff printout not defined! **/
X
X	char buffer[LONG_SLEN], filename[SLEN], printbuffer[LONG_SLEN];
X	char message_list[SLEN];
X	register int  retcode, tagged = 0, i;
X
X	if (strlen(printout) == 0) {
X	  error("Don't know how to print - option \"printmail\" undefined!");
X	  return;
X	}
X	
X	message_list[0] = '\0';	/* reset to null... */
X
X	for (i=0; i < message_count; i++) 
X	  if (header_table[i].status & TAGGED) {
X	    sprintf(message_list, "%s %d", message_list, 
X		    header_table[i].index_number);
X	    tagged++;
X	  }
X
X	if (! tagged)
X	  sprintf(message_list," %d", header_table[current-1].index_number);
X
X	sprintf(filename,"%s%d", temp_print, getpid());
X
X	if (in_string(printout, "%s"))
X	  sprintf(printbuffer, printout, filename);
X	else
X	  sprintf(printbuffer, "%s %s", printout, filename);
X
X	sprintf(buffer,"(%s -p -f %s%s > %s; %s 2>&1) > /dev/null",
X		readmsg, infile, message_list, 
X		filename,
X		printbuffer);
X	
X	dprint0(2,"Printing system call...\n");
X
X  	Centerline(LINES, "queueing...");
X
X	if ((retcode = system_call(buffer, SH)) == 0) {
X	  sprintf(buffer, "Message%s queued up to print", plural(tagged));
X	  Centerline(LINES, buffer);
X	}
X	else
X	  error1("Printout failed with return code %d", retcode);
X
X	unlink(filename);	/* remove da temp file! */
X}
X
Xlist_folders()
X{
X	/** list the folders in the users FOLDERHOME directory.  This is
X	    simply a call to "ls -C"
X	**/
X
X	char buffer[SLEN];
X
X	CleartoEOS();	/* don't leave any junk on the bottom of the screen */
X	sprintf(buffer, "cd %s;ls -C", folders);
X	printf("\n\rContents of your folder directory:\n\r\n\r");
X	system_call(buffer, SH); 
X	printf("\n\r");
X}
X
END_OF_src/syscall.c
if test 5245 -ne `wc -c <src/syscall.c`; then
    echo shar: \"src/syscall.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: End of archive 4 \(of 19\).
cp /dev/null ark4isdone
DONE=true
for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ; do
    if test ! -f ark${I}isdone ; then
	echo shar: You still need to run archive ${I}.
	DONE=false
    fi
done
if test "$DONE" = "true" ; then
	echo You have unpacked all 19 archives.
	echo "See the Instructions file"
	rm -f ark[1-9]isdone ark[1-9][0-9]isdone
fi
##  End of shell archive.
exit 0