syd@dsinc.UUCP (Syd Weinstein) (12/13/88)
---- Cut Here and unpack ---- #!/bin/sh # this is part 14 of a multipart archive # do not concatenate these parts, unpack them in order with /bin/sh # file src/leavembox.c continued # CurArch=14 if test ! -r s2_seq_.tmp then echo "Please unpack part 1 first!" exit 1; fi ( read Scheck if test "$Scheck" != $CurArch then echo "Please unpack part $Scheck next!" exit 1; else exit 0; fi ) < s2_seq_.tmp || exit 1 echo "x - Continuing file src/leavembox.c" sed 's/^X//' << 'SHAR_EOF' >> src/leavembox.c X * Fix for bug when you leave an empty mailfile for a non-existent one X * X * 88/08/27 ssw X * add deluth patches X * X * 88/08/27 nicmad!brown (Mike Brown) X * move extern int errno out of BSD ifdef X * X * 88/08/27 ssw X * Changes due to alpha testing at dsinc X * remove header file unistd - not present and not used X * X * Revision 2.2 88/07/21 09:58:48 edc X * Final hacks and cleanup to the 2.1 alpha test release. X * X * Revision 2.1 88/06/28 12:52:07 edc X * Added NOUTIMBUF define. Automatically defines utimbuf on non-bsd systems if X * set. This is checked for in Configure.sh since it seems that many systems X * don't define utimbuf for some non-obvious reason. X * X * X * Revision 2.0 88/06/27 17:25:13 edc X * The original 2.0 gamma sources as leaked from HP X * X * X * X ******************************************************************************/ X X/** leave current mailbox, updating etc. as needed... X X**/ X X#include "headers.h" X#include <sys/types.h> X#include <sys/stat.h> X#include <sys/file.h> X#include <errno.h> X X#define ECHOIT 1 /* echo on for prompting! */ X X/********** X Since a number of machines don't seem to bother to define the utimbuf X structure for some *very* obscure reason.... X X Suprise, though, BSD has a different utime() entirely...*sigh* X**********/ X X#ifndef BSD X# ifdef NOUTIMBUF X Xstruct utimbuf { X time_t actime; /** access time **/ X time_t modtime; /** modification time **/ X }; X X X# endif NOUTIMBUF X#endif BSD X Xextern int errno; X Xchar *error_name(), *error_description(), *strcpy(); Xunsigned short getegid(); Xunsigned long sleep(); X Xint Xleave_mbox(quitting) Xint quitting; X{ X /** Exit, saving files into mbox and deleting specified, or simply X delete specified mail... If "quitting" is true, then output status X regardless of what happens. Returns 1 iff mailfile was X changed (ie messages deleted from file), 0 if not, and -1 if new X mail has arrived in the meantime... X **/ X X FILE *temp; X char outfile[SLEN], buffer[SLEN]; X struct stat buf; /* stat command */ X#ifdef BSD X time_t utime_buffer[2]; /* utime command */ X#else X struct utimbuf utime_buffer; /* utime command */ X#endif X register int to_delete = 0, to_save = 0, i, X pending = 0, number_saved = 0, last_sortby; X char answer; X long bytes(); X X dprint(1, (debugfile, "\n\n-- leaving_mailbox --\n\n")); X X if (message_count == 0) { X if (mbox_specified == 0) /* null mailbox lock? */ X unlock(); X X return(FALSE); /* nothing changed */ X } X X for (i = 0; i < message_count; i++) X if (ison(header_table[i].status, DELETED)) to_delete++; X else to_save++; X X dprint(2, (debugfile, X "Count: %d to delete and %d to save\n", to_delete, to_save)); X X if (mbox_specified == 0) X update_mailtime(); X X if (hp_softkeys && question_me) { X define_softkeys(YESNO); /* YES or NO on softkeys */ X softkeys_on(); X } X X if (to_delete) { X if (always_del) /* set up the default answer... */ X answer = 'y'; X else X answer = 'n'; X X if (to_save) { X if (question_me) { X fflush(stdin); X sprintf(buffer, "Delete message%s? (y/n) ", plural(to_delete)); X answer = want_to(buffer, answer, ECHOIT); X } X if (answer != 'y') { X if (mbox_specified == 0) unlock(); /* remove lock! */ X dprint(3, (debugfile, "\tDelete message%s? - answer was NO\n", X plural(to_delete))); X error("Nothing deleted"); X return(FALSE); /* nothing was deleted! */ X } X } X else if (! to_save) { /* nothing to save!! */ X if (question_me) { X fflush(stdin); X answer = want_to("Delete all mail? (y/n) ", answer, ECHOIT); X } X if (answer != 'y') { X if (mbox_specified == 0) unlock(); /* remove lock! */ X dprint(3, (debugfile, "Delete all mail? - answer was NO\n")); X error("Nothing deleted"); X return(FALSE); /* nothing was deleted */ X } X } X } X X /** we have to check to see what the sorting order was...so that X the order of saved messages is the same as the order of the X messages originally (a subtle point...) **/ X X if (sortby != RECEIVED_DATE) { /* what we want anyway! */ X last_sortby = sortby; X sortby = RECEIVED_DATE; X sort_mailbox(message_count, FALSE); X sortby = last_sortby; X } X X if (to_save && mbox_specified == 0) { X if (always_leave) /* set up default answer */ X answer = 'y'; X else X answer = 'n'; X if (question_me) { X fflush(stdin); X answer = want_to("Keep mail in incoming mailbox? (y/n) ", X answer, ECHOIT); X } X if (answer == 'y') { X if (to_delete) /* okay - keep undeleted as pending! */ X pending++; X else { /* gag! nothing to delete, don't save! */ X unlock(); /* remove mailfile lock! */ X dprint(3, (debugfile, X "Keep mail in incoming mailbox? -- answer was YES\n")); X error("Mailbox unchanged"); X return(FALSE); /* nothing changed! */ X } X } X } X X /** next, let's lock the file up and make one last size check **/ X X if (mbox_specified == 0) lock(OUTGOING); X X if (mailfile_size != bytes(infile)) { X unlock(); X error("New mail has just arrived - resyncing"); X return(-1); X } X X /** okay...now lets do it! **/ X X if (to_save > 0) { X if (to_delete > 0) X sprintf(buffer ,"[%s %d message%s, and deleting %d]", X pending? "keeping" : "storing", X to_save, plural(to_save), to_delete); X else if (quitting) X sprintf(buffer,"[%s %s]", X pending? "keeping" : "storing", X to_save > 1? "all messages" : "message"); X else X buffer[0] = '\0'; /* no string! */ X } X else { X if (to_delete > 0) X sprintf(buffer, "[deleting all messages]"); X else if (quitting) X sprintf(buffer, "[no messages to %s, and none to delete]", X pending? "keep" : "save"); X else X buffer[0] = '\0'; X } X X dprint(2, (debugfile, "Action: %s\n", buffer)); X X error(buffer); X X if (! mbox_specified) { X if (pending) { /* keep some messages pending! */ X sprintf(outfile,"%s%d", temp_mbox, getpid()); X unlink(outfile); X } X else if (mailbox_defined) /* save to specified mailbox */ X strcpy(outfile, mailbox); X else /* save to $home/mbox */ X sprintf(outfile,"%s/mbox", home); X } X else { X if (! to_delete) return(FALSE); /* no work to do! */ X sprintf(outfile, "%s%d", temp_file, getpid()); X unlink(outfile); /* ensure it's empty! */ X } X X if (to_save) { X if ((errno = can_open(outfile, "a"))) { X error1( X "Permission to append to %s denied! Leaving mailbox intact\n", X outfile); X dprint(1, (debugfile, X "Error: Permission to append to outfile %s denied!! (%s)\n", X outfile, "leavembox")); X dprint(1, (debugfile, "** %s - %s **\n", error_name(errno), X error_description(errno))); X unlock(); X return(0); X } X if ((temp = fopen(outfile,"a")) == NULL) { X if (mbox_specified == 0) X unlock(); /* remove mailfile lock! */ X dprint(1, (debugfile, "Error: could not append to file %s\n", X outfile)); X dprint(1, (debugfile, "** %s - %s **\n", error_name(errno), X error_description(errno))); X sprintf(buffer, " Could not append to file %s! ", X outfile); X Centerline(LINES-1, buffer); X emergency_exit(); X } X X for (i = 0; i < message_count; i++) X if (! (header_table[i].status & DELETED)) { X current = i+1; X if (! number_saved++) { X dprint(3, (debugfile, "Saving message%s #%d, ", X plural(to_save), current)); X } X else { X dprint(3, (debugfile, "#%d, ", current)); X } X copy_message("", temp, FALSE, FALSE); X } X fclose(temp); X dprint(2, (debugfile, "\n\n")); X } X X /* remove source file...either default mailbox or original copy of X specified one! */ X X if (stat(infile, &buf) != 0) { /* grab original times... */ X dprint(1, (debugfile, "Error: errno %s attempting to stat file %s\n", X error_name(errno), infile)); X error3("Error %s (%s) on stat(%s)", error_name(errno), X error_description(errno), infile); X } X X fclose(mailfile); /* close the baby... */ X X if (mailfile_size != bytes(infile)) { X sort_mailbox(message_count, FALSE); /* display sorting order! */ X unlock(); X error("New mail has just arrived - resyncing..."); X return(-1); X } X X X if (infile != NULL) X unlink(infile); /* and BLAMO! */ X X if (to_save && (mbox_specified || pending)) { X if (link(outfile, infile) != 0) X if (errno == EXDEV || X errno == EEXIST ) { /** different file devices! Use copy! **/ X if (copy(outfile, infile) != 0) { X dprint(1, (debugfile, "leavembox: copy(%s, %s) failed;", X outfile, infile)); X dprint(1, (debugfile, "** %s - %s **\n", error_name(errno), X error_description(errno))); X error("couldn't modify mail file!"); X sleep(1); X sprintf(infile,"%s/%s", home, unedited_mail); X if (copy(outfile, infile) != 0) { X dprint(1, (debugfile, X "leavembox: couldn't copy to %s either!! Help;", X infile)); X dprint(1, (debugfile, "** %s - %s **\n", error_name(errno), X error_description(errno))); X error("something godawful is happening to me!!!"); X emergency_exit(); X } X else { X dprint(1, (debugfile, X "\nWoah! Confused - Saved mail in %s (leavembox)\n", X infile)); X error1("saved mail in %s", infile); X } X } X } X else { X dprint(1, (debugfile, "link(%s, %s) failed (leavembox)\n", X outfile, infile)); X dprint(1, (debugfile, "** %s - %s **\n", error_name(errno), X error_description(errno))); X error2("link failed! %s - %s", error_name(errno), X error_description(errno)); X emergency_exit(); X } X X unlink(outfile); X restore_file_stats(infile); X } X else if (keep_empty_files) { X sleep(1); X error1("..keeping empty mail file '%s'..", infile); X temp = fopen(infile, "w"); X fclose(temp); X restore_file_stats(infile); X } X X if (mbox_specified == 0) { X if (! pending) { /* if none still being saved */ X temp = fopen(infile, "w"); X fclose(temp); X } X X restore_file_stats(infile); X X /* let's set the access times of the new mail file to be X the same as the OLD one (still sitting in 'buf') ! */ X X#ifdef BSD X utime_buffer[0] = buf.st_atime; X utime_buffer[1] = buf.st_mtime; X#else X utime_buffer.actime = buf.st_atime; X utime_buffer.modtime= buf.st_mtime; X#endif X X#ifdef BSD X if (utime(infile, utime_buffer) != 0) { X#else X if (utime(infile, &utime_buffer) != 0) { X#endif X dprint(1, (debugfile, X "Error: encountered error doing utime (leavmbox)\n")); X dprint(1, (debugfile, "** %s - %s **\n", error_name(errno), X error_description(errno))); X error2("Error %s trying to change file %s access time", X error_name(errno), infile); X } X X unlock(); /* remove the lock on the file ASAP! */ X X /** finally, let's change the ownership of the default X outgoing mailbox, if needed **/ X X if (to_save) X chown(outfile, userid, groupid); X } X X mailfile_size = bytes(infile); X return(to_delete); X} X Xchar lock_name[SLEN]; Xint we_created_lock = 0; /* set to true iff we create it */ X Xlock(direction) Xint direction; X{ X /** Create lock file to ensure that we don't get any mail X while altering the mailbox contents! X If it already exists sit and spin until X either the lock file is removed...indicating new mail X or X we have iterated MAX_ATTEMPTS times, in which case we X either fail or remove it and make our own (determined X by if REMOVE_AT_LAST is defined in header file X X If direction == INCOMING then DON'T remove the lock file X on the way out! (It'd mess up whatever created it!). X **/ X X register int iteration = 0, lock_fd; X X sys_lock_file(lock_name, username); X lock_fd = open(lock_name, O_WRONLY | O_CREAT | O_EXCL, 0777); X X if (lock_fd < 0 && errno == EACCES) { X if (direction == OUTGOING) { X dprint(1, (debugfile, X "Error encountered attempting to create lock %s\n", X lock_name)); X dprint(1, (debugfile, "** %s - %s **\n", error_name(errno), X error_description(errno))); X MoveCursor(LINES, 0); X printf( X "\n\rError encountered while attempting to create lock file %s;\n\r", X lock_name); X printf("** %s - %s **\n\r\n\r", error_name(errno), X error_description(errno)); X leave(); X } X else { /* permission denied in the middle? Odd... */ X dprint(1, (debugfile, X "Can't create lock file: creat(%s) raises error %s (lock)\n", X lock_name, error_name(errno))); X leave(error1( X "Can't create lock file! I need write permission in \"%s\"\n\r", X mailhome)); X } X } X X /** if lock_fd is not a valid file descriptor at this point, it X means that we're circling because someone *else* has the X file descriptor. We'll keep iterating until something X new and exciting happens or we give up. X **/ X X while (lock_fd < 0 && iteration++ < MAX_ATTEMPTS) { X X dprint(2, (debugfile,"File '%s' already exists! Waiting...(lock)\n", X lock_name)); X X if (direction == INCOMING) { X if (iteration == 1) X PutLine0(LINES, 0, "Mail being received...\t\twaiting...1 "); X else X printf("%d ", iteration); X fflush(stdout); X } X else X error1(" Attempt #%d: Mail being received...waiting ", iteration); X sleep(5); X X lock_fd = open(lock_name, O_WRONLY | O_CREAT | O_EXCL, 0777); X } X X if (lock_fd < 0) { /* we tried. We really did. But noooo */ X X#ifdef REMOVE_AT_LAST X X /** time to waste the lock file! Must be there in error! **/ X X dprint(2, (debugfile, X "Warning: I'm giving up waiting - removing lock file(lock)\n")); X if (direction == INCOMING) X PutLine0(LINES, 0,"\nTimed out - removing current lock file..."); X else X error("Throwing away the current lock file!"); X X if (unlink(lock_name) != 0) { X dprint(1, (debugfile, X "Error %s (%s)\n\ttrying to unlink file %s (%s)\n", X error_name(errno), error_description(errno), lock_name)); X PutLine1(LINES, 0, X "\n\rI couldn't remove the current lock file %s\n\r", X lock_name); X PutLine2(LINES, 0, "** %s - %s **\n\r", error_name(errno), X error_description(errno)); X if (direction == INCOMING) X leave(); X else X emergency_exit(); X } X X /* everything is okay, so lets act as if nothing had happened... */ X X#else X X /* Okay...we die and leave, not updating the mailfile mbox or X any of those! */ X X if (direction == INCOMING) { X PutLine1(LINES, 0, "\n\r\n\rGiving up after %d iterations...\n\r", X iteration); X PutLine0(LINES, 0, X "\n\rPlease try to read your mail again in a few minutes.\n\r\n\r"); X dprint(1, (debugfile, X "Warning: bailing out after %d iterations...(lock)\n", X iteration)); X leave_locked(0); X } X else { X dprint(1, (debugfile, X "Warning: after %d iterations, timed out! (lock)\n", X iteration)); X leave(error("Timed out on lock file reads. Leaving program.")); X } X X#endif X } X X /* if we get here we've created the lock file, so lets just split */ X X close(lock_fd); /* close it. We don't want to KEEP the thing! */ X} X Xunlock() X{ X /** Remove the lock file! This must be part of the interrupt X processing routine to ensure that the lock file is NEVER X left sitting in the mailhome directory! **/ X X (void) unlink(lock_name); X} SHAR_EOF echo "File src/leavembox.c is complete" chmod 0444 src/leavembox.c || echo "restore of src/leavembox.c fails" echo "x - extracting src/limit.c (Text)" sed 's/^X//' << 'SHAR_EOF' > src/limit.c && X Xstatic char rcsid[] = "@(#)$Id: limit.c,v 2.1 88/07/21 09:58:51 edc Exp $"; X X/******************************************************************************* X * The Elm Mail System - $Revision: 2.1 $ $State: Exp $ X * X * Copyright (c) 1986 Dave Taylor X ******************************************************************************* X * Bug reports, patches, comments, suggetions should be sent to: X * X * Syd Weinstein, Elm Corrdinator X * syd@dsinc.UUCP dsinc!syd X * X ******************************************************************************* X * $Log: limit.c,v $ X * Revision 2.1 88/07/21 09:58:51 edc X * checked in with -k by syd at 88.09.15.20.28.48. X * X * Revision 2.1 88/07/21 09:58:51 edc X * Final hacks and cleanup to the 2.1 alpha test release. X * X * Revision 2.0 88/06/27 17:25:14 edc X * The original 2.0 gamma sources as leaked from HP X * X * X * X ******************************************************************************/ X X/** This stuff is inspired by MH and dmail and is used to 'select' X a subset of the existing mail in the folder based on one of a X number of criteria. The basic tricks are pretty easy - we have X as status of VISIBLE associated with each header stored in the X (er) mind of the computer (!) and simply modify the commands to X check that flag...the global variable `selected' is set to the X number of messages currently selected, or ZERO if no select. X**/ X X#include "headers.h" X X#define TO 1 X#define FROM 2 X Xchar *shift_lower(); X Xint Xlimit() X{ X /** returns non-zero if we did enough to redraw the screen **/ X X char criteria[STRING], first[STRING], rest[STRING]; X int last_current; X X if (selected) { X PutLine1(LINES-2, 0, X "Already have selection criteria - add more? (y/n) n%c", X BACKSPACE); X criteria[0] = ReadCh(); X if (tolower(criteria[0]) == 'y') X PutLine0(LINES-3, COLUMNS-30, "Adding criteria..."); X else { X selected = 0; X PutLine0(LINES-3, COLUMNS-30, "Use '?' for help"); X } X X } X X PutLine1(LINES-2, 0, "Enter criteria: "); X CleartoEOLN(); X X criteria[0] = '\0'; X optionally_enter(criteria, LINES-2, 16, FALSE); X X if (strlen(criteria) == 0) return(0); X X split_word(criteria, first, rest); X X if (equal(first, "all")) { X selected = 0; X return(TRUE); X } X X last_current = current; X current = -1; X X if (equal(first, "subj") || equal(first, "subject")) X selected = limit_selection(SUBJECT, rest, selected); X else if (equal(first, "to")) X selected = limit_selection(TO, rest, selected); X else if (equal(first, "from")) X selected = limit_selection(FROM, rest, selected); X else { X selected = 0; X error1("Don't understand \"%s\" as a selection criteria!", first); X sleep(2); X } X X if (! selected) X current = last_current; X else X current = visible_to_index(1)+1; /* map it and shift up 1 */ X X if (! selected) X set_error("no items selected"); X else { X sprintf(first, "%d items selected", selected); X set_error(first); X } X X return(selected); X} X Xint Xlimit_selection(based_on, pattern, additional_criteria) Xint based_on, additional_criteria; Xchar *pattern; X{ X /** Given the type of criteria, and the pattern, mark all X non-matching headers as ! VISIBLE. If additional_criteria, X don't mark as visible something that isn't currently! X **/ X X register int index, count = 0; X X dprint(2, (debugfile, "\n\n\n**limit on %d - '%s' - (%s) **\n\n", X based_on, pattern, additional_criteria?"add'tl":"base")); X X if (based_on == SUBJECT) { X for (index = 0; index < message_count; index++) X if (! in_string(shift_lower(header_table[index].subject), pattern)) X header_table[index].status &= ~VISIBLE; X else if (additional_criteria && X header_table[index].status | VISIBLE) X header_table[index].status &= ~VISIBLE; /* shut down! */ X else { /* mark it as readable */ X header_table[index].status |= VISIBLE; X count++; X dprint(5, (debugfile, X " Message %d (%s from %s) marked as visible\n", X index, header_table[index].subject, X header_table[index].from)); X } X } X else if (based_on == FROM) { X for (index = 0; index < message_count; index++) X if (! in_string(shift_lower(header_table[index].from), pattern)) X header_table[index].status &= ~VISIBLE; X else if (additional_criteria && X header_table[index].status | VISIBLE) X header_table[index].status &= ~VISIBLE; /* shut down! */ X else { /* mark it as readable */ X header_table[index].status |= VISIBLE; X count++; X dprint(5, (debugfile, X " Message %d (%s from %s) marked as visible\n", X index, header_table[index].subject, X header_table[index].from)); X } X } X else if (based_on == TO) { X for (index = 0; index < message_count; index++) X if (! in_string(shift_lower(header_table[index].to), pattern)) X header_table[index].status &= ~VISIBLE; X else if (additional_criteria && X header_table[index].status | VISIBLE) X header_table[index].status &= ~VISIBLE; /* shut down! */ X else { /* mark it as readable */ X header_table[index].status |= VISIBLE; X count++; X dprint(5, (debugfile, X " Message %d (%s from %s) marked as visible\n", X index, header_table[index].subject, X header_table[index].from)); X } X } X X dprint(4, (debugfile, "\n** returning %d selected **\n\n\n", count)); X X return(count); X} X Xint Xnext_visible(index) Xint index; X{ X /** Given 'index', this routine will return the actual index into the X array of the NEXT visible message, or '-1' if none are visible X **/ X X register int remember_for_debug; X remember_for_debug = index; X X index--; /* shift from 'current' to actual index */ X index++; /* make sure we don't bump into ourself! */ X X while (index < message_count) { X if (header_table[index].status & VISIBLE) { X dprint(9, (debugfile, "[Next visible: given %d returning %d]\n", X remember_for_debug, index+1)); X return(index+1); X } X index++; X } X X return(-1); X} X Xint Xprevious_visible(index) Xint index; X{ X /** Just like 'next-visible', but backwards FIRST... */ X X register int remember_for_debug; X remember_for_debug = index; X X index -= 2; /* shift from 'current' to actual index, and skip us! */ X X while (index > -1) { X if (header_table[index].status & VISIBLE) { X dprint(9, (debugfile, "[previous visible: given %d returning %d]", X remember_for_debug, index+1)); X return(index+1); X } X index--; X } X X return(-1); X} X Xint Xcompute_visible(message) Xint message; X{ X /** return the 'virtual' index of the specified message in the X set of messages - that is, if we have the 25th message as X the current one, but it's #2 based on our limit criteria, X this routine, given 25, will return 2. X **/ X X register int index, count = 0; X X if (! selected) return(message); X X if (message < 0) message = 0; /* normalize */ X X for (index = 0; index <= message; index++) X if (header_table[index].status & VISIBLE) X count++; X X dprint(4, (debugfile, X "[compute-visible: displayed message %d is actually %d]\n", X count, message)); X X return(count); X} X Xint Xvisible_to_index(message) Xint message; X{ X /** Given a 'virtual' index, return a real one. This is the X flip-side of the routine above, and returns (message_count+1) X if it cannot map the virtual index requested (too big) X **/ X X register int index = 0, count = 0; X X for (index = 0; index < message_count; index++) { X if (header_table[index].status & VISIBLE) X count++; X if (count == message) { X dprint(4, (debugfile, X "visible-to-index: (up) index %d is displayed as %d\n", X message, index)); X return(index); X } X } X X dprint(4, (debugfile, "index %d is NOT displayed!\n", message)); X X return(message_count+1); X} SHAR_EOF chmod 0444 src/limit.c || echo "restore of src/limit.c fails" echo "x - extracting src/mailmsg1.c (Text)" sed 's/^X//' << 'SHAR_EOF' > src/mailmsg1.c && X Xstatic char rcsid[] = "@(#)$Id: mailmsg1.c,v 2.1 88/09/15 20:28:51 syd Exp $"; X X/******************************************************************************* X * The Elm Mail System - $Revision: 2.1 $ $State: Exp $ X * X * Copyright (c) 1986 Dave Taylor X ******************************************************************************* X * Bug reports, patches, comments, suggetions should be sent to: X * X * Syd Weinstein, Elm Corrdinator X * syd@dsinc.UUCP dsinc!syd X * X ******************************************************************************* X * $Log: mailmsg1.c,v $ X * Revision 2.1 88/09/15 20:28:51 syd X * checked in with -k by syd at 88.09.15.20.28.51. X * X * 88/09/13 Rob Bernardo <rob@pbhyf.PacBell.COM > X * fixes how elm knows when to repaint the screen or not. X * X * 88/08/27 ssw X * add deluth patches X * add bcc strings always X * X * 88/08/27 ssw X * split tolower and ReadCh due to macro calls X * X * Revision 2.1 88/07/21 09:58:53 edc X * Final hacks and cleanup to the 2.1 alpha test release. X * X * Revision 2.0 88/06/27 17:25:15 edc X * The original 2.0 gamma sources as leaked from HP X * X * X * X ******************************************************************************/ X X/** Interface to allow mail to be sent to users. Part of ELM **/ X X X#include "headers.h" X X/** strings defined for the hdrconfg routines **/ X Xchar subject[SLEN], in_reply_to[SLEN], expires[SLEN], X action[SLEN], priority[SLEN], reply_to[SLEN], to[VERY_LONG_STRING], X cc[VERY_LONG_STRING], expanded_to[VERY_LONG_STRING], X expanded_cc[VERY_LONG_STRING], user_defined_header[SLEN], X bcc[VERY_LONG_STRING], expanded_bcc[VERY_LONG_STRING]; X Xchar *format_long(), *strip_commas(), *tail_of_string(), *strcpy(); Xunsigned long sleep(); X Xint Xsendmsg(given_to, given_cc, given_subject, edit_message, form_letter, replying) Xchar *given_to, *given_cc, *given_subject; Xint edit_message, form_letter, replying; 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 if (replying) then add an In-Reply-To: header... X Return TRUE if the main part of the screen has been changed X (useful for knowing whether a redraw is needed. X **/ X X int copy_msg = FALSE, is_a_response = FALSE; X X /* First: zero all current global message strings */ X X cc[0] = reply_to[0] = expires[0] = '\0'; X X#ifdef ALLOW_BCC X bcc[0] = expanded_bcc[0] = '\0'; X#endif X 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 strcpy(cc, given_cc); /* and so on.. */ 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 dprint(3, (debugfile, "\nMailing to %s\n", expanded_to)); X X if (get_subject(subject) == 0) /* get the Subject: field */ X return(0); X X dprint(4, (debugfile, "Subject is %s\n", subject)); X X if (prompt_for_cc) { X if (get_copies(cc, expanded_to, expanded_cc, copy_msg) == 0) X return(0); X X if (strlen(cc) > 0) X dprint(4, (debugfile, "Copies to %s\n", expanded_cc)); X } X X if (mail_only) /* indicate next step... */ X printf("\n\r"); X else X MoveCursor(LINES,0); /* so you know you've hit <return> ! */ X X /** generate the In-Reply-To: header... **/ X X if (is_a_response && replying) X generate_reply_to(current-1); X X /* and mail that puppy outta here! */ X X return(mail(copy_msg, edit_message, FALSE, form_letter)); 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 (void) build_address(strip_commas(to_field), address); X } X else if (mail_only) X (void) 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 char ch; 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 fflush(stdout); X } X else X if (user_level == 0) { X PutLine0(LINES-2,0,"Subject of message: "); X len = 20; 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 { X printf("\n\rNo subject - Continue with message? (y/n) n%c", X BACKSPACE); X fflush(stdout); X } X else X PutLine1(LINES-2,0,"No subject - Continue with message? (y/n) n%c", X BACKSPACE); X X ch = ReadCh(); X if (tolower(ch) != 'y') { /* user says no! */ X if (mail_only) { X printf("\n\r\n\rMail Cancelled!\n\r"); X fflush(stdout); 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, copy_message) Xchar *cc_field, *address, *addressII; Xint copy_message; 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 If copy-message, that means that we're going to have to invoke X a screen editor, so we'll need to delay after displaying the X possibly rewritten Cc: line... 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 /** The following test is that if the build_address routine had X reason to rewrite the entry given, then, if we're mailing only X print the new Cc line below the old one. If we're not, then X assume we're in screen mode and replace the incorrect entry on X the line above where we are (e.g. where we originally prompted X for the Cc: field). X **/ X X if (build_address(strip_commas(cc_field), addressII)) X if (mail_only) { X printf("\rCopies To: %s\r\n", format_long(addressII, 4)); X fflush(stdout); X } X else { X PutLine1(LINES-1, 11, "%s", addressII); X if ((strcmp(editor, "builtin") != 0 && strcmp(editor, "none") != 0) X || copy_message) X sleep(2); X } X X if (strlen(address) + strlen(addressII) > VERY_LONG_STRING) { X dprint(2, (debugfile, 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} SHAR_EOF chmod 0444 src/mailmsg1.c || echo "restore of src/mailmsg1.c fails" echo "x - extracting src/mailmsg2.c (Text)" sed 's/^X//' << 'SHAR_EOF' > src/mailmsg2.c && X Xstatic char rcsid[] = "@(#)$Id: mailmsg2.c,v 2.1 88/09/15 20:28:54 syd Exp $"; X X/******************************************************************************* X * The Elm Mail System - $Revision: 2.1 $ $State: Exp $ X * X * Copyright (c) 1986 Dave Taylor X ******************************************************************************* X * Bug reports, patches, comments, suggetions should be sent to: X * X * Syd Weinstein, Elm Corrdinator X * syd@dsinc.UUCP dsinc!syd X * X ******************************************************************************* X * $Log: mailmsg2.c,v $ X * Revision 2.1 88/09/15 20:28:54 syd X * checked in with -k by syd at 88.09.15.20.28.55. X * X * 88/09/13 Rob Bernardo <rob@pbhyf.PacBell.COM > X * fixes how elm knows when to repaint the screen or not. X * X * 88/09/02 Syd Weinstein X * added version buffer X * X * 88/08/27 ssw X * add deluth patches X * X * 88/08/27 ssw X * split tolower and ReadCh due to macro calls X * X * Revision 2.1 88/07/21 09:58:55 edc X * Final hacks and cleanup to the 2.1 alpha test release. X * X * Revision 2.0 88/06/27 17:25:17 edc X * The original 2.0 gamma sources as leaked from HP X * X * X * X ******************************************************************************/ X X/** Interface to allow mail to be sent to users. Part of ELM **/ X X X#include "headers.h" X#include <errno.h> X Xextern int errno; Xextern char version_buff[]; X Xchar *error_name(), *error_description(), *strip_parens(); Xchar *strcat(), *strcpy(); Xchar *format_long(), *strip_commas(), *tail_of_string(); X Xunsigned long sleep(); X X#ifdef SITE_HIDING X char *get_ctime_date(); X#endif XFILE *write_header_info(); X X/* these are all defined in the mailmsg1.c file! */ X Xextern char subject[SLEN], in_reply_to[SLEN], expires[SLEN], X action[SLEN], priority[SLEN], reply_to[SLEN], to[VERY_LONG_STRING], X cc[VERY_LONG_STRING], expanded_to[VERY_LONG_STRING], X expanded_cc[VERY_LONG_STRING], user_defined_header[SLEN]; X X#ifdef ALLOW_BCC Xchar bcc[VERY_LONG_STRING], expanded_bcc[VERY_LONG_STRING]; X#endif X Xint gotten_key = 0; X Xchar *bounce_off_remote(); X Xmail(copy_msg, edit_message, batch, form) Xint copy_msg, edit_message, batch, form; X{ X /** Given the addresses and various other miscellany (specifically, X 'copy-msg' indicates whether a copy of the current message should X be included, 'edit_message' indicates whether the message should X be edited and 'batch' indicates that the message should be read X from stdin) this routine will invoke an editor for the user and X then actually mail off the message. 'form' can be YES, NO, or X MAYBE. YES=add "Content-Type: mailform" header, MAYBE=add the X M)ake form option to last question, and NO=don't worry about it! X Also, if 'copy_msg' = FORM, then grab the form temp file and use X that... X Return TRUE if the main part of the screen has been changed X (useful for knowing whether a redraw is needed. X **/ X X FILE *reply, *real_reply; /* second is post-input buffer */ X char filename[SLEN], filename2[SLEN], fname[SLEN], X very_long_buffer[VERY_LONG_STRING]; X int ch; X register int retransmit = FALSE; X int already_has_text = FALSE; /* we need an ADDRESS */ X int old_raw; X int need_redraw = 0; X X static int cancelled_msg = 0; X X dprint(4, (debugfile, "\nMailing to \"%s\" (with%s editing)\n", X expanded_to, edit_message? "" : "out")); X X /** first generate the temporary filename **/ X X sprintf(filename,"%s%d",temp_file, getpid()); X X /** if possible, let's try to recall the last message? **/ X X if (! batch && copy_msg != FORM && user_level != 0) X retransmit = recall_last_msg(filename, copy_msg, &cancelled_msg, X &already_has_text); X X /** if we're not retransmitting, create the file.. **/ X X if (! retransmit) X if ((reply = fopen(filename,"w")) == NULL) { X dprint(1, (debugfile, X "Attempt to write to temp file %s failed with error %s (mail)\n", X filename, error_name(errno))); X error2("Could not create file %s (%s)",filename, X error_name(errno)); X return(need_redraw); X } X X chown (filename, userid, groupid); X X if (batch) { X Raw(OFF); X if (isatty(fileno(stdin))) { X fclose(reply); /* let edit-the-message open it! */ X printf("To: %s\nSubject: %s\n", expanded_to, subject); X strcpy(editor, "none"); /* force inline editor */ X if (no_editor_edit_the_message(filename)) X return(need_redraw);/* confused? edit_the_msg returns 1 if bad */ X X batch = FALSE; /* we've done it... **/ X edit_message = FALSE; X X Raw(ON); X goto top_of_verify_loop; X } X else { X while (gets(very_long_buffer) != NULL) X fprintf(reply, "%s\n", very_long_buffer); X } X } X X if (copy_msg == FORM) { X sprintf(fname, "%s%d", temp_form_file, getpid()); X fclose(reply); /* we can't retransmit a form! */ X if (access(fname,ACCESS_EXISTS) != 0) { X error("couldn't find forms file!"); X return(need_redraw); X } X unlink(filename); X dprint(4, (debugfile, "-- linking existing file %s to file %s --\n", X fname, filename)); X link(fname, filename); X unlink(fname); X } X else if (copy_msg && ! retransmit) /* if retransmit we have it! */ X if (edit_message) { X copy_message(prefixchars, reply, noheader, FALSE); X already_has_text = TRUE; /* we just added it, right? */ X } X else X copy_message("", reply, noheader, FALSE); X X if (!batch && ! retransmit && signature && copy_msg != FORM) { X fprintf(reply, "\n--\n"); /* News 2.11 compatibility? */ X if (chloc(expanded_to, '!') == -1 && chloc(expanded_to, '@') == -1) { X if (strlen(local_signature) > 0) { X if (local_signature[0] != '/') X sprintf(filename2, "%s/%s", home, local_signature); X else X strcpy(filename2, local_signature); X (void) append(reply, filename2); X already_has_text = TRUE; /* added signature... */ X } X } X else { X if (remote_signature[0] != '/') X sprintf(filename2, "%s/%s", home, remote_signature); X else X strcpy(filename2, remote_signature); X (void) append(reply, filename2); X already_has_text = TRUE; /* added signature... */ X } X } X X if (! retransmit && copy_msg != FORM) X if (reply != NULL) X (void) fclose(reply); /* on replies, it won't be open! */ X X /** Edit the message **/ X X if (edit_message) X create_readmsg_file(); /* for "readmsg" routine */ X Xtop_of_verify_loop: X X ch = edit_message? 'e' : ' '; /* drop through if needed... */ X X if (! batch) { X do { X switch (ch) { X case 'e': if (edit_the_message(filename, already_has_text)) { X cancelled_msg = TRUE; X return(need_redraw); X } X need_redraw++; X break; X X case 'h': if (mail_only) X batch_header_editor(); X else X edit_headers(); X need_redraw++; X break; X X default : /* do nothing */ ; X } X X /** ask that silly question again... **/ X X if ((ch = verify_transmission(filename, &form)) == 'f') { X cancelled_msg = TRUE; X return(need_redraw); X } X X } while (ch != 's'); X X if (form == YES) X if (format_form(filename) < 1) { X cancelled_msg = TRUE; X return(need_redraw); X } X X if ((reply = fopen(filename,"r")) == NULL) { X dprint(1, (debugfile, X "Attempt to open file %s for reading failed with error %s (mail)\n", X filename, error_name(errno))); X error1("Could not open reply file (%s)", error_name(errno)); X return(need_redraw); X } X } X else if ((reply = fopen(filename,"r")) == NULL) { X dprint(1, (debugfile, X "Attempt to open file %s for reading failed with error %s (mail)\n", X filename, error_name(errno))); X error1("Could not open reply file (%s)", error_name(errno)); X return(need_redraw); X } X X cancelled_msg = FALSE; /* it ain't cancelled, is it? */ X X /** ask about bounceback if the user wants us to.... **/ X X if (uucp_hops(to) > bounceback && bounceback > 0 && copy_msg != FORM) X if (verify_bounceback() == TRUE) { X if (strlen(cc) > 0) strcat(expanded_cc, ", "); X strcat(expanded_cc, bounce_off_remote(to)); X } X X /** grab a copy if the user so desires... **/ X X if (auto_cc) X save_copy(subject, expanded_to, expanded_cc, filename, to); X X /** write all header information into real_reply **/ X X sprintf(filename2,"%s%d",temp_file, getpid()+1); X X /** try to write headers to new temp file **/ X X dprint(6, (debugfile, "Composition file='%s' and mail buffer='%s'\n", X filename, filename2)); X X#ifdef ALLOW_BCC X dprint(2,(debugfile,"--\nTo: %s\nCc: %s\nBcc: %s\nSubject: %s\n---\n", X expanded_to, expanded_cc, expanded_bcc, subject)); X#else X dprint(2,(debugfile,"---\nTo: %s\nCc: %s\nSubject: %s\n---\n", X expanded_to, expanded_cc, subject)); X#endif X X if ((real_reply=write_header_info(filename2, expanded_to, expanded_cc, X#ifdef ALLOW_BCC X expanded_bcc, X#endif X form == YES)) == NULL) { X X /** IT FAILED!! MEIN GOTT! Use a dumb mailer instead! **/ X X dprint(3, (debugfile, "** write_header failed: %s\n", X error_name(errno))); X X if (cc[0] != '\0') /* copies! */ X sprintf(expanded_to,"%s %s", expanded_to, expanded_cc); X X sprintf(very_long_buffer, "( (%s -s \"%s\" %s ; %s %s) & ) < %s", X mailx, subject, strip_parens(strip_commas(expanded_to)), X remove, filename, filename); X X error1("Message sent using dumb mailer - %s", mailx); X sleep(2); /* ensure time to see this prompt! */ X X } X else { X copy_message_across(reply, real_reply); X X fclose(real_reply); X X if (cc[0] != '\0') /* copies! */ X sprintf(expanded_to,"%s %s", expanded_to, expanded_cc); X X#ifdef ALLOW_BCC X if (bcc[0] != '\0') { X strcat(expanded_to, " "); X strcat(expanded_to, expanded_bcc); X } X#endif X X if (access(sendmail, EXECUTE_ACCESS) == 0 X#ifdef SITE_HIDING X && ! is_a_hidden_user(username)) X#else X ) X#endif X if (!sendmail_verbose) X { X X sprintf(very_long_buffer,"( (%s %s %s ; %s %s) & ) < %s", X sendmail, smflags, strip_parens(strip_commas(expanded_to)), X remove, filename2, filename2); X } X else X { X X sprintf(very_long_buffer,"( (%s %s %s ; %s %s) & ) < %s", X sendmail, smflagsv, strip_parens(strip_commas(expanded_to)), X remove, filename2, filename2); X } X else /* oh well, use default mailer... */ X sprintf(very_long_buffer,"( (%s %s ; %s %s) & ) < %s", X mailer, strip_parens(strip_commas(expanded_to)), X remove, filename2, filename2); X } X X fclose(reply); X X if (mail_only) { X printf("sending mail..."); X fflush(stdout); X } X else { X PutLine0(LINES,0,"sending mail..."); X CleartoEOLN(); X } X X system_call(very_long_buffer, SH); X X if (mail_only) X printf("\rmail sent! \n\r"); X else X set_error("Mail sent!"); X X return(need_redraw); X} X Xmail_form(address, subj) Xchar *address, *subj; X{ X /** copy the appropriate variables to the shared space... */ X X strcpy(subject, subj); X strcpy(to, address); X strcpy(expanded_to, address); X X return(mail(FORM, NO, NO, NO)); X} X Xint Xrecall_last_msg(filename, copy_msg, cancelled_msg, already_has_text) Xchar *filename; Xint copy_msg, *cancelled_msg, *already_has_text; X{ X char ch; X X /** If filename exists and we've recently cancelled a message, X the ask if the user wants to use that message instead! This X routine returns TRUE if the user wants to retransmit the last X message, FALSE otherwise... X **/ X X register int retransmit = FALSE; X X if (access(filename, EDIT_ACCESS) == 0 && *cancelled_msg) { X Raw(ON); X CleartoEOLN(); X if (copy_msg) X PutLine1(LINES-1,0,"Recall last kept message instead? (y/n) y%c", X BACKSPACE); X else X PutLine1(LINES-1,0,"Recall last kept message? (y/n) y%c", X BACKSPACE); X fflush(stdout); X ch = ReadCh(); X if (tolower(ch) != 'n') { X Write_to_screen("Yes",0); X retransmit++; X *already_has_text = TRUE; X } X else X Write_to_screen("No",0); X X fflush(stdout); X X *cancelled_msg = 0; X } X X return(retransmit); X} X Xint Xverify_transmission(filename, form_letter) Xchar *filename; Xint *form_letter; X{ X /** Ensure the user wants to send this. This routine returns X the character entered. Modified compliments of Steve Wolf X to add the'dead.letter' feature. X Also added form letter support... X **/ X X FILE *deadfd, *messagefd; X char ch, buffer[LONG_SLEN], fname[SLEN]; X X if (mail_only) { X if (isatty(fileno(stdin))) { Xbatch_reprompt: X printf("\n\rYour options now are:\n\r"); X printf( X "S)end the message, E)dit it again, change/add H)eaders or F)orget it\n\r"); X printf("\n\rWhat is your choice? s%c", BACKSPACE); X fflush(stdin); /* wait for answer! */ X fflush(stdout); X ch = ReadCh(); X ch = tolower(ch); X X if (ch == 'f') { /* forget this message! */ X printf("Forget\n\r\n\r"); X /** try to save it as a dead letter file **/ SHAR_EOF echo "End of part 14" echo "File src/mailmsg2.c is continued in part 15" echo "15" > s2_seq_.tmp exit 0 -- ===================================================================== Sydney S. Weinstein, CDP, CCP Elm Coordinator Datacomp Systems, Inc. Voice: (215) 947-9900 {allegra,bellcore,bpa,vu-vlsi}!dsinc!syd FAX: (215) 938-0235