/* * xmail - X window system interface to the mail program * * Copyright 1990,1991,1992 by National Semiconductor Corporation * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation, and that the name of National Semiconductor Corporation not * be used in advertising or publicity pertaining to distribution of the * software without specific, written prior permission. * * NATIONAL SEMICONDUCTOR CORPORATION MAKES NO REPRESENTATIONS ABOUT THE * SUITABILITY OF THIS SOFTWARE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" * WITHOUT EXPRESS OR IMPLIED WARRANTY. NATIONAL SEMICONDUCTOR CORPORATION * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO * EVENT SHALL NATIONAL SEMICONDUCTOR CORPORATION BE LIABLE FOR ANY SPECIAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. * * Author: Michael C. Wagnitz - National Semiconductor Corporation * * * The following software modules were created and are Copyrighted by * National Semiconductor Corporation: * * 1. regerr: * 2. compile_pattern: * 3. match: and * 4. update_times. * * Author: Michael C. Wagnitz - National Semiconductor Corporation * */ #define PARSER #include "global.h" #include "xmailregexp.h" #include #include #include "MailwatchP.h" #if defined(sun) && defined(SVR4) #define USE_REGEXP #endif extern void reset_mailbox(Widget gw, int down); static char MailPrompt[80]; /* overkill on size, but to be safe */ #if defined(USE_REGEXP) #define INIT register char *sp=instring; int sed=1; #define GETC() (*sp++) #define PEEKC() (*sp) #define UNGETC(c) (--sp) #define RETURN(c) return #define ERROR(c) regerr(c) #include int regexp_indx; static char *regexp_error[] = { "","","","","","","","","","","", /* 11 */ "Range endpoint too large", "","","","", /* 16 */ "Bad number", "","","","","","","","", /* 25 */ "``\\digit'' out of range", "","","","","","","","","","", /* 36 */ "Illegal or missing delimiter", "","","","", /* 41 */ "No remembered search string", /* 42 */ "\\(\\) imbalance", /* 43 */ "Too many \\(", /* 44 */ "More than 2 numbers given in \\{\\}", /* 45 */ "} expected after \\", /* 46 */ "First number exceeds second in \\{\\}", "", "", /* 49 */ "[] imbalance", /* 50 */ "Regular expression too long", NULL }; regerr(c) int c; { (void) fprintf(stderr, "xmail: %s (item %d).\n", regexp_error[c], regexp_indx); exit(0); } /* ** @(#)compile_pattern() - compile regular expression patterns in a table. ** A pattern table is an array of pattern records. Each pattern record ** consists of a regular expression, and a buffer for the compiled expression. */ void compile_pattern(patternTable) PatternRecPtr patternTable; { String bp; PatternRecPtr cp; for (regexp_indx=0, cp=patternTable; cp->pat||cp->buf; regexp_indx++, cp++) { if (! cp->pat) continue; /* skip over grouping separations */ bp = (String) XtMalloc((unsigned) BUFSIZ); (void) compile(cp->pat, bp, &bp[BUFSIZ], '\0'); cp->buf = bp; } } #endif /* ** @(#)match() - string against regular expressions in pattern table. */ int match(PatternRecPtr patternTable, char *string) { PatternRecPtr cp; char *bp; int n, id; if (strcmp(string, "") == 0) return -1; for (id = 0, cp = patternTable; cp->pat || cp->buf; cp++) { if (! cp->pat) { id++; continue; } #if defined(USE_REGEXP) if (advance(string, cp->buf)) return (id); #else if (bp = (char *)re_comp(cp->pat)) { (void) fprintf(stderr, "xmail: re_comp: %s\n", bp); return -1; } else { if ((n = re_exec(string)) == 1) return (id); else if (n < 0) { (void) fprintf(stderr, "xmail: re_exec: internal error\n"); return -1; } } #endif } return (-1); } /* ** @(#)parser_init()- compile command and output pattern tables. */ void parser_init(void) { #if defined(USE_REGEXP) (void) compile_pattern(command_pattern); (void) compile_pattern(output_pattern); #else ; #endif } /* ** @(#)update_times - update previous folder's timestamps, so SetNewness() ** will restore no unread message flag for that folder. */ static update_times(void) { MailwatchWidget mb = (MailwatchWidget) XtNameToWidget(toplevel,"icon.mailbox"); char *p, *q, tmp[BUFSIZ]; /* ** we need to strip the foldername from the previous title-bar contents */ p = strrchr(lastFolder, '"'); q = strchr(lastFolder, '"'); if (p && q && p != q) { *p = '\0'; if (strcmp(++q, mb->mailbox.filename) != 0) { /* not system folder */ if (*q == '+') { /* the majority of the times */ initfoldir(); (void) sprintf(tmp, "%s%s", foldir, ++q); q = tmp; } (void) utime(q, (struct utimbuf *) 0); } } } /* end - update_times */ /* ** @(#)Reissue_Is_Valid - true/false reissue of Copy/Save to author was valid */ int Reissue_Is_Valid(void) { int n; String p, h, strchr(const char *, int), strrchr(const char *, int); char buf[BUFSIZ], tmp[BUFSIZ]; if (Command[0] != 'C' && Command[0] != 'S') return(False); SetCursor(WATCH); if (1 == sscanf(Command, "%*s %d", &n)) { Command[0] = (Command[0] == 'C' ? 'c' : 's'); LASTCH(Command) = ' '; } else { n = SelectionNumber(False); sprintf(buf, "%c %d ", Command[0] == 'C' ? 'c' : 's', n); (void) strcpy(Command, buf); } (void) sprintf(buf, "f %d", n); p = QueryMail(buf); buf[0] = '\0'; if (1 != sscanf(p, "> %*d %s", buf)) (void) sscanf(p, " %*d %s", buf); if (strchr(buf, '<')) { if (! sscanf(buf, "%*[^<]<%[^>]>", tmp)) (void) sscanf(buf, "<%[^>]>", tmp); (void) strcpy(buf, tmp); } else if (strchr(buf, '(')) { for (h = buf; *h && strchr(" \t", *h); h++); if (1 != sscanf(h, "%*[^)]) %s", tmp)) (void) sscanf(h, "%s", tmp); (void) strcpy(buf, tmp); } if ((int)strlen(buf)) { if (h = strrchr(buf, '!')) /* UUCP - host![host!]user[@host]... */ bcopy(h + 1, buf, (int)strlen(h)); if (h = strchr(buf, '@')) { /* ARPA address - user[%host]@host */ *h = '\0'; if (h = strchr(buf, '%')) *h = '\0'; if (h = strrchr(buf, ':')) /* DECNet form - host::user%host@host */ bcopy(h + 1, buf, (int)strlen(h)); if (strchr("'\"", buf[0])) /* or - ["']user[@host]["']%host@host */ bcopy(buf + 1, buf, (int)strlen(buf)); } } if (h = GetMailEnv("outfolder")) { XtFree((String) h); (void) strcat(Command, "+"); } (void) strcat(Command, buf); (void) strcat(Command, "\n"); writeMail(Command); return (True); } /* end - Reissue_Is_Valid */ #include "unset.h" char *checkOptions( char *optionStr ) { char *holdPtr, *linePtr, *eolPtr; int x, y; static char optionsToUnset[128]; /* handle a NULL optionStr by returning NULL */ if (optionStr == NULL) return( NULL ); holdPtr = optionStr; while (holdPtr) { /* get a line, by getting the * address of the carriage return */ linePtr = holdPtr; eolPtr = strchr(holdPtr,(int)'\n'); /* advance our holdPtr to one character after * the carriage return */ if (eolPtr) holdPtr = eolPtr + 1; else { /* if its NULL, then its got to be the last line in the * string and we'll set holdPtr to be NULL to break * out of the loop */ eolPtr = strchr(holdPtr,(int)'\0'); /* Whoa! What's going on here .. its got to be garbage to * get out of the loop */ if (!eolPtr) break; holdPtr = NULL; } /* this is an optimization to get out of checking the * options if we've already found them all */ for (x = 0, y = numOptions ; x < numOptions; x++ ) { if (!options[x].found) { if (strncmp( linePtr, options[x].name, options[x].len) == 0) { options[x].found = TRUE; y--; break; } } } /* end for */ if (!y) break; } /* end while */ /* now we'll build a string of the options to unset comprised * of the ones we found above */ holdPtr = optionsToUnset; for (x = 0 ; x < numOptions; x++ ) { if ( options[x].found ) { sprintf( holdPtr, " %s", options[x].name ); holdPtr += options[x].len + 1; *holdPtr = '\0'; } } /* if you didn't find any, then return NULL */ if (holdPtr == optionsToUnset) return(NULL); return( optionsToUnset ); } /* ** @(#)parse() - command and output and call appropriate handler */ void parse(String msg) { static Boolean first_time = True; int j, k, msgnum; String c, s; char tmp[128], *toUnset, *endOfLine; Widget button, icon, iw; j = match(command_pattern, Command); switch (j) { /* ** If error on startup, mail has terminated connection. Remove our input ** handler, close the file from our end, and indicate that no connection ** exists. Otherwise, establish conversation requirements and display the ** current message. If *Show_Last: resource is NOT False, show latest, if ** none are newer. We delay setting the icon event handler until now, to ** ensure mail startup completed before being interrupted by a map event, ** for the case where we have been started iconic. */ case XM_C_START : /* ** test to ensure we are not running binmail by mistake */ if (((int)strlen(msg) >= 17) && (! strcmp("unknown option N\n", &msg[(int)strlen(msg)-17]))) { Bell(msg); XCloseDisplay(XtDisplay(toplevel)); (void) fprintf(stderr, "\007xmail: The specified (or default) mail handler is not supported.\n"); _exit(0); } if (XM_O_BELL == match(output_pattern, msg)) { if (mailpid) { XtRemoveInput(mailInputId); (void) close(mail_fd); mailpid = 0; MailPrompt[0] = '\0'; } if (strncmp(msg, "No mail for ", 12) == 0) Bell("No mail in your system mailbox\n"); else Bell(msg); (void) UpdateTitleBar("No current folder"); *msg = '\0'; } else { /* ** To prevent a race condition (?) when starting the ** application iconic, (which would cause this loop to ** repeat multiple times), test the value of the mail ** environment variable ``screen''. If its value is ** 10,000 then we're done. */ if (c = GetMailEnv("screen")) { j = (strcmp("10000", c) != 0) ? True : False; XtFree((String) c); } else j = True; if (j) { /* if not yet set to 10000... */ (void) strcpy(tmp, "set screen=10000 toplines=100 "); /* default action for xmail is hold (ala mailtool) */ if (c = GetMailrc("nohold")) XtFree((String) c); else (void) strcat(tmp, "hold"); msg = QueryMail(tmp); /* let's see what options are set. If we * find crt, replyall or cmd in the list, then * we will unset them */ *msg = '\0'; (void) strcpy(tmp, "set"); msg = QueryMail(tmp); toUnset = checkOptions( msg ); if (toUnset) { sprintf(tmp,"unset %s",toUnset ); msg = QueryMail(tmp); } *msg = '\0'; if (msgnum = file_handler()) { (void) sprintf(tmp, "%d", msgnum); msg = QueryMail(tmp); } Bell(""); /* reset any worthy-ness flag */ Bell(Default_Status_Info); } else *msg = '\0'; } if (first_time) { /* only need to do this once */ Waiting = FALSE; first_time = False; icon = XtNameToWidget(toplevel, "icon"); XtAddEventHandler(icon, StructureNotifyMask, False, icon_handler, (XtPointer)NULL); if (In_Bogus_Mail_File) { (void) sprintf(tmp, "%s+", tmpName); (void) unlink(tmp); *msg = '\0'; /* ignore 'skipping' message */ } } break; /* ** In response to normal or change folder commands, test output. If an ** error, display message in status window, and ring bell unless command ** was save, Save to author, or undelete. If Save or Copy results in an ** error, and the error response begins with 'Unknown', then this is not ** Sun mail, and we should try and compensate for these missing functions. ** If our current mail message folder is the bogus folder, erase any text ** and index and note state. If we have text of next mail message, tell ** index_handler to mark its number, else retrieve the appropriate message ** text (which also marks its number). If *Show_Last: resource is NOT False, ** file cmd shows latest, if none new. */ case XM_C_EXEC : case XM_C_FILE : /* This crass hack speeds up the error message search * by taking advantage of the observation that the * error messages occur on the first line of output. * So we put in a temporary null to prevent the FSM * from searching the whole message. */ if (endOfLine = strchr(msg, '\n')) { *endOfLine = 0; j = match(output_pattern, msg); *endOfLine = '\n'; } else j = match(output_pattern, msg); switch (j) { case XM_O_BELL: if (strchr("SsCcw", Command[0]) != NULL) { if (strncmp(msg, "Unknown ", 8) == 0 && Reissue_Is_Valid()) { *msg = '\0'; break; } if (isdigit(msg[(int)strlen(msg) - 2])) LASTCH(msg) = '\0'; else Command[0] = '\0'; } if (strncmp(msg, tmpName, (int)strlen(tmpName)) == 0) Bell("No mail in your system mailbox"); else { Bell(msg); *msg = '\0'; } /* ** Save our current message number, because asking about our 'file' status ** will reset mail's idea of the 'current message' count to one, regardless. */ msgnum = SelectionNumber(False); /* ** Now ask for 'file' status to determine if we are still caught in our 'bogus' ** mailfile, in order to generate a more meaningful title-bar status message. */ msg = QueryMail("file"); if (strncmp(&msg[1], tmpName, (int)strlen(tmpName)) == 0) { (void) UpdateTitleBar("No current folder"); iw = XtNameToWidget(toplevel, "topBox"); writeTo(XtNameToWidget(iw, "indexWindow"), " ", REPLACE); writeText(" "); } else { (void) UpdateTitleBar(msg); /* ** If not in our bogus mail folder, reset the current message ** number in Mail by again pointing at that numbered message. */ (void) sprintf(tmp, "f %d", msgnum); msg = QueryMail(tmp); if (Command[0] != '\0') if (strchr("Ssw", Command[0])) markIndex(">S"); } *msg = '\0'; break; case XM_O_EXEC: writeText(msg); if (strchr("-np", Command[0])) msgnum = index_handler(0, 0); else markIndex("> "); *msg = '\0'; break; case XM_O_FILE: c = strchr(msg, '\n'); if ((int)(c - msg) < (int)strlen(msg) - 1) { *c = '\0'; /* don't bell this */ Bell(""); /* clear worthy flag */ Bell(msg); } *msg = '\0'; msgnum = file_handler(); update_times(); /* now that we're out */ if (msgnum) { (void) sprintf(tmp, "%d", msgnum); msg = QueryMail(tmp); } break; } break; /* ** When deleting a message, simply mark that index line with a capital 'D'. ** If un-deleting, mark that index line with a blank ' '. If no current ** index, restore it. If autoprinting, mail will include text of next msg. */ case XM_C_DELETE : if (XM_O_BELL == match(output_pattern, msg)) { Bell(msg); *msg = '\0'; } else { writeText(msg); msgnum = SelectionNumber(False); if (Command[0] == 'd') { if (msgnum) { markIndex("D"); msgnum = index_handler(msgnum + 1, 0); } } else { /* if we are 'undeleting' */ c = QueryMail("="); (void) sscanf(c, "%d", &msgnum); iw = XtNameToWidget(toplevel, "topBox.indexWindow"); if (TextGetLastPos(iw) < (XawTextPosition) 4) { c = QueryMail("h"); writeTo(iw, c, REPLACE); } msgnum = index_handler(msgnum, 1); } c = QueryMail("file"); /* resets current msg to 1 */ (void) UpdateTitleBar(c); if (msgnum == 0) /* if this was the last msg */ writeText(" "); /* in folder, erase its text */ else { /* ** reset mail's idea of what is the current message */ (void) sprintf(tmp, "f %d", msgnum); msg = QueryMail(tmp); } *msg = '\0'; } break; /* ** In response to a request to view new mail, first reset the mailbox flag. ** If mail came back with an error, complain to the user. Otherwise, clear ** the Newmail command button highlighting. Then display any new mail text. */ case XM_C_NEWMAIL: button = XtNameToWidget(toplevel, "topBox.commandPanel.Folder"); UnsetNewmail(button, NULL, NULL); if (XM_O_BELL == match(output_pattern, msg)) { if (strncmp(msg, "No mail for ", 12) == 0) { Bell("No mail in your system mailbox\n"); } else { Bell(msg); } *msg = '\0'; } else { Bell(""); /* reset any worthy-ness flag */ Bell(Default_Status_Info); if (*msg) { s = msg; c = strchr(msg, '\n'); if ((c - s) + 1 < (int)strlen(msg)) { *c = '\0'; /* don't bell this */ Bell(msg); } } msgnum = file_handler(); update_times(); /* we're out of prev. folder */ *msg = '\0'; if (msgnum) { (void) sprintf(tmp, "%d", msgnum); msg = QueryMail(tmp); } } break; default: j = match(output_pattern, msg); switch (j) { case XM_O_BELL: Bell(msg); *msg = '\0'; break; /* ** If output is from the print command, display a status message */ case XM_O_PRINT: (void) sscanf(&Command[2], "%d", &j); c = strrchr(msg, '/'); (void) sscanf(c, "/%d", &k); (void) sprintf(tmp, "Message %d sent to printer -- %d bytes\n", j, k); Bell(tmp); *msg = '\0'; break; /* ** If we didn't specifically ask for it, and its not an error, just toss it. ** Preserve post processing moved here, in case we weren't allowed to do it. */ default: if (strncmp(Command, "preserve", 8) == 0) markIndex("P"); *msg = '\0'; break; } break; } /* ** If any text remains, display it in the bottom (text) window. */ if (*msg) { for (j = (int)strlen(msg)-1; j > 1 && msg[j] == '\n' && msg[j-1] == '\n'; j--); msg[++j] = '\0'; /* drop all but the last newline */ writeText(msg); *msg = '\0'; } SetCursor(NORMAL); } /* parse */ #define FILEBUF 8192 /* ** @(#)QueryMail() - Sends a command and returns corresponding output. ** If called by the Xt input procedure readMail, no command is included. ** In an attempt to eliminate the occasional segmentation violation in ** either malloc or free, we no longer free this buffer each time it is ** used. Instead we simply allow it to grow to the maximum size of a ** read ever performed on the mail pipe. */ char * QueryMail(char *cmd) { static int outputsize = 0; int j, size, length = 0; static String output; String temp; char s[FILEBUF]; char *endOfLine; if (MailPrompt[0] != '\0') length = (int)strlen(MailPrompt); /* ** allocate one block to 'output' to begin with */ if (outputsize <= 0) { outputsize = FILEBUF; output = (String) XtMalloc((unsigned) outputsize); } output[0] = '\0'; if (! mailpid) Bell("No current mail program connection\n"); else { if (*cmd) { /* allow us to be called by readMail */ if (LASTCH(cmd) != '\n') (void) sprintf(s, "%s\n", cmd); else (void) strcpy(s, cmd); writeMail(s); } for (;;) { if ((size = read(mail_fd, s, FILEBUF)) < 1) { /* EOF or an error? */ if (! *output) { /* For some reason Mail has gone away */ (void) strcpy(output, "No current mail service connection\n"); if (mailpid) { XtRemoveInput(mailInputId); (void) close(mail_fd); mailpid = 0; MailPrompt[0] = '\0'; (void) strcpy(Command, "Start"); /* Focus our errors */ writeText(" "); /* erase any current message display */ writeTo(XtNameToWidget(toplevel, "topBox.indexWindow"), " ", REPLACE); } } break; } if (size < FILEBUF) s[size] = '\0'; if ((int)strlen(output) + size >= outputsize) { outputsize += FILEBUF; temp = (String) XtRealloc(output, outputsize); output = temp; } (void) strcat(output, s); if (strcmp(Command, "Start") == 0) { /* ** Check for no message response before we try to catch the prompt. */ /* Once again, the crass hack for grepping just the first line. */ if (endOfLine = strchr(output,'\n')) { *endOfLine = 0; j = match(output_pattern, output); *endOfLine = '\n'; } else j = match(output_pattern, output); if (XM_O_BELL == j) break; /* ** If we have no prompt and are just starting, get the prompt string. ** This makes the GROSS assumption that we will have gotten it here. */ if (*output && ! *MailPrompt) { if ((temp = strrchr(output, '\n')) != NULL) (void) strcpy(MailPrompt, temp + 1); else (void) strcpy(MailPrompt, output); length = (int)strlen(MailPrompt); } } j = (int)strlen(output) - length; if (j < 0) j = 0; /* no references before zero */ if (*MailPrompt && strcmp(&output[j], MailPrompt) == 0) { output[j] = '\0'; /* Drop prompt from end of text */ break; /* and return from read routine */ } } if (*cmd) LASTCH(output) = '\0'; /* drop newline for normal queries */ } return(output); } /* QueryMail */