[mod.sources] v09i014: ELM Mail System, Part14/19

sources-request@mirror.UUCP (03/12/87)

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

#! /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 14 (of 19)."
# Contents:  doc/Filter.guide src/newmbox.c src/reply.c
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo shar: Extracting \"doc/Filter.guide\" \(15096 characters\)
if test -f doc/Filter.guide ; then 
  echo shar: Will not over-write existing file \"doc/Filter.guide\"
else
sed "s/^X//" >doc/Filter.guide <<'END_OF_doc/Filter.guide'
X.PH ""
X\"
X\"  A guide to the Elm Filter program
X\"  format with 'tbl Filter.guide | troff -mm > Filter.format'
X\"  or something similar.
X\"  (C) Copyright 1986 Dave Taylor
X\"
X\"  Last modification: January 19th, 1987
X\"
X.SA 1
X.nr Hy 1
X.nr Pt 1
X.nr Pi 8
X.lg
X.HM 1 1
X.rs
X.ds HF 3  3  
X.ds HP 12 12 10 10 10
X.PF ""
X.ce 99
X.sp 9
X.ps 20
X\fBElm Filter Guide\fR
X.sp 7
X.ps 12
X\fIWhat the filter program is, what it does,
Xand how to use it\fR
X.sp 5
XDave Taylor
X.sp
XHewlett-Packard Laboratories
X1501 Page Mill Road
XPalo Alto CA
X94304
X.sp 
Xemail: taylor@hplabs.HP.COM or hplabs!taylor
X.sp 10
X.ps 18
X\fB\(co\fR\s12 Copyright 1986, 1987 by Dave Taylor
X.ps 10
X.SK
X.sp 5
X.ps 14
X\fBElm Filter Guide\fR
X.PH "'Filter Guide''version 1.5'
X.PF "''Page \\\\nP''"
X.nr P 1
X.sp
X.ps 10
X(version 1.5)
X.sp 2
XDave Taylor
X.sp
XHewlett-Packard Laboratories
X1501 Page Mill Road
XPalo Alto CA
X94304
X.sp 
Xemail: taylor@hplabs.HP.COM or hplabs!taylor
X.sp 2
X\*(DT
X.ce 0
X.sp 3
X.P
XOne of the greatest problems with the burgeoning electronic mail
Xexplosion is that we tend to get mail that we don't care about.
XAmusingly, perhaps, we have the equivalent of electronic junk mail.
XNot amusing, however, is the fact that this can rapidly 
Xaccumulate and end up taking over your mailbox!
X.P
XAt the same time we often get mail that, while it is interesting
Xand important, can easily be filed to be read later, without ever
Xactually cluttering up the mailbox.
X.sp 2
XThis, then, is what \fIfilter\fR does for you!  The \fIfilter\fR program
Xallows you to define a set of rules by which all incoming mail should
Xbe screened, and a subsequent set of actions to perform based on whether
Xthe rules were met or not.  \fIFilter\fR also has the ability to mail
Xa summary of what actions it performed on the incoming mail as often as
Xyou'd like.
X.sp
X.ps 12
X\fB
XWriting the Rules\fR
X.ps 10
X.sp
XThe language for writing \fIfilter\fR rules is pretty simple, actually.
XThe fundamental structure is;
X.nf
X
X	if  (\fIcondition\fR)  then  \fIaction\fR
X
X.fi
XWhere \fIcondition\fR is constructed by an arbitrary number of 
Xindividual conditions of the form ``\fIfield\fR  \fIrelation\fR  \fIvalue\fR''.
XThe \fIfield\fR value can be;
X.nf
X
X	subject
X	from
X	to
X	lines
X	contains
X
X.fi
Xwhere, if ``lines'' is choosen, the \fIrelation\fR can be any of the
Xstandard relationships (`>', `<', `>=', `<=', `!=' and `=').  
XIf another action is
Xchoosen, ``contains'' can be used as the relation, ``='', or, if you'd
Xlike, you can skip the relationship entirely (e.g. `subject "joe"').
XThe \fIvalue\fR is any quoted string that is to be matched against
Xor number if ``lines'' is the field being considered.
X.sp
XInvidivual conditions are joined together by using the word ``and'',
Xand the logic of a condition can be flipped by using ``not'' as the
Xfirst word (e.g. `not subject "joe"').  We'll see more examples of
Xthis later.
X.sp
XNote that the ``or'' logical conjunction isn't a valid part of the
X\fIfilter\fR conditional statement.  
X.sp
XFinally, <\fIaction\fR> can be any of;
X.nf
X
X	delete
X
X	save   \fIfoldername\fR
X
X	savecopy  \fIfoldername\fR
X
X	forward  \fIaddress\fR
X
X	execute  \fIcommand\fR
X
X	leave
X
X.fi
Xwhere they result in the actions;  \fBdelete\fR deletes the message;
X\fBsave\fR saves a copy of the message in the specified foldername;
X\fBsavecopy\fR does the same as save, but also puts a copy in your mailbox;
X\fBforward\fR sends the message to the specified address; 
X\fBexecute\fR feeds the message to the specified command (or complex
Xsequence of commands) as standard input;
Xand \fBleave\fR leaves the message in your mailbox.
X.sp
XFoldernames can contain any of a number of macros, too, as we'll see in
Xthe example ruleset below.  The macros available for the string fields are;
X.DS CB
X.TS
Xl l.
X     Macro	   Meaning
X
X      %d	   day of the month
X      %D	   day of the week (0-6)
X      %h	   hour of the day (0-23)
X      %m	   month of the year (0-11)
X      %r	   return address of message
X      %s	   subject of original message
X      %S	   ``Re: \fIsubject of original message\fR''
X      %t	   current hour and minute in HH:MM format
X      %y	   year (last two digits)
X.TE
X.DE
X.sp
XThe rules file can also contain comments (any line starting with a `#')
Xand blank lines.  
X.sp 
XThe file itself needs to reside in your home directory and be 
Xcalled \fI.filter-rules\fR.  Here's an example;
X.nf
X
X    #  $HOME/.filter-rules
X    #
X    #  Filter  rules  for  the  Elm  Filter  program.  Don't  change  without  some
X    #  serious  thought.  (remember  -  order  counts)
X    #
X    #     Dave  Taylor
X   
X    #  rule  1
X    if  (from  contains  "!uucp")  then  delete
X
X    #  rule  2
X    to   "postmaster"   ?   save  "/tmp/postmaster-mail.%d"
X
X    #  rule  3
X    if  (to  "culture"  and  lines  >  20)  ?   save  "/users/taylor/Mail/culture"
X
X    #  rule  4
X    subject  =  "filter test"   ?   forward  "hpldat!taylor"
X
X    #  rule  5
X    if  [  subject  =  "elm"  ]   savecopy  "/users/taylor/Mail/elm-incoming"
X
X    #  rule  6
X    subject  =  "display-to-console" ?  execute "cat - > /dev/console"
X
X.fi
X(notice the loose syntax - there are lots of valid ways to specify a
Xrule in the \fIfilter\fR program!!)
X.sp
XTo translate these into English;
X.sp
X.AL
X.LI
XAll messages from uucp should be summarily deleted.
X.LI
XAll mail to postmaster should be saved in a folder (file) called 
X/tmp/posmaster-mail.\fInumeric-day-of-the-week\fR
X.LI
XAll mail addressed to `culture' with at least 20 lines
X should be automatically appended to the folder
X/users/taylor/Mail/culture.
X.LI
XAll messages that contain the subject `filter test' should be forwarded to
Xme, but via the address `hpldat!taylor' (to force a non-user forward)
X.LI
XAll messages with a subject that contains the word `elm' should be saved in
Xthe folder ``/users/taylor/Mail/elm-incoming'' and also dropped into my
Xmailbox.
X.LI
XAny message with the subject ``display-to-console'' will be immediately
Xwritten to the console.
X.LE
X.sp
XNotice that the \fIorder\fR of the rules is very important.  If we, for 
Xexample, were to get a message from `uucp' that had the subject `filter test',
Xthe \fIfilter\fR program would match rule 1 and delete the message.  It 
Xwould never be forwarded to `hpldat!taylor'.  It is for this reason that
Xgreat care should be taken with the ordering of the rules.
X.sp
X.ps 12
X\fBChecking the rules out\fR
X.ps 10
X.sp
XThe \fIfilter\fR program has a convenient way of check out the rules you 
Xhave written.  Simply invoke it with the \fB-r\fR (\fBr\fRules) flag;
X.nf
X
X	% \fBfilter -r\fR
X
X	Rule 1: if (from = "!uucp") then
X	          Delete 
X
X	Rule 2: if (to = "postmaster") then
X	          Save  /tmp/postmaster-mail.2
X
X	Rule 3: if (to = "culture" and lines > 20) then
X	          Save  /users/taylor/Mail/culture
X
X	Rule 4: if (subject = "filter test") then
X	        Forward  hpldat!taylor
X
X	Rule 5: if (subject="elm") then
X	          Copy  and  Save  /users/taylor/Mail/elm-incoming
X
X	Rule 6: if (subject="display-to-console") then
X		  Execute "cat - > /dev/console"
X
X.fi
XThere are a few things to notice - first off, these are the parsed and
Xrebuilt rules, so we can see that they are all in a 
Xconsistent format.  Also, notice on the filename for rule 2 that the
Xprogram has correctly expanded the ``%d'' macro to be the day of the 
Xmonth.
X.sp 2
XIt is \fBhighly\fR recommended that you always check your ruleset before
Xactually letting the program use it!
X.sp
X.ps 12
X\fBActually Using the Program\fR
X.ps 10
X.sp
XNow the bad news.  If you aren't running \fIsendmail\fR you cannot use
Xthis program as currently written.  Why?  Because the \fIfilter\fR
Xprogram expects to be put in your \fI.forward\fR file and that is something
Xthat only \fIsendmail\fR looks at!
X.sp
XThe format for the entry in the \fI.forward\fR file (located in your
Xhome directory) is simply;
X.nf
X
X	"|filter"
X
X.fi
XAlright, it isn't quite \fIthat\fR simple!  Since \fIfilter\fR will be invoked
Xby processes that don't know where you are logged in, you need to have some
Xway to trap the error messages.  For ease of use, it was decided to have all
Xthe messages written to \fIstderr\fR which means that you have two main
Xchoices for the actual entry.  Either;
X.nf
X
X	"|filter  >  /dev/console  2>&1"
X
X.fi
Xwhich will log all errors on the system console (each error is prefixed with
X``filter (\fIusername\fR)'' to distinguish it), or;
X.nf
X
X	"|filter  >>  $HOME/.filter_errors  2>&1"
X
X.fi
XIf you want to have a copy saved to a file.  A possible strategy would be
Xto have the errors written to a file and to then have a few lines in 
Xyour \fI.login\fR script like;
X.nf
X
X        if  (  -f  .filter_errors)  then
X           echo  "\ \ "
X           echo  "Filter  program  errors;"
X           cat  .filter_errors
X           echo  "\ \ "
X        endif
X
X.fi
XYou can also use the \fB-v\fR flag in combination with the above to have
Xthe errors written to a file and a single line indicating messages being
Xsent off or saved to folders written to the console by having 
Xyour \fI.forward\fR file;
X.nf
X
X	"|filter  -v  > /dev/console  2>>  $HOME/.filter_errors"
X
X.fi
XSuffice to say, you can get pretty tricky with all this!!  One last point - if
Xyou're interested in having it beep (for output to the screen, I would
Xthink) you can use the \fB-a\fR (\fBa\fRudible) flag for any of these 
Xinvocations!
X.sp
X.ne 5
X.ps 12
X\fBSummarizing the Actions Taken\fR
X.ps 10
X.sp
XThe \fIFilter\fR program keeps a log of all actions performed, including
Xwhat rules it matched against, in your home directory in a file 
Xcalled \fI.filter_log\fR.  You can either directly operate on this file,
Xor, much more recommended, you can one of the two summarize flags to
Xthe program and let \fIit\fR do the work for you!
X.sp
XThe difference between the two is best demonstrated by example;
X.nf
X
X	% \fBfilter -s\fR
X
X	Summary of filter activity;
X
X	The default rule of putting mail into your mailbox was used 18 times
X
X	Rule #3: (save in "/users/taylor/Mail/culture") was applied 2 times.
X
Xvs
X
X	% \fBfilter -S\fR
X
X	Mail from root about 
X		PUT in mailbox: the default action
X
X	Mail from taylor about Filter Summary
X		PUT in mailbox: the default action
X
X	Mail from hpcea!hpcesea!hpcesed!scott@hplabs.HP.COM
X	   about Comments and questions about elm
X		PUT in mailbox: the default action
X
X	[etc etc]
X
X.fi
XTo actually use either of the summarizing options, there 
Xare two ways that are recommended;
X.sp
XThe preferred way is to have a line in either your \fIcrontab\fR
X(ask your administrator for help with this) that invokes the \fIfilter\fR
Xprogram as often as you desire with the \fB-s\fR flag.  For example, I
Xhave a summary mailed to me every morning at 8:00 am;
X.nf
X
X    0   8   *   *   *   "/usr/local/bin/filter  -s  |  elm  -s  'Filter  Summary'  taylor"
X
X.fi
X.sp
XAn alternative is to have your \fI.login\fR execute the command each time.
X.sp 2
XNote that if you want to have your log files cleared out each time the 
Xsummary is generated you'll need to use the '-c' flag too.  Also,
Xif you want to keep a long list of actions performed you can do this
Xby saving it as you display it.  A way to do this would be, if you were to
Xhave the invocation in your \fI.login\fR script, to use;
X.nf
X
X	echo  "Filter  Log;"
X	filter  -c  -s  |  tee  -a  PERM.filter.log
X
X.fi
Xwhich would append a copy of all the output to the file `PERM.filter.log'
Xand would avoid you having to read larger and larger summaries of
Xwhat the program had done.
X.sp
X.ne 5
X.ps 12
X\fBFurther Testing of the Ruleset\fR
X.ps 10
X.sp
XWith the \fIreadmsg\fR command available, it is quite easy to test the
Xrules you've written to see if they'll do what you desire.  
X.sp
XFor example, we can use the \fB-n\fR flag to \fIfilter\fR, which means
X`don't actually do this, just tell me what rule you matched, if any, and
Xwhat action you would have performed' (you can see why a single letter 
Xflag is easier to type in!!), and feed it each message in our mailbox 
Xby using a command like;
X.nf
X
X	% \fBset message=1\fR
X	% \fBset total_messages=`messages`\fR
X
X	% \fBwhile  (1)\fR
X	> \fBif ($message > $total_messages) exit\fR
X	> \fBecho processing message $message\fR
X	> \fBreadmsg -h $message | filter -n\fR
X	> \fBecho " "\fR
X	> \fB@ messages++\fR
X	> \fBend\fR
X
X.fi
Xwhich will then hand each of the messages in your mailbox to the \fIfilter\fR
Xprogram and display what action would have been taken with that message and
Xwhy.
X.sp
XFor example, if we do this for a few interesting messages in my mailbox,
Xwe'd end up with output like;
X.nf
X
X       Mail from taylor about filter test
X	     FORWARDED to hpldat!taylor by rule;
X	        subject="filter test"  ? forward "hpldat!taylor"
X
X       Mail from bradley%hplkab@hplabsc about Re:  AI-ED mailing address for HP
X	     PUT in mailbox: the default action
X
X       Mail from taylor about display-to-console
X	     EXECUTED "cat - > /dev/console"
X
X.fi
X(sharp users will notice that this is exactly the same format as the longer
Xsummary listing!!)
X.sp 
X.ps 12
X\fBWhat Forwarded Messages Look Like\fR
X.ps 10
X.sp
XWhen a message is forwarded to another user by the \fIaction\fR being specified
Xas ``forward \fIaddress\fR'', then the program can generate one of two styles
Xof message.  If the message is to you, then it'll simply add it to your mailbox
Xin such a way as to ensure that the return address is that of the person who
Xsent the message and so on.
X.sp
XIf not, then the message is enclosed in a message of the form;
X.nf
X
X   From taylor Thu Oct  2 15:07:04 1986
X   Date: Thu, 2 Oct 86 15:06:58 pdt
X   Subject: "filter test"
X   From: The filter of taylor@hpldat <taylor>
X   To: hpldat!taylor
X   X-Filtered-By: filter, version 1.4
X
X   -- Begin filtered message --
X   
X       From taylor Thu Oct  2 15:06:41 1986
X       Date: Thu, 2 Oct 86 15:06:33 pdt
X       From: Dave Taylor <taylor>
X       Subject: filter test
X 
X       Just a simple test.
X 
X   -- End of filtered message --
X
X.fi
XThe subject of the actual message is the same as the subject of the 
Xmessage being forwarded, but in quotes.  The `From:'  field indicates
Xhow the message was sent, and the `X-Filtered-By:' identifies what
Xversion of filter is being used.
X.sp
X.ps 12
X\fBAreas to Improve\fR
X.ps 10
X.sp
XWhile the \fIfilter\fR program as presented herein is obviously a
Xnice addition to the set of tools available for dealing with electronic
Xmail, there are some key features that are missing and will be added in
Xthe future based on demand.
X.sp
XAs I see it, the main things missing are;
X.AL
X.LI
XThe ability to use regular expressions in the patterns.  
XThis would be a \fIvery\fR nice feature!
X.LI
XPerhaps more \fIactions\fR available (but what?)
X.LI
XCertainly the ability to filter based on any field or combination of
Xfields.  
X.LE
X.sp 2
X.ps 12
X\fBWarnings and Things to Look Out For\fR
X.ps 10
X.sp
XSince this is a pretty simple program, there are a few pitfalls, some
Xof which have already been mentioned;
X.sp
X\fBOrder\fR counts in the rules.  Beware!
X.sp
X\fBMatching\fR is pretty simple - make sure your patterns are sufficiently
Xexclusive before having any destructive rules.
X.sp 2
XFinally, as with the rest of the \fBElm\fR mail system, I welcome feedback
Xand suggestion on how to improve this program!!
END_OF_doc/Filter.guide
if test 15096 -ne `wc -c <doc/Filter.guide`; then
    echo shar: \"doc/Filter.guide\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"src/newmbox.c\" \(14549 characters\)
if test -f src/newmbox.c ; then 
  echo shar: Will not over-write existing file \"src/newmbox.c\"
else
sed "s/^X//" >src/newmbox.c <<'END_OF_src/newmbox.c'
X/**			newmbox.c			**/
X
X/**  read new mailbox file, (C) Copyright 1986 by Dave Taylor  **/
X
X#include <ctype.h>
X
X#ifdef BSD
X#undef tolower		/* we have our own "tolower" routine instead! */
X#endif
X
X#include "headers.h"
X
X#include <sys/types.h>		
X#include <sys/stat.h>
X#include <errno.h>
X
X#ifdef BSD			/* Berkeley has library elsewhere... */
X#  ifndef BSD4.1
X#    include <sys/time.h>
X#  else
X#    include <time.h>
X#  endif
X#else
X#  include <time.h>
X#endif
X
Xextern int errno;
X
Xchar *error_name(), *error_description(), *strcpy(), *strncpy();
Xunsigned long sleep();
Xvoid rewind();
Xvoid  exit();
X
Xstruct header_rec *realloc();
X
Xint
Xnewmbox(stat, resync, main_screen)
Xint stat, resync, main_screen;
X{
X	/** Read a new mailbox file or resync on current file.
X
X	    Values of stat and what they mean;
X
X		stat = 0	- changing mailboxes from within program
X		stat = 1	- read default mailbox or infile for the 
X			          first time
X	        stat = 2	- read existing mailbox, new mail arrived
X
X	    resync is TRUE iff we know the current mailbox has changed.  If
X	    it's set to true this means that we MUST READ SOMETHING, even 
X	    if it's the current mailbox again!!
X
X	    main_screen simply tells where the counting line should be.
X
X	**/
X
X	int  switching_to_default = 0, switching_from_default = 0;
X	int  iterations = 0, redraw = 0; /* for dealing with the '?' answer */
X	char buff[SLEN];
X
X	if (mbox_specified == 0 && stat == 0)
X	  switching_from_default++;
X
X	if (stat > 0) {
X	  if (stat == 1 && strlen(infile) == 0) {
X
X	    /* Subtlety - check to see if there's another instantiation
X	       of Elm (e.g. if the /tmp file is in use).  If so, DIE! */
X
X	    sprintf(infile, "%s%s", temp_mbox, username);
X	    if (access(infile, ACCESS_EXISTS) != -1) {
X	      error(
X	    "Hey!  An instantiation of Elm is already reading this mail!\n\r");
X	      fprintf(stderr,
X"\n\r     [if this is in error then you'll need to remove '/tmp/mbox.%s']\n\r", 
X		  username);
X              exit(1);
X            }
X            sprintf(infile, "%s%s", mailhome, username);
X	  }
X	  if (strlen(infile) == 0)	/* no filename yet?? */
X	    sprintf(infile,"%s%s",mailhome, username);
X	  if ((errno = can_access(infile, READ_ACCESS))) {
X	    if (strncmp(infile, mailhome, strlen(mailhome)) == 0) {
X	      /* oh wow.  incoming mailbox with no messages... */
X	      return(1);
X	    }
X	    error2("Can't open mailbox '%s' for reading [%s]", infile,
X		    error_name(errno));
X	    exit(1);
X	  }
X	}
X	else { 		 	/* get name of new mailbox! */
X	  MoveCursor(LINES-3, 30);
X	  CleartoEOS();
X	  PutLine0(LINES-3, 40, "(Use '?' to list your folders)");
X	  show_last_error();
Xask_again:
X	  buff[0] = '\0';
X	  if (iterations++ == 0) {
X	    PutLine0(LINES-2,0,"Name of new mailbox: ");
X	    (void) optionally_enter(buff, LINES-2, 21, FALSE);
X	    ClearLine(LINES-2);
X	  }
X	  else {
X	    printf("\n\rName of new mailbox: ");
X	    (void) optionally_enter(buff, -1, -1, FALSE);
X	  }
X	  if (strlen(buff) == 0) {
X	    if (resync && file_changed)
X	      strcpy(buff, infile);
X	    else
X	      return(redraw);
X	  }
X	  if (strcmp(buff, "?") == 0) {
X	    redraw = 1;		/* we'll need to clean the screen */
X	    dprint0(1,"***  setting redraw to 1 ***\n");
X	    list_folders();
X	    goto ask_again;
X	  }
X	  if (strcmp(buff, "!") == 0 ||
X		   strcmp(buff, "%") == 0) 	/* go to mailbox */
X	    sprintf(buff,"%s%s", mailhome, username);
X	  else if (! expand_filename(buff)) {
X	    error1("can't expand file %s", buff);
X	    if (resync && file_changed)
X	      strcpy(buff, infile);
X	    else
X	      return(FALSE);	
X	  }
X
X	  if (strcmp(buff, infile) == 0 && ! resync) { 
X	    dprint0(3,"User requested change to current mailbox! (newmbox)\n");
X	    error("already reading that mailbox!");
X	    return(FALSE);
X	  }
X
X	  if ((errno = can_access(buff, READ_ACCESS))) {
X	    dprint2(2,"Error: attempt to open %s as mailbox denied (%s)!\n",
X		     buff, "newmbox");
X	    error1("Permission to open file %s denied", buff);
X	    if (resync && file_changed)
X	      strcpy(buff, infile);
X	    else
X	      return(FALSE);	
X	  }
X
X	  if (first_word(buff, mailhome)) {	/* a mail file! */
X	    mbox_specified = 0; 	  /* fake program to think that */
X	    stat = 1;		    	  /*     we're the default file */
X	    switching_to_default++;	  /*        remember this act!  */
X	  }
X
X	  if (resync && file_changed && strcmp(buff, infile) == 0)
X	    PutLine0(LINES-3,COLUMNS-40,"Resynchronizing file");
X	  else
X	    PutLine1(LINES-3,COLUMNS-40,"Mailbox: %s", buff);
X	  CleartoEOLN();
X	  strcpy(infile,buff);
X	  if (! switching_to_default) mbox_specified = 1;
X	
X	}
X
X	if (switching_from_default) {	/* we need to remove the tmp file */
X	    sprintf(buff, "%s%s", temp_mbox, username);
X	    if (unlink(buff) != 0) {
X	      error1(
X	    "Sorry, but I can't seem to unlink your temp mail file [%s]\n\r",
X		error_name(errno));
X              silently_exit();
X	    }
X	}
X
X	clear_error();
X	clear_central_message();
X
X	header_page = 0;
X
X	if (mailfile != NULL)
X	  (void) fclose(mailfile);  /* close it first, to avoid too many open */
X
X	if ((mailfile = fopen(infile,"r")) == NULL) 
X	  message_count = 0;
X	else if (stat < 2) {          /* new mail file! */
X	  current = 1;
X	  message_count = read_headers(FALSE, main_screen);
X	  if (! message_count) current = 0;
X	}
X	else 	/* resync with current mail file */
X	  message_count = read_headers(TRUE, main_screen);
X
X	if (stat < 2)
X	  selected = 0;	/* we don't preselect new mailboxes, boss! */
X
X	return(TRUE);
X}
X
Xint
Xread_headers(rereading, main_screen)
Xint rereading, main_screen;
X{
X	/** Reads the headers into the header_table structure and leaves
X	    the file rewound for further I/O requests.   If the file being
X	    read is the default mailbox (ie incoming) then it is copied to
X	    a temp file and closed, to allow more mail to arrive during 
X	    the elm session.  If 'rereading' is set, the program will copy
X	    the status flags from the previous data structure to the new 
X	    one if possible.  This is (obviously) for re-reading a mailfile!
X	**/
X
X	FILE *temp;
X	struct header_rec *temp_struct;
X	char buffer[LONG_STRING], temp_filename[SLEN];
X	long bytes = 0L, line_bytes = 0L;
X	register int line = 0, count = 0, subj = 0, copyit = 0, in_header = 1;
X	int count_x, count_y = 17, new_messages = 0, err;
X	int in_to_list = FALSE, forwarding_mail = FALSE;
X
X	static int first_read = 0;
X
X	if (! first_read++) {
X	  ClearLine(LINES-1);
X	  ClearLine(LINES);
X	  if (rereading)
X	    PutLine2(LINES, 0, "Reading in %s, message: %d", infile, 
X		     message_count);
X	  else
X	    PutLine1(LINES, 0, "Reading in %s, message: 0", infile);
X	  count_x = LINES;
X          count_y = 22 + strlen(infile);
X	}
X	else {
X	  count_x = LINES-2;
X	  if (main_screen)
X	    PutLine0(LINES-2, 0, "Reading message: 0");
X	  else {
X	    PutLine0(LINES, 0, "\n");
X	    PutLine0(LINES, 0, "Reading message: 0");
X	    count_x = LINES;
X	  }
X	}
X
X	if (mbox_specified == 0) {
X	  lock(INCOMING);	/* ensure no mail arrives while we do this! */
X	  sprintf(temp_filename,"%s%s",temp_mbox, username);
X	  if (! rereading) {
X	    if (access(temp_filename, ACCESS_EXISTS) != -1) {
X	      /* Hey!  What the hell is this?  The temp file already exists? */
X	      /* Looks like a potential clash of processes on the same file! */
X	      unlock();				     /* so remove lock file! */
X	      error("What's this?  The temp mailbox already exists??");
X	      sleep(2);
X	      error("Ahhhh.....I give up");
X	      silently_exit();	/* leave without tampering with it! */
X	    }
X	    if ((temp = fopen(temp_filename,"w")) == NULL) {
X	     err = errno;
X	     unlock();	/* remove lock file! */
X	     Raw(OFF);
X	     Write_to_screen(
X		     "\nCouldn't open file %s for use as temp mailbox;\n", 1,
X	             temp_filename);
X	     Write_to_screen("** %s - %s **\n", 2,
X		     error_name(err), error_description(err));
X	     dprint3(1,
X                "Error: Couldn't open file %s as temp mbox.  errno %s (%s)\n",
X	         temp_filename, error_name(err), "read_headers");
X	     leave();
X	    }
X	   get_mailtime();
X	   copyit++;
X	   chown(temp_filename, userid, groupid);
X	   chmod(temp_filename, 0700);	/* shut off file for other people! */
X	 }
X	 else {
X	   if ((temp = fopen(temp_filename,"a")) == NULL) {
X	     err = errno;
X	     unlock();	/* remove lock file! */
X	     Raw(OFF);
X	     Write_to_screen(
X		     "\nCouldn't reopen file %s for use as temp mailbox;\n", 1,
X	             temp_filename);
X	     Write_to_screen("** %s - %s **\n", 2,
X		     error_name(err), error_description(err));
X	     dprint3(1,
X                "Error: Couldn't reopen file %s as temp mbox.  errno %s (%s)\n",
X	         temp_filename, error_name(err), "read_headers");
X	     emergency_exit();
X	    }
X	   copyit++;
X	  }
X	}
X
X	if (rereading) {
X	   if (fseek(mailfile, mailfile_size, 0)) {
X	     err = errno;
X	     Write_to_screen(
X		"\nCouldn't seek to %ld (end of mailbox) in %s!\n", 2,
X	     	mailfile_size, infile);	
X	     Write_to_screen("** %s - %s **\n", 2,
X		     error_name(err), error_description(err));
X	     dprint4(1,
X     "Error: Couldn't seek to end of mailbox %s: (offset %ld) Errno %s (%s)\n",
X	        infile, mailfile_size, error_name(err), "read_headers");
X	     emergency_exit();
X	   }
X	   count = message_count;		/* next available  */
X	   bytes = mailfile_size;		/* start correctly */
X	   if (message_count > 0)
X	     line = header_table[message_count - 1].lines;
X	   else
X	     line = 0;
X	}
X
X	while (fgets(buffer, LONG_STRING, mailfile) != NULL) {
X
X	  if (bytes == 0L) { 	/* first line of file... */	
X	    if (! mbox_specified) {
X	      if (first_word(buffer, "Forward to ")) {
X	        set_central_message("Mail being forwarded to %s", 
X                   (char *) (buffer + 11));
X	        forwarding_mail = TRUE;
X	      }
X	    }
X	    if (! first_word(buffer, "From ") && !forwarding_mail) {
X	        PutLine0(LINES, 0, 
X		  "\n\rMail file is corrupt!!  I can't read it!!\n\r\n\r");
X		fflush(stderr);
X		dprint0(1, "\n\n**** First mail header is corrupt!! ****\n\n");
X		dprint1(1, "Line is;\n\t%s\n\n", buffer);
X	        mail_only++;	/* to avoid leave() cursor motion */
X	        leave();
X	    }
X	  }
X
X	  if (copyit) fputs(buffer, temp);
X	  line_bytes = (long) strlen(buffer); 
X	  line++;
X	  if (first_word(buffer,"From ")) {
X	
X	    /** try to allocate new headers, if needed... **/
X
X	    if (count >= max_headers) {
X	      max_headers += KLICK;
X	      dprint2(3,
X		  "\n\nAbout to allocate headers, count = %d, max_headers=%d\n",
X		  count, max_headers);
X	      if ((temp_struct = realloc(header_table, max_headers * 
X		   sizeof(struct header_rec))) == NULL) {
X	        error1(
X      "\n\r\n\rCouldn't allocate enough memory!  Failed on message #%d\n\r\n\r",
X			count);
X	        leave();
X	       }
X	       header_table = temp_struct;
X	       dprint1(7,"\tallocated %d more headers!\n\n", KLICK);
X	     }
X	      
X	    if (real_from(buffer, &header_table[count])) {
X	      header_table[count].offset = (long) bytes;
X	      header_table[count].index_number = count+1;
X	      if (! rereading || count > message_count) 
X	        header_table[count].status = VISIBLE;     /* default status! */
X	      strcpy(header_table[count].subject, "");	/* clear subj    */
X	      header_table[count-1].lines = line;
X	      if (new_msg(header_table[count])) {
X	        header_table[count].status |= NEW;	/* new message!  */
X
X	        if (! new_messages++ && point_to_new && ! rereading &&
X	            sortby == RECEIVED_DATE) {
X		  current = count+1;
X	          get_page(current);	/* make sure we're ON that page! */
X	        }
X
X		/* Quick comment on that last conditional test...
X
X		   We want to move the current pointer to the first new
X		   message IF this is the first of the new messages, the
X		   user requested this feature, we're not rereading the 
X		   mailbox (imagine how THAT could screw the user up!),
X		   and we're not in some funky sorting mode (received-date is
X		   the default).  As always, I'm open to suggestions on
X		   other ways to have this work intelligently.
X		*/
X	
X	      }
X	      count++;
X	      subj = 0;
X	      line = 0;
X	      in_header = 1;
X	      PutLine1(count_x, count_y, "%d", count);
X	    }
X	  }
X	  else if (in_header) {
X	    if (first_word(buffer,">From")) 
X	      forwarded(buffer, &header_table[count-1]); /* return address */
X	    else if (first_word(buffer,"Subject:") ||
X		     first_word(buffer,"Subj:") ||
X		     first_word(buffer,"Re:")) {
X	      if (! subj++) {
X	        remove_first_word(buffer);
X	        strncpy(header_table[count-1].subject, buffer, STRING);
X	      }
X	    }
X	    else if (first_word(buffer,"From:"))
X	      parse_arpa_from(buffer, header_table[count-1].from);
X	    
X	    /** when it was sent... **/
X
X	    else if (first_word(buffer, "Date:")) 
X	      parse_arpa_date(buffer, &header_table[count-1]);
X
X	    /** some status things about the message... **/
X
X	    else if (first_word(buffer, "Priority:"))
X	      header_table[count-1].status |= PRIORITY;
X	    else if (first_word(buffer, "Content-Type: mailform"))
X	      header_table[count-1].status |= FORM_LETTER;
X	    else if (first_word(buffer, "Action:"))
X	      header_table[count-1].status |= ACTION;
X
X	    /** next let's see if it's to us or not... **/
X
X	    else if (first_word(buffer, "To:")) {
X	      in_to_list = TRUE;
X	      header_table[count-1].to[0] = '\0';	/* nothing yet */
X	      figure_out_addressee((char *) buffer +3, 
X				   header_table[count-1].to);
X	    }
X
X	    else if (buffer[0] == LINE_FEED || buffer[0] == '\0') {
X	      if (in_header) {
X	        in_header = 0;	/* in body of message! */
X	        fix_date(&header_table[count-1]);
X	      }
X	    }
X	    else if (in_to_list == TRUE) {
X	      if (whitespace(buffer[0]))
X	        figure_out_addressee(buffer, header_table[count-1].to);
X	      else in_to_list = FALSE;
X	    }
X	  }
X	  bytes += (long) line_bytes;
X	}
X
X	header_table[count > 0? count-1:count].lines = line + 1;
X	
X	if (mbox_specified == 0) {
X	  unlock();	/* remove lock file! */
X	  fclose(mailfile);
X	  fclose(temp);
X	  if ((mailfile = fopen(temp_filename,"r")) == NULL) {
X	    err = errno;
X	    MoveCursor(LINES,0);
X	    Raw(OFF);
X	    Write_to_screen("\nAugh! Couldn't reopen %s as temp mail file;\n",
X	           1, temp_filename);
X	    Write_to_screen("** %s - %s **\n", 2, error_name(err),
X		   error_description(err));
X	    dprint3(1,
X          "Error: Reopening %s as temp mail file failed!  errno %s (%s)\n",
X	           temp_filename, error_name(errno), "read_headers");
X	    leave();
X	  }
X	}
X	else 
X          rewind(mailfile);
X
X	sort_mailbox(count, 1);	 		/* let's sort this baby now! */
X
X	return(count);
X}
END_OF_src/newmbox.c
if test 14549 -ne `wc -c <src/newmbox.c`; then
    echo shar: \"src/newmbox.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: Extracting \"src/reply.c\" \(14009 characters\)
if test -f src/reply.c ; then 
  echo shar: Will not over-write existing file \"src/reply.c\"
else
sed "s/^X//" >src/reply.c <<'END_OF_src/reply.c'
X/**		reply.c		**/
X
X/*** routine allows replying to the sender of the current message 
X
X     (C) Copyright 1985, Dave Taylor
X***/
X
X#include "headers.h"
X#include <errno.h>
X
X#ifndef BSD
X#  include <sys/types.h>
X#  include <sys/utsname.h>
X#endif
X
X/** Note that this routine generates automatic header information
X    for the subject and (obviously) to lines, but that these can
X    be altered while in the editor composing the reply message! 
X**/
X
Xchar *strip_parens(), *get_token();
X
Xextern int errno;
X
Xchar *error_name(), *strcat(), *strcpy();
X
Xint
Xreply()
X{
X	/** Reply to the current message.  Returns non-zero iff
X	    the screen has to be rewritten. **/
X
X	char return_address[LONG_SLEN], subject[SLEN];
X	int  return_value, form_letter;
X
X	form_letter = (header_table[current-1].status & FORM_LETTER);
X
X	get_return(return_address);
X
X	if (first_word(header_table[current-1].from, "To:")) {
X	  strcpy(subject, header_table[current-1].subject);
X	  if (form_letter)
X	    return_value = mail_filled_in_form(return_address, subject);
X	  else
X	    return_value = send(return_address, subject, TRUE, NO);
X	}
X	else if (header_table[current-1].subject[0] != '\0') {
X	  if ((strncmp("Re:", header_table[current-1].subject, 3) == 0) ||
X	      (strncmp("RE:", header_table[current-1].subject, 3) == 0) ||
X	      (strncmp("re:", header_table[current-1].subject, 3) == 0)) 
X	    strcpy(subject, header_table[current-1].subject);
X	  else {
X	    strcpy(subject,"Re: ");
X	    strcat(subject,header_table[current-1].subject); 
X	  }
X	  if (form_letter)
X	    return_value = mail_filled_in_form(return_address, subject);
X	  else
X	    return_value = send(return_address, subject, TRUE, NO);
X	}
X	else
X	  if (form_letter)
X	    return_value = mail_filled_in_form(return_address, 
X						"Filled in Form");
X	  else
X	    return_value = send(return_address, "Re: your mail", TRUE, NO);
X
X	return(return_value);
X}
X
Xint
Xreply_to_everyone()
X{
X	/** Reply to everyone who received the current message.  
X	    This includes other people in the 'To:' line and people
X	    in the 'Cc:' line too.  Returns non-zero iff the screen 
X            has to be rewritten. **/
X
X	char return_address[LONG_SLEN], subject[SLEN];
X	char full_address[VERY_LONG_STRING];
X	int  return_value;
X
X	get_return(return_address);
X
X	strcpy(full_address, return_address);	/* sender gets copy */
X	
X	get_and_expand_everyone(return_address, full_address);
X
X	if (header_table[current-1].subject[0] != '\0') {
X	  if ((strncmp("Re:", header_table[current-1].subject, 3) == 0) ||
X	      (strncmp("RE:", header_table[current-1].subject, 3) == 0) ||
X	      (strncmp("re:", header_table[current-1].subject, 3) == 0)) 
X	    strcpy(subject, header_table[current-1].subject);
X	  else {
X	    strcpy(subject,"Re: ");
X	    strcat(subject,header_table[current-1].subject); 
X	  }
X	  return_value = send(full_address, subject, TRUE, NO);
X	}
X	else
X	  return_value = send(full_address, "Re: your mail", TRUE, NO);
X
X	return(return_value);
X
X}
X
Xint
Xforward()
X{
X	/** Forward the current message.  What this actually does is
X	    to set auto_copy to true, then call 'send' to get the 
X	    address and route the mail. 
X	**/
X
X	char subject[SLEN], address[VERY_LONG_STRING];
X	int  original_cc, results, edit_msg = FALSE;
X
X	original_cc = auto_copy;
X	address[0] = '\0';
X
X	if (header_table[current-1].status & FORM_LETTER)
X	  PutLine0(LINES-3,COLUMNS-40,"<no editing allowed>");
X	else {
X	  edit_msg = (want_to("Edit outgoing message (y/n) ? ",'y',FALSE)!='n');
X	  Write_to_screen("%s", 1, edit_msg? "Yes" : "No");
X	}
X
X	auto_cc = TRUE;			/* we want a copy */
X
X	if (strlen(header_table[current-1].subject) > 0) {
X	  strcpy(subject,header_table[current-1].subject); 
X	  results = send(address, subject, edit_msg,
X	    header_table[current-1].status & FORM_LETTER? 
X	    PREFORMATTED : allow_forms);
X	}
X	else
X	  results = send(address, "Forwarded Mail...", edit_msg, 
X	    header_table[current-1].status & FORM_LETTER? 
X	    PREFORMATTED : allow_forms);
X	
X	auto_copy = original_cc;
X
X	return(results);
X}
X
Xget_and_expand_everyone(return_address, full_address)
Xchar *return_address, *full_address;
X{
X	/** Read the current message, extracting addresses from the 'To:'
X	    and 'Cc:' lines.   As each address is taken, ensure that it
X	    isn't to the author of the message NOR to us.  If neither,
X	    prepend with current return address and append to the 
X	    'full_address' string.
X	**/
X
X    char ret_address[LONG_SLEN], buf[LONG_SLEN], new_address[LONG_SLEN],
X	 address[LONG_SLEN], comment[LONG_SLEN];
X    int  in_message = 1, first_pass = 0, index;
X
X    /** First off, get to the first line of the message desired **/
X
X    if (fseek(mailfile, header_table[current-1].offset, 0) == -1) {
X	dprint3(1,"Error: seek %ld resulted in errno %s (%s)\n", 
X		 header_table[current-1].offset, error_name(errno), 
X		 "get_and_expand_everyone");
X	error2("ELM [seek] couldn't read %d bytes into file (%s)",
X	       header_table[current-1].offset, error_name(errno));
X	return;
X    }
X 
X    /** okay!  Now we're there!  **/
X
X    /** let's fix the ret_address to reflect the return address of this
X	message with '%s' instead of the persons login name... **/
X
X    translate_return(return_address, ret_address);
X
X    /** now let's parse the actual message! **/
X
X    while (in_message) {
X      in_message = (int) (fgets(buf, LONG_SLEN, mailfile) != NULL);
X      if (first_word(buf, "From ") && first_pass++ != 0) 
X	in_message = FALSE;
X      else if (first_word(buf, "To:") || first_word(buf, "Cc:") ||
X	       first_word(buf, "CC:") || first_word(buf, "cc:")) {
X	do {
X	  no_ret(buf);
X
X	  /** we have a buffer with a list of addresses, each of either the
X	      form "address (name)" or "name <address>".  Our mission, should
X	      we decide not to be too lazy, is to break it into the two parts.
X	  **/
X	      
X	  if (!whitespace(buf[0]))
X	    index = chloc(buf, ':')+1;		/* skip header field */
X	  else
X	    index = 0;				/* skip whitespace   */
X
X	  while (break_down_tolist(buf, &index, address, comment)) {
X
X	    if (okay_address(address, return_address)) {
X	      sprintf(new_address, ret_address, address);
X	      optimize_and_add(new_address, full_address);
X	    }
X	  }
X
X          in_message = (int) (fgets(buf, LONG_SLEN, mailfile) != NULL);
X
X	  if (in_message) dprint1(1,"> %s", buf);
X	
X	} while (in_message && whitespace(buf[0]));
X
X      }
X      else if (strlen(buf) < 2)	/* done with header */
X	 in_message = FALSE;
X    }
X}
X
Xint
Xokay_address(address, return_address)
Xchar *address, *return_address;
X{
X	/** This routine checks to ensure that the address we just got
X	    from the "To:" or "Cc:" line isn't us AND isn't the person	
X	    who sent the message.  Returns true iff neither is the case **/
X
X	char our_address[SLEN];
X	struct addr_rec  *alternatives;
X
X	if (in_string(address, return_address))
X	  return(FALSE);
X
X	sprintf(our_address, "%s!%s", hostname, username);
X
X	if (in_string(address, our_address))
X	  return(FALSE);
X
X	sprintf(our_address, "%s@%s", username, hostname);
X	  
X	if (in_string(address, our_address))
X	  return(FALSE);
X
X	alternatives = alternative_addresses;
X
X	while (alternatives != NULL) {
X	  if (in_string(address, alternatives->address))
X	    return(FALSE);
X	  alternatives = alternatives->next;
X	}
X
X	return(TRUE);
X}
X	    
Xoptimize_and_add(new_address, full_address)
Xchar *new_address, *full_address;
X{
X	/** This routine will add the new address to the list of addresses
X	    in the full address buffer IFF it doesn't already occur.  It
X	    will also try to fix dumb hops if possible, specifically hops
X	    of the form ...a!b...!a... and hops of the form a@b@b etc 
X	**/
X
X	register int len, host_count = 0, i;
X	char     hosts[MAX_HOPS][SLEN];	/* array of machine names */
X	char     *host, *addrptr;
X
X	if (in_string(full_address, new_address))
X	  return(1);	/* duplicate address */
X
X	/** optimize **/
X	/*  break down into a list of machine names, checking as we go along */
X	
X	addrptr = (char *) new_address;
X
X	while ((host = get_token(addrptr, "!", 1)) != NULL) {
X	  for (i = 0; i < host_count && ! equal(hosts[i], host); i++)
X	      ;
X
X	  if (i == host_count) {
X	    strcpy(hosts[host_count++], host);
X	    if (host_count == MAX_HOPS) {
X	       dprint1(2,
X              "Error: hit max_hops limit trying to build return address (%s)\n",
X		      "optimize_and_add");
X	       error("Can't build return address - hit MAX_HOPS limit!");
X	       return(1);
X	    }
X	  }
X	  else 
X	    host_count = i + 1;
X	  addrptr = NULL;
X	}
X
X	/** fix the ARPA addresses, if needed **/
X	
X	if (chloc(hosts[host_count-1], '@') > -1)
X	  fix_arpa_address(hosts[host_count-1]);
X	  
X	/** rebuild the address.. **/
X
X	new_address[0] = '\0';
X
X	for (i = 0; i < host_count; i++)
X	  sprintf(new_address, "%s%s%s", new_address, 
X	          new_address[0] == '\0'? "" : "!",
X	          hosts[i]);
X
X	if (full_address[0] == '\0')
X	  strcpy(full_address, new_address);
X	else {
X	  len = strlen(full_address);
X	  full_address[len  ] = ',';
X	  full_address[len+1] = ' ';
X	  full_address[len+2] = '\0';
X	  strcat(full_address, new_address);
X	}
X
X	return(0);
X}
X
Xget_return_name(address, name, shift_lower)
Xchar *address, *name;
Xint   shift_lower;
X{
X	/** Given the address (either a single address or a combined list 
X	    of addresses) extract the login name of the first person on
X	    the list and return it as 'name'.  Modified to stop at
X	    any non-alphanumeric character. **/
X
X	/** An important note to remember is that it isn't vital that this
X	    always returns just the login name, but rather that it always
X	    returns the SAME name.  If the persons' login happens to be,
X	    for example, joe.richards, then it's arguable if the name 
X	    should be joe, or the full login.  It's really immaterial, as
X	    indicated before, so long as we ALWAYS return the same name! **/
X
X	/** Another note: modified to return the argument as all lowercase
X	    always, unless shift_lower is FALSE... **/
X
X	char single_address[LONG_SLEN];
X	register int i, loc, index = 0;
X
X	dprint2(6,"get_return_name called with (%s, <>, shift=%s)\n", 
X		   address, onoff(shift_lower));
X
X	/* First step - copy address up to a comma, space, or EOLN */
X
X	for (i=0; address[i] != ',' && ! whitespace(address[i]) && 
X	     address[i] != '\0'; i++)
X	  single_address[i] = address[i];
X	single_address[i] = '\0';
X
X	/* Now is it an ARPA address?? */
X
X	if ((loc = chloc(single_address, '@')) != -1) {	  /* Yes */
X
X	  /* At this point the algorithm is to keep shifting our copy 
X	     window left until we hit a '!'.  The login name is then
X             located between the '!' and the first metacharacter to 
X	     it's right (ie '%', ':' or '@'). */
X
X	  for (i=loc; single_address[i] != '!' && i > -1; i--)
X	      if (single_address[i] == '%' || 
X	          single_address[i] == ':' ||
X	          single_address[i] == '.' ||	/* no domains */
X		  single_address[i] == '@') loc = i-1;
X	
X	  if (i < 0 || single_address[i] == '!') i++;
X
X	  for (index = 0; index < loc - i + 1; index++)
X	    if (shift_lower)
X	      name[index] = tolower(single_address[index+i]);
X	    else
X	      name[index] = single_address[index+i];
X	  name[index] = '\0';
X	}
X	else {	/* easier - standard USENET address */
X
X	  /* This really is easier - we just cruise left from the end of
X	     the string until we hit either a '!' or the beginning of X
X	while (buf[*index] != ',' && buf[*index] != '\0')
X	  buffer[loc++] = buf[(*index)++];
X
X	(*index)++;
X
X	buffer[loc] = '\0';
X	while (whitespace(buffer[loc - 1]))
X	    buffer[--loc] = '\0';	/* remove trailing whitespace */
X
X	buffer[loc] = '\0';
X
X	if (strlen(buffer) == 0) return(FALSE);
X
X	dprint1(5, "\n* got \"%s\"\n", buffer);
X
X	if (buffer[loc-1] == ')') {	/*   address (name)  format */
X	  for (loc = 0;buffer[loc] != '(' && loc < strlen(buffer); loc++)
X		/* get to the opening comment character... */ ;
X
X	  loc--;	/* back up to just before the paren */
X	  while (whitespace(buffer[loc])) loc--;	/* back up */
X
X	  /** get the address field... **/
X
X	  for (i=0; i <= loc; i++)
X	    address[i] = buffer[i];
X	  address[i] = '\0';
X
X	  /** now get the comment field, en toto! **/
X
X	  loc = 0;
X
X	  for (i = chloc(buffer, '('); i < strlen(buffer); i++)
X	    comment[loc++] = buffer[i];
X	  comment[loc] = '\0';
X	}
X	else if (buffer[loc-1] == '>') {	/*   name <address>  format */
X	  dprint0(7, "\tcomment <address>\n");
X	  for (loc = 0;buffer[loc] != '<' && loc < strlen(buffer); loc++)
X		/* get to the opening comment character... */ ;
X	  while (whitespace(buffer[loc])) loc--;	/* back up */
X
X	  /** get the comment field... **/
X
X	  comment[0] = '(';
X	  for (i=1; i < loc; i++)
X	    comment[i] = buffer[i-1];
X	  comment[i++] = ')';
X	  comment[i] = '\0';
X
X	  /** now get the address field, en toto! **/
X
X	  loc = 0;
X
X	  for (i = chloc(buffer,'<') + 1; i < strlen(buffer) - 1; i++)
X	    address[loc++] = buffer[i];
X	
X	  address[loc] = '\0';
X	}
X	else {
X	  strcpy(address, buffer);
X	  comment[0] = '\0';
X	}
X
X	dprint2(5,"-- returning '%s' '%s'\n", address, comment);
X
X	return(TRUE);
X}
END_OF_src/reply.c
if test 14009 -ne `wc -c <src/reply.c`; then
    echo shar: \"src/reply.c\" unpacked with wrong size!?
fi
# end of overwriting check
fi
echo shar: End of archive 14 \(of 19\).
cp /dev/null ark14isdone
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