[mod.sources] v09i008: ELM Mail System, Part08/19

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

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

#! /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 8 (of 19)."
# Contents:  filter/parse.c src/input_utils.c src/mailmsg1.c
#   src/options.c utils/answer.c
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo shar: Extracting \"filter/parse.c\" \(8929 characters\)
if test -f filter/parse.c ; then 
  echo shar: Will not over-write existing file \"filter/parse.c\"
else
sed "s/^X//" >filter/parse.c <<'END_OF_filter/parse.c'
X/**			filter_parse.c			**/
X
X/** This is the parser for the filter program.  It accepts a wide variety of
X    constructs, building the ruleset table as it goes along.  Check the 
X    data structure in filter.h for more information on how the rules are
X    stored.  The parser is a cunning state-table based program.
X
X   (C) Copyright 1986, Dave Taylor
X**/
X
X#include <stdio.h>
X#include <ctype.h>
X
X#include "defs.h"
X#include "filter.h"
X
X#define NONE			0
X#define AND			10
X
X#define NEXT_CONDITION		0
X#define GETTING_OP		1
X#define READING_ARGUMENT	2
X#define READING_ACTION		3
X#define ACTION_ARGUMENT		4
X
Xchar *strtok(), *whatname(), *actionname();
X
Xint
Xget_filter_rules()
X{
X	/** Given the users home directory, open and parse their rules table,
X	    building the data structure as we go along.
X	    returns -1 if we hit an error of any sort...
X	**/
X
X	FILE *fd;				/* the file descriptor     */
X	char  buffer[SLEN], 			/* fd reading buffer       */
X	      *str, 				/* ptr to read string      */
X	      *word,				/* ptr to 'token'          */
X	      filename[SLEN], 			/* the name of the ruleset */
X	      action_argument[SLEN], 		/* action arg, per rule    */
X	      cond_argument[SLEN];		/* cond arg, per condition */
X	int   not_condition = FALSE, 		/* are we in a "not" ??    */
X	      type=NONE, 			/* what TYPE of condition? */
X	      lasttype, 			/* and the previous TYPE?  */
X	      state = NEXT_CONDITION,		/* the current state       */
X	      in_single, in_double, 		/* for handling spaces.    */
X	      i, 				/* misc integer for loops  */
X	      relop = NONE,			/* relational operator     */
X	      action, 				/* the current action type */
X	      line = 0;				/* line number we're on    */
X
X	struct condition_rec	*cond, *newcond;
X
X	sprintf(filename,"%s/%s", home, filterfile);
X
X	if ((fd = fopen(filename,"r")) == NULL) {
X	  fprintf(stderr,"filter (%s): Couldn't read user filter rules file!\n",
X		  username);
X	  return(-1);
X	}
X
X	cond_argument[0] = action_argument[0] = '\0';
X
X	/* Now, for each line... **/
X
X	if ((cond = (struct condition_rec *) 
X		     malloc(sizeof(struct condition_rec))) == NULL) {
X	  fprintf(stderr,"couldn't malloc first condition rec!\n");
X	  return(-1);
X	}
X	
X	rules[total_rules].condition = cond;	/* hooked in! */
X
X	while (fgets(buffer, SLEN, fd) != NULL) {
X	  line++;
X
X	  if (buffer[0] == '#' || strlen(buffer) < 2)
X	    continue;		/* nothing to look at! */
X
X	  in_single = in_double = 0;
X
X	  for (i=0; i < strlen(buffer); i++) {
X	    if (buffer[i] == '"') 
X	      in_double = ! in_double;
X	    else if (buffer[i] == '\'')
X	      in_single = ! in_single;
X	    if ((in_double || in_single) && buffer[i] == ' ')
X	      buffer[i] = '_';
X	  }
X
X	  lasttype = type;
X	  type = NONE;
X	  str = (char *) buffer;
X
X	  /** Three pieces to this loop - get the `field', the 'relop' (if
X	      there) then, if needed, get the argument to check against (not 
X	      needed for errors or the AND, of course)
X	  **/
X
X	  while ((word = strtok(str, " ()[]:\t\n")) != NULL) {
X
X	    str = (char *) NULL;		/* we can start stomping! */
X	  
X	    lowercase(word);
X
X	    if (strcmp(word, "if") == 0) {	/* only ONE 'if' allowed */
X	      if ((word = strtok(str, " ()[]:\t\n")) == NULL)	/* NEXT! */
X	        continue;
X	      lowercase(word);
X	    }
X	
X	    if (state == NEXT_CONDITION) {
X	      lasttype = type;
X	      type = NONE;
X
X	      if (the_same(word, "not") || the_same(word, "!")) {
X	        not_condition = TRUE;
X	        if ((word = strtok(str, " ()[]'\"\t\n")) == NULL)
X	          continue;
X	      }
X
X	           if (the_same(word, "from")) 	    type = FROM;
X	      else if (the_same(word, "to")) 	    type = TO;
X	      else if (the_same(word, "subject"))   type = SUBJECT;
X	      else if (the_same(word, "lines"))     type = LINES;
X	      else if (the_same(word, "contains"))  type = CONTAINS;
X	      else if (the_same(word, "and") || 
X	               the_same(word, "&&")) 	    type = AND;
X
X	      else if (the_same(word,"?") || the_same(word, "then")) {
X
X		/** shove THIS puppy into the structure and let's continue! **/
X
X	        if (lasttype == AND) {
X	          fprintf(stderr,
X       "%sfilter (%s): Error reading line %d of rules - badly placed \"and\"\n",
X		  BEEP, username, line);
X		  return(-1);
X	        }
X
X		cond->matchwhat = lasttype;
X	        if (relop == NONE) relop = EQ;	/* otherwise can't do -relop */
X	        cond->relation  = (not_condition? - (relop) : relop);
X
X		for (i=0;i<strlen(cond_argument);i++)
X	          if (cond_argument[i] == '_') cond_argument[i] = ' ';
X
X		strcpy(cond->argument1, cond_argument);
X	        if ((newcond = (struct condition_rec *)
X		     malloc(sizeof(struct condition_rec))) == NULL) {
X	          fprintf(stderr,"Couldn't malloc new cond rec!!\n");
X		  return(-1);
X	        }
X	        cond->next = NULL;
X
X	        relop = EQ;	/* default relational condition */
X
X	        state = READING_ACTION;
X	        if ((word = strtok(str, " ()[]'\"\t\n")) == NULL)
X	          continue;
X	        goto get_outta_loop;
X	      }
X
X	      if (type == NONE) {
X	        fprintf(stderr,
X      "%sfilter (%s): Error reading line %d of rules - field \"%s\" unknown!\n",
X		     BEEP, username, line, word);
X		return(-1);
X	      }
X
X	      if (type == AND) {
X
X		/** shove THIS puppy into the structure and let's continue! **/
X
X		cond->matchwhat = lasttype;
X	        cond->relation  = (not_condition? - (relop) : relop);
X		strcpy(cond->argument1, cond_argument);
X	        if ((newcond = (struct condition_rec *)
X	             malloc(sizeof(struct condition_rec))) == NULL) {
X	          fprintf(stderr,"Couldn't malloc new cond rec!!\n");
X		  return(-1);
X	        }
X	        cond->next = newcond;
X		cond = newcond;
X		cond->next = NULL;
X
X	        not_condition = FALSE;
X	        state = NEXT_CONDITION;
X	      }
X	      else {
X	        state = GETTING_OP;
X	      }
X	    }
X
Xget_outta_loop: 	/* jump out when we change state, if needed */
X
X	    if (state == GETTING_OP) {
X
X	       if ((word = strtok(str, " ()[]'\"\t\n")) == NULL)
X	         continue;
X
X	       lowercase(word);
X
X	       relop = NONE;
X
X	       if (the_same(word, "=") || the_same(word, "in") || 
X                   the_same(word, "contains")) {
X                 state = READING_ARGUMENT;
X	         relop = EQ;
X	       }
X	       else {
X	         if (the_same(word, "<=")) 	relop = LE;
X	         else if (the_same(word, ">="))	relop = GE;
X	         else if (the_same(word, ">"))	relop = GT;
X	         else if (the_same(word, "<>")||
X		          the_same(word, "!="))	relop = NE;
X	         else if (the_same(word, "<"))	relop = LT;
X
X		 /* maybe there isn't a relop at all!! */
X
X		 state=READING_ARGUMENT;
X
X	       }
X	    }
X		 
X	    if (state == READING_ARGUMENT) {
X	      if (relop != NONE) {
X	        if ((word = strtok(str, " ()[]'\"\t\n")) == NULL)
X	          continue;
X	      }
X	      for (i=0;i<strlen(word);i++)
X	        if (word[i] == '_') word[i] = ' ';
X
X	      strcpy(cond_argument, word);
X	      state = NEXT_CONDITION;
X	    }
X
X	    if (state == READING_ACTION) {
X	      action = NONE;
X
X	      not_condition = FALSE;
X
X	      if (the_same(word, "delete"))       action = DELETE;
X	      else if (the_same(word, "savec"))   action = SAVECC;
X	      else if (the_same(word, "save"))    action = SAVE;
X	      else if (the_same(word, "forward")) action = FORWARD;
X	      else if (the_same(word, "exec"))    action = EXEC;
X	      else if (the_same(word, "leave"))   action = LEAVE;
X	      else {
X	        fprintf(stderr,
X	"%sfilter (%s): Error on line %d of rules - action \"%s\" unknown\n",
X			BEEP, username, line, word);
X	      }
X
X	      if (action == DELETE || action == LEAVE) {
X	        /** add this to the rules section and alloc next... **/
X
X	        rules[total_rules].action = action;
X		rules[total_rules].argument2[0] = '\0';	/* nothing! */
X	        total_rules++;
X	         
X	        if ((cond = (struct condition_rec *)
X		     malloc(sizeof(struct condition_rec))) == NULL) {
X	          fprintf(stderr,"couldn't malloc first condition rec!\n");
X	          return(-1);
X	        }
X	
X	        rules[total_rules].condition = cond;	/* hooked in! */
X	        state = NEXT_CONDITION;	
X	      }
X	      else {
X	        state = ACTION_ARGUMENT;
X	      }
X
X	      if ((word = strtok(str, " ()[]'\"\t\n")) == NULL)
X	        continue;
X
X	    }
X	
X	    if (state == ACTION_ARGUMENT) {
X	      strcpy(action_argument, word);
X
X	      /** add this to the rules section and alloc next... **/
X
X	      rules[total_rules].action = action;
X	      expand_macros(action_argument, rules[total_rules].argument2,line);
X	      total_rules++;
X	         
X	      if ((cond = (struct condition_rec *)
X		     malloc(sizeof(struct condition_rec))) == NULL) {
X	        fprintf(stderr,"couldn't malloc first condition rec!\n");
X	        return(-1);
X	      }
X	
X	      rules[total_rules].condition = cond;	/* hooked in! */
X
X	      state = NEXT_CONDITION;
X	      if ((word = strtok(str, " ()[]'\"\t\n")) == NULL)
X	        continue;
X	    }
X	  }
X	}
X
X	return(0);
X}
END_OF_filter/parse.c
if test 8929 -ne `wc -c <filter/parse.c`; then
    echo shar: \"filter/parse.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"src/input_utils.c\" \(8598 characters\)
if test -f src/input_utils.c ; then 
  echo shar: Will not over-write existing file \"src/input_utils.c\"
else
sed "s/^X//" >src/input_utils.c <<'END_OF_src/input_utils.c'
X/**			input_utils.c			**/
X
X/** Mindless I/O routines for ELM 
X	
X    (C) Copyright 1985 Dave Taylor
X**/
X
X#include "headers.h"
X#include <errno.h>
X
Xextern int errno;		/* system error number */
X
Xunsigned alarm();
X
X#define special_char(c)		(c == ' ' || c == '\t' || c == '/' || c == ',' \
X				 || c == '\0')
X
X#define erase_a_char()		{ Writechar(BACKSPACE); Writechar(' '); \
X			          Writechar(BACKSPACE); fflush(stdout); }
X
Xint
Xwant_to(question, dflt, echo_answer)
Xchar *question, dflt;
Xint  echo_answer;
X{
X	/** Ask 'question' at LINES-2, COLUMNS-40, returning the answer in 
X	    lower case.  If 'echo_answer', then echo answer.  'dflt' is the 
X	    default answer if <return> is pressed. (Note: 'dflt' is also what 
X	    will be returned if <return> is pressed!)
X	**/
X	register char ch, cols;
X
X	cols = (strlen(question) < 30)? COLUMNS-40 : COLUMNS-50;
X
X	PutLine3(LINES-3, cols,"%s%c%c", question, dflt, BACKSPACE);
X	fflush(stdout);
X	fflush(stdin);
X	ch = tolower(ReadCh());
X
X	if (echo_answer && ch > (char) ' ') {
X	  Writechar(ch);
X	  fflush(stdout);
X	}
X
X	return(ch == '\n' || ch == '\r' ? dflt : ch);
X}
X
Xint
Xread_number(ch)
Xchar ch;
X{
X	/** Read a number, where 'ch' is the leading digit! **/
X	
X	char buff[SHORT_SLEN];
X	int  num;
X
X	buff[0] = ch;
X	buff[1] = '\0';
X
X	PutLine0(LINES-3, COLUMNS-40,"Set current message to :");
X	if (optionally_enter(buff, LINES-3, COLUMNS-15, TRUE) == -1)
X	  return(current);
X
X	sscanf(buff,"%d", &num);
X	return(num);
X}
X
Xint
Xoptionally_enter(string, x, y, append_current)
Xchar *string;
Xint  x,y, append_current;
X{
X	/** Display the string on the screen and if RETURN is pressed, return 
X	    it.  Otherwise, allow standard text input, including backspaces 
X	    and such until RETURN is hit.  
X	    If "append_current" is set, then leave the default string in 
X	    place and edit AFTER it...assume 'x,y' is placing us at the
X	    beginning of the string...
X	    This routine returns zero unless INTERRUPT hit, then it returns
X	    -1 and must be treated accordingly.
X	    Added ^W and ^R support...
X	    Also added that if x and y are < 0 don't try any cursor stuff
X	**/
X
X	char ch;
X	register int index = 0, use_cursor_control;
X
X	use_cursor_control = ((! mail_only) && x >= 0 && y >= 0);
X	
X	if (use_cursor_control)
X	  PutLine1(x,y, "%s", string);	
X	else
X	  printf("%s", string);	
X
X	CleartoEOLN();
X
X	if (! append_current) 
X	  if (use_cursor_control)
X	    MoveCursor(x,y);
X	  else
X	    non_destructive_back_up(strlen(string));
X
X	if (cursor_control)
X	  transmit_functions(OFF);
X
X	ch = getchar();
X
X	if (ch == '\n' || ch == '\r') {
X	  if (cursor_control)
X	    transmit_functions(ON);
X	  return(0);	/* we're done.  No change needed */
X	}
X	
X	CleartoEOLN();
X
X	index = (append_current? strlen(string) : 0);
X
X	if (ch == kill_line) {
X	  if (use_cursor_control)
X	    MoveCursor(x,y);
X	  else
X	    back_up(index);
X          CleartoEOLN();
X	  index = 0;
X	}
X	else if (ch != backspace) {
X	  Writechar(ch);
X	  string[index++] = ch;
X	}
X	else if (index > 0) {
X	  index--;
X	  erase_a_char();
X	}
X	else {
X	  Writechar(' ');
X	  Writechar(BACKSPACE);
X	  fflush(stdout);
X	}
X
X	do {
X	  ch = getchar();
X
X	  /* the following is converted from a case statement to
X	     allow the variable characters (backspace, kill_line
X	     and break) to be processed.  Case statements in
X	     C require constants as labels, so it failed ...
X	  */
X
X	    if (ch == backspace) {
X              if (index > 0) {
X	        erase_a_char();
X		index--;
X	      }
X	      else {
X		Writechar(' ');
X		Writechar(BACKSPACE);
X	        fflush(stdout);
X	      }
X	    }
X	    else if (ch == '\n' || ch == '\r') {
X	      string[index] = '\0';
X	      if (cursor_control)
X	        transmit_functions(ON);
X	      return(0);
X	    }
X	    else if (ch == ctrl('W')) {		/* back up a word! */
X	      if (special_char(string[index]) && index > 0) {
X		index--;
X	        erase_a_char();
X	      }
X	      while (index > 0 && ! special_char(string[index])) {
X		index--;
X	        erase_a_char();
X	      }
X	    }
X	    else if (ch == ctrl('R')) {
X	      string[index] = '\0';
X	      if (use_cursor_control) {
X	        PutLine1(x,y, "%s", string);	
X	        CleartoEOLN();
X	      }
X	      else
X	        printf("\n%s", string);	
X	    }
X	    else if (ch == kill_line) {
X	      if (use_cursor_control)
X	        MoveCursor(x,y);
X	      else
X	        back_up(index+1);
X              CleartoEOLN();
X	      index = 0;
X	    }
X	    else if (ch == NULL) {
X	      if (cursor_control)
X	        transmit_functions(ON);
X	      fflush(stdin); 	/* remove extraneous chars, if any */
X	      string[0] = '\0'; /* clean up string, and... */
X	      return(-1);
X	    }
X	    else {  /* default case */
X		        
X	      string[index++] = ch;
X	      Writechar(ch);
X	   }
X	} while (index < SLEN);
X
X	string[index] = '\0';
X
X	if (cursor_control)
X	  transmit_functions(ON);
X	return(0);
X}
X
Xint
Xpattern_enter(string, alt_string, x, y, alternate_prompt)
Xchar *string, *alt_string, *alternate_prompt;
Xint  x,y;
X{
X	/** This function is functionally similar to the routine
X	    optionally-enter, but if the first character pressed
X	    is a '/' character, then the alternate prompt and string
X	    are used rather than the normal one.  This routine 
X	    returns 1 if alternate was used, 0 if not
X	**/
X
X	char ch;
X	register index = 0;
X
X	PutLine1(x, y, "%s", string);	
X	CleartoEOLN();
X	MoveCursor(x,y);
X
X	if (cursor_control)
X	  transmit_functions(OFF);
X
X	ch = getchar();
X
X	if (ch == '\n' || ch == '\r') {
X	  if (cursor_control)
X	    transmit_functions(ON);
X	  return(0);	/* we're done.  No change needed */
X	}
X	
X	if (ch == '/') {
X	  PutLine1(x, 0, "%s", alternate_prompt);
X	  CleartoEOLN();
X	  (void) optionally_enter(alt_string, x, strlen(alternate_prompt)+1,
X		 FALSE);
X	  return(1);
X	}
X
X	CleartoEOLN();
X
X	index = 0;
X
X	if (ch == kill_line) {
X	  MoveCursor(x,y);
X          CleartoEOLN();
X	  index = 0;
X	}
X	else if (ch != backspace) {
X	  Writechar(ch);
X	  string[index++] = ch;
X	}
X	else if (index > 0) {
X	  index--;
X	  erase_a_char();
X	}
X	else {
X	  Writechar(' ');
X	  Writechar(BACKSPACE);
X	}
X
X	do {
X	  fflush(stdout);
X	  ch = getchar();
X
X	  /* the following is converted from a case statement to
X	     allow the variable characters (backspace, kill_line
X	     and break) to be processed.  Case statements in
X	     C require constants as labels, so it failed ...
X	  */
X
X	    if (ch == backspace) {
X              if (index > 0) {
X		index--;
X		erase_a_char();
X	      }
X	      else {
X		Writechar(' ');
X		Writechar(BACKSPACE);
X	      }
X	    }
X	    else if (ch == '\n' || ch == '\r') {
X	      string[index] = '\0';
X	      if (cursor_control)
X	        transmit_functions(ON);
X	      return(0);
X	    }
X	    else if (ch == ctrl('W')) {
X	      /* get to rightmost non-alpha */
X	      if (special_char (string[index]) && index > 0)
X		index--;
X	      while (index > 0 && ! special_char(string[index])) {
X		erase_a_char();
X		index--;
X	      }
X	    }
X	    else if (ch == ctrl('R')) {
X	      string[index] = '\0';
X	      if (!mail_only) {
X	        PutLine1(x,y, "%s", string);	
X	        CleartoEOLN();
X	      }
X	      else
X	        printf("\n%s", string);	
X	    }
X	    else if (ch == kill_line) {
X	      MoveCursor(x,y);
X              CleartoEOLN();
X	      index = 0;
X	    }
X	    else if (ch == NULL) {
X	      if (cursor_control)
X	        transmit_functions(ON);
X	      fflush(stdin); 	/* remove extraneous chars, if any */
X	      string[0] = '\0'; /* clean up string, and... */
X	      return(-1);
X	    }
X	    else {  /* default case */
X		        
X	      string[index++] = ch;
X	      Writechar(ch);
X	   }
X	} while (index < SLEN);
X
X	string[index] = '\0';
X
X	if (cursor_control)
X	  transmit_functions(ON);
X	return(0);
X}
X
Xback_up(spaces)
Xint spaces;
X{
X	/** this routine is to replace the goto x,y call for when sending
X	    mail without starting the entire "elm" system up... **/
X	
X	while (spaces--) {
X	  erase_a_char();
X	}
X}
X
Xnon_destructive_back_up(spaces)
Xint spaces;
X{
X	/** same as back_up() but doesn't ERASE the characters on the screen **/
X
X	while (spaces--)
X	  Writechar(BACKSPACE); 
X        fflush(stdout); 
X}
X
Xint
XGetPrompt()
X{
X	/** This routine does a read/timeout for a single character.
X	    The way that this is determined is that the routine to
X	    read a character is called, then the "errno" is checked
X	    against EINTR (interrupted call).  If they match, this
X	    returns NO_OP_COMMAND otherwise it returns the normal
X	    command.	
X	**/
X
X	int ch;
X
X	if (timeout > 0) {
X	  alarm((unsigned) timeout);
X	  errno = 0;	/* we actually have to do this.  *sigh*  */
X	  ch = ReadCh();
X	  if (errno == EINTR) ch = NO_OP_COMMAND;
X	  alarm((unsigned) 0);
X	}
X	else
X	  ch = ReadCh();
X
X	return(ch);
X}
END_OF_src/input_utils.c
if test 8598 -ne `wc -c <src/input_utils.c`; then
    echo shar: \"src/input_utils.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"src/mailmsg1.c\" \(8412 characters\)
if test -f src/mailmsg1.c ; then 
  echo shar: Will not over-write existing file \"src/mailmsg1.c\"
else
sed "s/^X//" >src/mailmsg1.c <<'END_OF_src/mailmsg1.c'
X/** 			mailmsg1.c			**/
X
X/** Interface to allow mail to be sent to users.  Part of ELM  **/
X
X/** (C) Copyright 1986, Dave Taylor 			       **/
X
X#include "headers.h"
X
X/** strings defined for the hdrconfg routines **/
X
Xchar subject[SLEN], action[SLEN], reply_to[SLEN], expires[SLEN], priority[SLEN];
Xchar to[VERY_LONG_STRING], cc[VERY_LONG_STRING], in_reply_to[SLEN];
Xchar user_defined_header[SLEN];
X
Xchar expanded_to[VERY_LONG_STRING], expanded_cc[VERY_LONG_STRING];
X
X#ifdef ALLOW_BCC
Xchar bcc[VERY_LONG_STRING], expanded_bcc[VERY_LONG_STRING];
X#endif
X
Xchar *format_long(), *strip_commas(), *tail_of_string(), *strcpy();
Xunsigned long sleep();
X
Xint
Xsend(given_to, given_subject, edit_message, form_letter)
Xchar *given_to, *given_subject;
Xint   edit_message, form_letter;
X{
X	/** Prompt for fields and then call mail() to send the specified
X	    message.  If 'edit_message' is true then don't allow the
X            message to be edited. 'form_letter' can be "YES" "NO" or "MAYBE".
X	    if YES, then add the header.  If MAYBE, then add the M)ake form
X	    option to the last question (see mailsg2.c) etc. etc. **/
X
X	int  copy_msg = FALSE, is_a_response = FALSE;
X
X	/* First: zero all current global message strings */
X
X	cc[0] = action[0] = reply_to[0] = expires[0] = priority[0] = '\0';
X#ifdef ALLOW_BCC
X	bcc[0] = expanded_bcc[0] = '\0';
X#endif
X	in_reply_to[0] = expanded_to[0] = expanded_cc[0] = '\0';
X
X	strcpy(subject, given_subject);		/* copy given subject */
X	strcpy(to, given_to);			/* copy given to:     */
X
X	/******* And now the real stuff! *******/
X
X	copy_msg=copy_the_msg(&is_a_response); /* copy msg into edit buffer? */
X
X	if (get_to(to, expanded_to) == 0)   /* get the To: address and expand */
X	  return(0);
X
X	/** are we by any chance just checking the addresses? **/
X
X	if (check_only) {
X	  printf("Expands to: %s\n", format_long(expanded_to, 12));
X	  putchar('\r');	/* don't ask... */
X	  leave();
X	}
X
X	/** if we're batchmailing, let's send it and GET OUTTA HERE! **/
X
X	if (mail_only && strlen(batch_subject) > 0) { 
X	  strcpy(subject, batch_subject);	/* get the batch subject */
X	  return(mail(FALSE, FALSE, TRUE, form_letter));
X	}
X
X	display_to(expanded_to);	/* display the To: field on screen... */
X
X	dprint1(3,"\nMailing to %s\n", expanded_to);
X  
X	if (get_subject(subject) == 0)	    /* get the Subject: field */
X	  return(0);
X
X	dprint1(4,"Subject is %s\n", subject);
X
X	if (get_copies(cc, expanded_to, expanded_cc) == 0)
X	  return(0);
X
X	if (strlen(cc) > 0)
X	  dprint1(4,"Copies to %s\n", expanded_cc);
X
X	if (mail_only) 				/* indicate next step... */
X	  printf("\n\r");
X	else
X	  MoveCursor(LINES,0);	/* you know you're hit <return> ! */
X
X	/** generate the In-Reply-To: header... **/
X
X	if (is_a_response)
X	  generate_reply_to(current-1);
X
X	/* and mail that puppy outta here! */
X
X	mail(copy_msg, edit_message, FALSE, form_letter);
X	
X	return(edit_message);
X}
X
Xget_to(to_field, address)
Xchar *to_field, *address;
X{
X	/** prompt for the "To:" field, expanding into address if possible.
X	    This routine returns ZERO if errored, or non-zero if okay **/
X
X	if (strlen(to_field) == 0) {
X	  if (user_level < 2) {
X	    PutLine0(LINES-2, 0, "Send the message to: ");
X	    (void) optionally_enter(to_field, LINES-2, 21, FALSE); 
X	  }
X	  else {
X	    PutLine0(LINES-2, 0, "To: ");
X	    (void) optionally_enter(to_field, LINES-2, 4, FALSE); 
X	  }
X	  if (strlen(to_field) == 0) {
X	    ClearLine(LINES-2);	
X	    return(0);
X	  }
X	  build_address(strip_commas(to_field), address); 
X	}
X	else if (mail_only) 
X	  build_address(strip_commas(to_field), address); 
X	else 
X	  strcpy(address, to_field);
X	
X	if (strlen(address) == 0) {	/* bad address!  Removed!! */
X	  if (! mail_only)
X	    ClearLine(LINES-2);
X	  return(0);
X	}
X
X	return(1);		/* everything is okay... */
X}
X
Xget_subject(subject_field)
Xchar *subject_field;
X{
X	/** get the subject and return non-zero if all okay... **/
X	int len = 9;
X
X	if (mail_only)
X	  printf("Subject: ");
X	else 
X	  if (user_level == 0) {
X	    PutLine0(LINES-2,0,"Subject of message: ");
X	    len = 21;
X	  }
X	  else
X	    PutLine0(LINES-2,0,"Subject: ");
X
X	CleartoEOLN();
X
X	if (optionally_enter(subject_field, LINES-2, len, TRUE) == -1) {
X	  /** User hit the BREAK key! **/
X	  MoveCursor(LINES-2,0); 	
X	  CleartoEOLN();
X	  error("mail not sent");
X	  return(0);
X	}
X
X	if (strlen(subject_field) == 0) {	/* zero length subject?? */
X	  if (mail_only) 
X	    printf("\n\rNo subject - Continue with message? (y/n) n%c",
X		  BACKSPACE);
X	  else
X	    PutLine1(LINES-2,0,"No subject - Continue with message? (y/n) n%c",
X		  BACKSPACE);
X
X	  if (tolower(ReadCh()) != 'y') {	/* user says no! */
X	    if (mail_only) {
X	      printf("\n\r\n\rMail Cancelled!\n\r");
X	      return(0);
X	    }
X	    ClearLine(LINES-2);
X	    error("mail not sent");
X	    return(0);
X	  }
X	  else if (! mail_only) {
X	    PutLine0(LINES-2,0,"Subject: <none>");
X	    CleartoEOLN();
X	  }
X	}
X
X	return(1);		/** everything is cruising along okay **/
X}
X
Xget_copies(cc_field, address, addressII)
Xchar *cc_field, *address, *addressII;
X{
X	/** Get the list of people that should be cc'd, returning ZERO if
X	    any problems arise.  Address and AddressII are for expanding
X	    the aliases out after entry! 
X	    If 'bounceback' is nonzero, add a cc to ourselves via the remote
X	    site, but only if hops to machine are > bounceback threshold.
X	**/
X
X	if (mail_only)
X	  printf("\n\rCopies To: ");
X	else
X	  PutLine0(LINES-1,0,"Copies To: ");
X
X	fflush(stdout);
X
X	if (optionally_enter(cc_field, LINES-1, 11, FALSE) == -1) {
X	  if (mail_only) {
X	    printf("\n\r\n\rMail not sent!\n\r");
X	    return(0);
X	  }
X	  ClearLine(LINES-2);
X	  ClearLine(LINES-1);
X	  
X	  error("mail not sent");
X	  return(0);
X	}
X	
X	build_address(strip_commas(cc_field), addressII);
X
X	if (strlen(address) + strlen(addressII) > VERY_LONG_STRING) {
X	  dprint0(2,
X		"String length of \"To:\" + \"Cc\" too long! (get_copies)\n");
X	  error("Too many people.  Copies ignored");
X	  sleep(2);
X	  cc_field[0] = '\0';
X	}
X
X	return(1);		/* everything looks okay! */
X}
X	
Xint
Xcopy_the_msg(is_a_response)
Xint *is_a_response;
X{
X	/** Returns True iff the user wants to copy the message being
X	    replied to into the edit buffer before invoking the editor! 
X	    Sets "is_a_response" to true if message is a response...
X	**/
X
X	int answer = FALSE;
X
X	if (strlen(to) > 0 && !mail_only) {	/* predefined 'to' line! */
X	  if (auto_copy) 
X	    answer = TRUE;
X	  else 
X	    answer = (want_to("Copy message? (y/n) ", 'n', TRUE) == 'y');
X	  *is_a_response = TRUE;
X	}
X	else
X	  if (strlen(subject) > 0)  	/* predefined 'subject' (Forward) */
X	    answer = TRUE;
X
X	return(answer);
X}
X
Xdisplay_to(address)
Xchar *address;
X{
X	/** Simple routine to display the "To:" line according to the
X	    current configuration (etc) 			      
X	 **/
X	register int open_paren;
X
X	if (mail_only)
X	  printf("To: %s\n\r", format_long(address, 3));
X	else {
X	  if (names_only)
X	    if ((open_paren = chloc(address, '(')) > 0) {
X	      if (open_paren < chloc(address, ')')) {
X	        output_abbreviated_to(address);
X	        return;
X	      } 
X	     }
X	  if (strlen(address) > 45) 
X	    PutLine1(LINES-3, COLUMNS-50, "To: (%s)", 
X		tail_of_string(address, 40));
X	  else {
X	    if (strlen(address) > 30) 
X	      PutLine1(LINES-3, COLUMNS-50, "To: %s", address);
X	    else
X	      PutLine1(LINES-3, COLUMNS-50, "          To: %s", address);
X	    CleartoEOLN();
X	  }
X	}
X}
X
Xoutput_abbreviated_to(address)
Xchar *address;
X{
X	/** Output just the fields in parens, separated by commas if need
X	    be, and up to COLUMNS-50 characters...This is only used if the
X	    user is at level BEGINNER.
X	**/
X
X	char newaddress[LONG_STRING];
X	register int index, newindex = 0, in_paren = 0;
X
X	index = 0;
X
X	while (newindex < 55 && index < strlen(address)) {
X	  if (address[index] == '(') in_paren++;
X	  else if (address[index] == ')') { 
X	    in_paren--;
X	    if (index < strlen(address)-4) {
X	      newaddress[newindex++] = ',';
X	      newaddress[newindex++] = ' ';
X	    }
X	  }
X	  
X	  if (in_paren && address[index] != '(')
X	      newaddress[newindex++] = address[index];
X	     
X	  index++;
X	}
X
X	newaddress[newindex] = '\0';
X
X	if (strlen(newaddress) > 50) 
X	   PutLine1(LINES-3, COLUMNS-50, "To: (%s)", 
X	       tail_of_string(newaddress, 40));
X	 else {
X	   if (strlen(newaddress) > 30)
X	     PutLine1(LINES-3, COLUMNS-50, "To: %s", newaddress);
X	   else
X	     PutLine1(LINES-3, COLUMNS-50, "          To: %s", newaddress);
X	   CleartoEOLN();
X	 }
X
X	return;
X}
END_OF_src/mailmsg1.c
if test 8412 -ne `wc -c <src/mailmsg1.c`; then
    echo shar: \"src/mailmsg1.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"src/options.c\" \(9046 characters\)
if test -f src/options.c ; then 
  echo shar: Will not over-write existing file \"src/options.c\"
else
sed "s/^X//" >src/options.c <<'END_OF_src/options.c'
X/**			options.c			**/
X
X/** This set of routines allows the alteration of a number of paramaters
X    in the Elm mailer, including the following;
X
X	calendar-file	<where to put calendar entries>
X	display pager	<how to page messages>
X	editor		<name of composition editor>
X	folder-dir	<folder directory>
X	sort-by		<how to sort mailboxes>
X	savefile	<file to save outbound message copies to>
X	printmail	<how to print messages>
X	full_username	<your full user name for outgoing mail>
X
X	arrow-cursor	<on or off>
X	menu-display    <on or off>
X
X	user-level	<BEGINNER|INTERMEDIATE|EXPERT>
X        names-only      <on or off>
X	
X    And others as they seem useful.
X
X    (C) Copyright 1986, Dave Taylor
X**/
X
X#include "headers.h"
X
X#undef onoff
X#define   onoff(n)	(n == 1? "ON ":"OFF")
X
Xchar *one_liner_for(), *level_name();
Xunsigned long sleep();
X
Xoptions()
X{
X	/** change options... **/
X
X	char ch;
X
X	display_options();
X
X	do {
X	  ClearLine(LINES-4);
X
X	  Centerline(LINES-4,
X	   "Select first letter of Option line, '>' to Save, or R)eturn");
X
X	  PutLine0(LINES-2, 0, "Command: ");
X
X	  ch = tolower(ReadCh());
X
X	  clear_error();	/* remove possible "sorting" message etc... */ 
X
X	  one_liner(one_liner_for(ch));
X
X	  switch (ch) {
X	    case 'c' : optionally_enter(calendar_file, 2, 23, FALSE);	break;
X	    case 'd' : optionally_enter(pager, 3, 23, FALSE); 		break;
X	    case 'e' : optionally_enter(editor, 4, 23, FALSE);		break;
X	    case 'f' : optionally_enter(folders, 5, 23, FALSE);		break;
X	    case 's' : change_sort(6,23);				break;
X	    case 'o' : optionally_enter(savefile, 7, 23, FALSE);	break;
X	    case 'p' : optionally_enter(printout, 8, 23, FALSE);	break;
X	    case 'y' : optionally_enter(full_username, 9, 23, FALSE);	break;
X	    case 'a' : on_or_off(&arrow_cursor, 12, 23); 		break;
X	    case 'm' : on_or_off(&mini_menu, 13, 23);			
X		       headers_per_page = LINES - (mini_menu ? 13 : 8); break;
X
X	    case 'u' : switch_user_level(&user_level,15, 23);		break;
X	    case 'n' : on_or_off(&names_only, 16, 23);			break;
X	
X	    case '?' : options_help(); 
X	               PutLine0(LINES-2,0,"Command: ");			break;
X	   
X	    case '>' : printf("Save options in .elmrc...");
X		       fflush(stdout);    save_options();		break;
X
X	    case 'x' :
X	    case 'r' :
X	    case ctrl('M'):
X	    case ctrl('J'): return;
X	    case ctrl('L'): display_options();				break;
X	    default: error("Command unknown!");
X	  }
X
X	} while (ch != 'r');
X}
X	
Xdisplay_options()
X{
X	/** Display all the available options.. **/
X	
X	char *sort_name();
X	
X	ClearScreen();
X	Centerline(0,"-- Elm Options Editor --");
X
X#ifdef ENABLE_CALENDAR
X	PutLine1(2, 0, "C)alendar file       : %s", calendar_file);
X#endif
X	PutLine1(3, 0, "D)isplay mail using  : %s", pager);
X	PutLine1(4, 0, "E)ditor              : %s", editor);
X	PutLine1(5, 0, "F)older directory    : %s", folders);
X	PutLine1(6, 0, "S)orting criteria    : %s", sort_name(FULL));
X	PutLine1(7, 0, "O)utbound mail saved : %s", savefile);
X	PutLine1(8, 0, "P)rint mail using    : %s", printout);
X	PutLine1(9, 0, "Y)our full name      : %s", full_username);
X
X	PutLine1(12,0, "A)rrow cursor        : %s", onoff(arrow_cursor));
X	PutLine1(13,0, "M)enu display        : %s", onoff(mini_menu));
X
X	PutLine1(15,0, "U)ser level          : %s", level_name(user_level));
X	PutLine1(16,0, "N)ames only          : %s", onoff(names_only));
X}
X
Xon_or_off(var, x, y)
Xint *var, x,y;
X{
X	/** 'var' field at x.y toggles between on and off... **/
X
X	char ch;
X
X     	PutLine0(x, y+6, 
X		"(use <space> to toggle, any other key to leave)");
X
X	MoveCursor(x,y+3);	/* at end of value... */
X
X	do {
X	  ch = ReadCh();
X
X	  if (ch == SPACE) {
X	    *var = ! *var;
X	    PutLine0(x,y, onoff(*var));
X	  }
X	} while (ch == SPACE);
X
X	MoveCursor(x,y+4); 	CleartoEOLN();	/* remove help prompt */
X}
X
X
Xswitch_user_level(ulevel, x, y)
Xint *ulevel, x, y;
X{
X	/** step through possible user levels... **/
X
X     	PutLine0(x, y+20, "<space> to change");
X
X	MoveCursor(x,y);	/* at end of value... */
X
X	while (ReadCh() == ' ') {
X	  *ulevel = (*ulevel == 2? 0 : *ulevel + 1);
X	  PutLine1(x,y, "%s", level_name(*ulevel));
X	}
X
X	MoveCursor(x,y+20); 	CleartoEOLN();	/* remove help prompt */
X}
X	
Xchange_sort(x, y)
Xint x,y;
X{
X	/** change the sorting scheme... **/
X	
X	int last_sortby,	/* so we know if it changes... */
X	    sign = 1;		/* are we reverse sorting??    */
X	char ch;		/* character typed in ...      */
X
X	last_sortby = sortby;	/* remember current ordering   */
X
X	PutLine0(x, COLUMNS-29, "(SPACE for next, or R)everse)");
X	sort_one_liner(sortby);
X	MoveCursor(x, y);
X
X	do {
X	  ch = tolower(ReadCh());
X	  switch (ch) {
X	    case SPACE : if (sortby < 0) { 
X	    		   sign = -1; 
X	    		   sortby = - sortby; 
X	  		 }
X			 else sign = 1;		/* insurance! */
X	  		 sortby = sign * ((sortby + 1) % (STATUS+1));
X			 if (sortby == 0) sortby = sign;  /* snicker */
X	  		 PutLine0(x, y, sort_name(PAD));
X			 sort_one_liner(sortby);
X	  		 MoveCursor(x, y);
X			 break;
X
X	    case 'r'   : sortby = - sortby;
X	  		 PutLine0(x, y, sort_name(PAD));
X			 sort_one_liner(sortby);
X	  		 MoveCursor(x, y);
X	 }
X        } while (ch == SPACE || ch == 'r');
X
X	MoveCursor(x, COLUMNS-30);	CleartoEOLN();
X
X	if (sortby != last_sortby) {
X	  error("resorting mailbox...");
X	  sleep(1);
X	  sort_mailbox(message_count, 0);
X	}
X	ClearLine(LINES-2);		/* clear sort_one_liner()! */
X}
X
Xone_liner(string)
Xchar *string;
X{
X	/** A single-line description of the selected item... **/
X
X	ClearLine(LINES-4);
X	Centerline(LINES-4, string);
X}
X
Xsort_one_liner(sorting_by)
Xint sorting_by;
X{
X	/** A one line summary of the particular sorting scheme... **/
X
X	ClearLine(LINES-2);
X
X	switch (sorting_by) {
X	  
X	  case -SENT_DATE : Centerline(LINES-2,
X"This sort will order most-recently-sent to least-recently-sent");	break;
X	  case -RECEIVED_DATE : Centerline(LINES-2,
X"This sort will order most-recently-received to least-recently-received");
X		 	    break;
X	  case -SENDER : Centerline(LINES-2,
X"This sort will order by sender name, in reverse alphabetical order");	break;
X	  case -SIZE   : Centerline(LINES-2,
X"This sort will order messages by longest to shortest");		break;
X	  case -SUBJECT : Centerline(LINES-2,
X"This sort will order by subject, in reverse alphabetical order");	break;
X	  case -STATUS  : Centerline(LINES-2,
X"This sort will order by reverse status - Deleted through Tagged...");	break;
X
X	  case SENT_DATE : Centerline(LINES-2,
X"This sort will order least-recently-sent to most-recently-sent");	break;
X	  case RECEIVED_DATE : Centerline(LINES-2,
X"This sort will order least-recently-received to most-recently-received");
X	                    break;
X	  case SENDER : Centerline(LINES-2,
X		        "This sort will order by sender name");	break;
X	  case SIZE   : Centerline(LINES-2,
X		        "This sort will order messages by shortest to longest");
X			break;
X	  case SUBJECT : Centerline(LINES-2,
X            		"This sort will order messages by subject");	break;
X	  case STATUS  : Centerline(LINES-2,
X"This sort will order by status - Tagged through Deleted...");		break;
X	}
X}
X
Xchar *one_liner_for(c)
Xchar c;
X{
X	/** returns the one-line description of the command char... **/
X
X	switch (c) {
X	    case 'c' : return(
X"This is the file where calendar entries from messages are saved.");
X
X	    case 'd' : return(
X"This is the program invoked to display individual messages (try 'builtin')");
X
X	    case 'e' : return(
X"This is the editor that will be used for sending messages, etc.");
X
X	    case 'f' : return(
X"This is the folders directory used when '=' (etc) is used in filenames");
X
X	    case 'm' : return(
X"This determines if you have the mini-menu displayed or not");
X
X	    case 'n' : return(
X"Whether to display the names and addresses on mail, or names only");
X	    case 'o' : return(
X"This is where copies of outbound messages are saved automatically.");
X
X	    case 'p' : return(
X"This is how printouts are generated.  \"%s\" will be replaced by the filename.");
X
X	    case 's' : return(
X"This is used to specify the sorting criteria for the mailboxes");
X
X	    case 'y' : return(
X"When mail is sent out, this is what your full name will be recorded as.");
X
X	    case 'a' : return(
X"This defines whether the ELM cursor is an arrow or a highlight bar.");
X
X	   case 'u' : return(
X"The level of knowledge you have about the Elm mail system.");
X
X	    default : return("");	/* nothing if we don't know! */
X	}
X}
X
Xoptions_help()
X{
X	/** help menu for the options screen... **/
X
X	char c, *ptr;
X
X	Centerline(LINES-3,
X     "Enter the key you want help on, '?' for a list, or '.' to exit help");
X
X	lower_prompt("Key : ");
X
X	while ((c = tolower(ReadCh())) != '.') {
X	  if (c == '?') {
X	     display_helpfile(OPTIONS_HELP);
X	     display_options();
X	     return;
X	  }
X	  if ((ptr = one_liner_for(c)) != NULL)
X	    error2("%c = %s", c, ptr);
X	  else
X	    error1("%c isn't used in this section", c);
X	  lower_prompt("Key : ");
X	}
X}
X
Xchar *level_name(n)
Xint n;
X{
X	/** return the 'name' of the level... **/
X
X	switch (n) {
X	  case 0 : return("Beginning User   ");
X	  case 1 : return("Intermediate User");
X	  default: return("Expert User      ");
X	}
X}
END_OF_src/options.c
if test 9046 -ne `wc -c <src/options.c`; then
    echo shar: \"src/options.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"utils/answer.c\" \(8992 characters\)
if test -f utils/answer.c ; then 
  echo shar: Will not over-write existing file \"utils/answer.c\"
else
sed "s/^X//" >utils/answer.c <<'END_OF_utils/answer.c'
X/**			answer.c			**/
X
X/** This program is a phone message transcription system, and
X    is designed for secretaries and the like, to allow them to
X    painlessly generate electronic mail instead of paper forms.
X
X    Note: this program ONLY uses the local alias file, and does not
X	  even read in the system alias file at all.
X
X    (C) Copyright 1986, Dave Taylor
X
X**/
X
X#include <stdio.h>
X#include <fcntl.h>
X#include <ctype.h>
X
X#include "defs.h"			/* ELM system definitions      */
X
X#define  ELM		"elm"		/* where the elm program lives */
X
X#define  answer_temp_file	"/tmp/answer."
X
Xstatic char ident[] = { WHAT_STRING };
X
Xstruct alias_rec user_hash_table  [MAX_UALIASES];
X
Xint user_data;		/* fileno of user data file   */
X
Xchar *expand_group(), *get_alias_address(), *get_token(), *strip_parens();
X
Xmain()
X{
X	FILE *fd;
X	char *address, buffer[LONG_STRING], tempfile[SLEN];
X	char  name[SLEN], user_name[SLEN];
X	int   msgnum = 0, eof;
X	
X	read_alias_files();
X
X	while (1) {
X	  if (msgnum > 9999) msgnum = 0;
X	
X	  printf("\n-------------------------------------------------------------------------------\n");
X
Xprompt:   printf("\nMessage to: ");
X	  gets(user_name, SLEN);
X	  if (user_name == NULL)
X	    goto prompt;
X	  
X	  if ((strcmp(user_name,"quit") == 0) ||
X	      (strcmp(user_name,"exit") == 0) ||
X	      (strcmp(user_name,"done") == 0) ||
X	      (strcmp(user_name,"bye")  == 0))
X	     exit(0);
X
X	  if (translate(user_name, name) == 0)
X	    goto prompt;
X
X	  address = get_alias_address(name, 1, 0);
X
X	  printf("address '%s'\n", address);
X
X	  if (address == NULL || strlen(address) == 0) {
X	    printf("Sorry, could not find '%s' [%s] in list!\n", user_name, 
X		   name);
X	    goto prompt;
X	  }
X
X	  sprintf(tempfile, "%s%d", answer_temp_file, msgnum++);
X
X	  if ((fd = fopen(tempfile,"w")) == NULL)
X	    exit(printf("** Fatal Error: could not open %s to write\n",
X		 tempfile));
X
X
X	  printf("\nEnter message for %s ending with a blank line.\n\n", 
X		 user_name);
X
X	  fprintf(fd,"\n\n");
X
X	  do {
X	   printf("> ");
X	   if (! (eof = (gets(buffer, SLEN) == NULL))) 
X	     fprintf(fd, "%s\n", buffer);
X	  } while (! eof && strlen(buffer) > 0);
X	
X	  fclose(fd);
X 
X	  sprintf(buffer, 
X	     "((%s -s \"While You Were Out\" %s ; %s %s) & ) < %s > /dev/null",
X	     ELM, strip_parens(address), remove, tempfile, tempfile);
X
X	  system(buffer);
X	}
X}
X
Xint
Xtranslate(fullname, name)
Xchar *fullname, *name;
X{
X	/** translate fullname into name..
X	       'first last'  translated to first_initial - underline - last
X	       'initial last' translated to initial - underline - last
X	    Return 0 if error.
X	**/
X	register int i, lastname = 0;
X
X	for (i=0; i < strlen(fullname); i++) {
X
X	  if (isupper(fullname[i]))
X	     fullname[i] = tolower(fullname[i]);
X
X	  if (fullname[i] == ' ') 
X	    if (lastname) {
X	      printf(
X	      "** Can't have more than 'FirstName LastName' as address!\n");
X	      return(0);
X	    }
X	    else
X	      lastname = i+1;
X	
X	}
X
X	if (lastname) 
X	  sprintf(name, "%c_%s", fullname[0], (char *) fullname + lastname);
X	else
X	  strcpy(name, fullname);
X
X	return(1);
X}
X
X	    
Xread_alias_files()
X{
X	/** read the user alias file **/
X
X	char fname[SLEN];
X	int  hash;
X
X	sprintf(fname,  "%s/.alias_hash", getenv("HOME")); 
X
X	if ((hash = open(fname, O_RDONLY)) == -1) 
X	  exit(printf("** Fatal Error: Could not open %s!\n", fname));
X
X	read(hash, user_hash_table, sizeof user_hash_table);
X	close(hash);
X
X	sprintf(fname,  "%s/.alias_data", getenv("HOME")); 
X
X	if ((user_data = open(fname, O_RDONLY)) == -1) 
X	  return;
X}
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.  Returns NULL if not found.
X	    Depth is the nesting depth, and varies according to the
X	    nesting level of the routine.  **/
X
X	static char buffer[VERY_LONG_STRING];
X	int    loc;
X
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, LONG_STRING);
X	  if (buffer[0] == '!' && mailing)
X	    return( (char *) expand_group(buffer, depth));
X	  else
X	    return( (char *) buffer);
X	}
X	
X	return( (char *) NULL);
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 the current recursion
X	    depth of the expansion (for the 'get_token' routine) **/
X
X	char   buffer[VERY_LONG_STRING];
X	char   buf[LONG_STRING], *word, *address, *bufptr;
X
X	strcpy(buf, members); 	/* parameter safety! */
X	buffer[0] = '\0';	/* nothing in yet!   */
X	bufptr = (char *) buf;	/* grab the address  */
X	depth++;		/* one more deeply into stack */
X
X	while ((word = (char *) get_token(bufptr, "!, ", depth)) != NULL) {
X	  if ((address = (char *) get_alias_address(word, 1, depth)) == NULL) {
X	    fprintf(stderr, "Alias %s not found for group expansion!", word);
X	    return( (char *) NULL);
X	  }
X	  else if (strcmp(buffer,address) != 0) {
X	    sprintf(buffer,"%s %s", buffer, address);
X	  }
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	  exit(printf("Bad alias name: %s.  Too long.\n", word));
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}
X
Xprint_long(buffer, init_len)
Xchar *buffer;
Xint   init_len;
X{
X	/** print buffer out, 80 characters (or less) per line, for
X	    as many lines as needed.  If 'init_len' is specified, 
X	    it is the length that the first line can be.
X	**/
X
X	register int i, loc=0, space, length; 
X
X	/* In general, go to 80 characters beyond current character
X	   being processed, and then work backwards until space found! */
X
X	length = init_len;
X
X	do {
X	  if (strlen(buffer) > loc + length) {
X	    space = loc + length;
X	    while (buffer[space] != ' ' && space > loc + 50) space--;
X	    for (i=loc;i <= space;i++)
X	      putchar(buffer[i]);
X	    putchar('\n');
X	    loc = space;
X	  }
X	  else {
X	    for (i=loc;i < strlen(buffer);i++)
X	      putchar(buffer[i]);
X	    putchar('\n');
X	    loc = strlen(buffer);
X	  }
X	  length = 80;
X	} while (loc < strlen(buffer));
X}
X
X/****
X     The following is a newly chopped version of the 'strtok' routine
X  that can work in a recursive way (up to 20 levels of recursion) by
X  changing the character buffer to an array of character buffers....
X****/
X
X#define MAX_RECURSION		20		/* up to 20 deep recursion */
X
X#undef  NULL
X#define NULL			(char *) 0	/* for this routine only   */
X
Xextern int strspn();
Xextern char *strpbrk();
X
Xchar *get_token(string, sepset, depth)
Xchar *string, *sepset;
Xint  depth;
X{
X
X	/** string is the string pointer to break up, sepstr are the
X	    list of characters that can break the line up and depth
X	    is the current nesting/recursion depth of the call **/
X
X	register char	*p, *q, *r;
X	static char	*savept[MAX_RECURSION];
X
X	/** is there space on the recursion stack? **/
X
X	if (depth >= MAX_RECURSION) {
X	 fprintf(stderr,"Error: Get_token calls nested greated than %d deep!\n",
X			MAX_RECURSION);
X	 exit(1);
X	}
X
X	/* set up the pointer for the first or subsequent call */
X	p = (string == NULL)? savept[depth]: string;
X
X	if(p == 0)		/* return if no tokens remaining */
X		return(NULL);
X
X	q = p + strspn(p, sepset);	/* skip leading separators */
X
X	if (*q == '\0')		/* return if no tokens remaining */
X		return(NULL);
X
X	if ((r = strpbrk(q, sepset)) == NULL)	/* move past token */
X		savept[depth] = 0;	/* indicate this is last token */
X	else {
X		*r = '\0';
X		savept[depth] = ++r;
X	}
X	return(q);
X}
X
Xchar *strip_parens(string)
Xchar *string;
X{
X	/** Return string with all parenthesized information removed.
X	    This is a non-destructive algorithm... **/
X
X	static char  buffer[LONG_STRING];
X	register int i, depth = 0, buffer_index = 0;
X
X	for (i=0; i < strlen(string); i++) {
X	  if (string[i] == '(')
X	    depth++;
X	  else if (string[i] == ')') 
X	    depth--;
X	  else if (depth == 0)
X	    buffer[buffer_index++] = string[i];
X	}
X	
X	buffer[buffer_index] = '\0';
X
X	return( (char *) buffer);
X}
END_OF_utils/answer.c
if test 8992 -ne `wc -c <utils/answer.c`; then
    echo shar: \"utils/answer.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: End of archive 8 \(of 19\).
cp /dev/null ark8isdone
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