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