static char rcsid[] = "@(#)$Id: readmsg.c,v 1.7 1997/10/20 20:24:37 wfp5p Exp $"; /******************************************************************************* * The Elm Mail System - $Revision: 1.7 $ $State: Exp $ * * Copyright (c) 1988-1995 USENET Community Trust * Copyright (c) 1986,1987 Dave Taylor ******************************************************************************* * Bug reports, patches, comments, suggestions should be sent to: * * Bill Pemberton, Elm Coordinator * flash@virginia.edu * ******************************************************************************* * $Log: readmsg.c,v $ * Revision 1.7 1997/10/20 20:24:37 wfp5p * Incomingfolders no longer set Magic mode on for all remaining folders. * * Revision 1.6 1996/03/14 17:30:11 wfp5p * Alpha 9 * * Revision 1.5 1995/09/29 17:42:49 wfp5p * Alpha 8 (Chip's big changes) * * Revision 1.4 1995/09/11 15:19:36 wfp5p * Alpha 7 * * Revision 1.3 1995/06/01 13:13:33 wfp5p * Readmsg was fixed to work correctly if called from within elm. From Chip * Rosenthal * * Revision 1.2 1995/04/20 21:02:10 wfp5p * Added the showreply feature and emacs key bindings. * * Revision 1.1.1.1 1995/04/19 20:38:39 wfp5p * Initial import of elm 2.4 PL0 as base for elm 2.5. * ******************************************************************************/ /** This program extracts messages from a mail folder. It is particularly useful when the user is composting a mail response in an external editor. He or she can call this program to pull a copy of the original message (or any other message in the folder) into the editor buffer. One of the first things we do is look for a folder state file. If we are running as a subprocess to Elm this file should tell us what folder is currently being read, the seek offsets to all the messages in the folder, and what message(s) in the folder is/are selected. We will load in all that info and use it for defaults, as applicable. If a state file does not exist, then the default is to show whatever messages the user specifies on the command line. Unless specified otherwise, this would be from the user's incoming mail folder. Even if a state file exists, the user can override the defaults and select a different set of messages to extract, or select an entirely different folder. Messages can be selected on the command line as follows: readmsg [-options] * Selects all messages in the folder. readmsg [-options] pattern ... Selects messsage(s) which match "pattern ...". The selected message will contain the "pattern ..." somewhere within the header or body, and it must be an exact (case sensitive) match. Normally selects only the first match. The "-a" selects all matches. readmsg [-options] sel ... where: sel == a number -- selects that message number sel == $ -- selects last message in folder sel == 0 -- selects last message in folder The undocumented "-I" option is a kludge to deal with an Elm race condition. The problem is that Elm does piping/printing/etc. by running "readmsg|command" and placing the mail message selection into a folder state file. However, if the "command" portion of the pipeline craps out, Elm might regain control before "readmsg" completes. The first thing Elm does is unlink the folder state file. Thus "readmsg" can't figure out what to do -- there is no state file or command line args to select a message. In this case, "readmsg" normally gives a usage diagnostic message. The "-I" option says to ignore this condition and silently terminate. **/ #define INTERN #include "elm_defs.h" #include "s_readmsg.h" /* level of headers to display */ #define HDRSEL_ALL 1 #define HDRSEL_WEED 2 #define HDRSEL_NONE 3 #define metachar(c) (c == '=' || c == '+' || c == '%') /* increment for growing dynamic lists */ #define ALLOC_INCR 256 /* program name for diagnostics */ char *prog; /* * The "folder_idx_list" is a list of seek offsets into the folder, * indexed by message number. The "folder_size" value indicates the * number of messages in the folder, as well as the length of the index. * This index will be used if we need to access messages by message * number. If possible, the index will be loaded from the external state * file, otherwise we will have to make a pass through the entire folder * to build up the index. The index is not needed if we are printing * everything ("*" specified on the command line) or if we are searching * for pattern match. * * The "folder_clen_list" contains the content lengths for the messages. * This information is available *only* if we were spawned from Elm and * the content lengths have been retrieved from the folder state file. * If this information is available, it will prevent truncated messages * due to embedded "From" lines. If content length information is not * available, this list will be left NULL. */ int folder_size; long *folder_idx_list; long *folder_clen_list; /* * Information on a folder message. */ struct mssg_info { long folder_pos; /* start of message seek position */ long content_length; /* message body length, or 0 if unknown */ }; /* * Header weeding information. */ #define DFLT_WEED_LIST "Subject: From: To: Cc: Apparently- Date:" struct weed_header_info { char *hdrname; /* string to compare against header */ int hdrlen; /* characters in "hdrname" */ int hdrskip; /* skip (instead of accept) on match? */ struct weed_header_info *next; }; /* head of linked list of weeded headers */ struct weed_header_info Weedlist; /* is default to accept headers NOT in weed list? */ int weed_dflt_accept = FALSE; /* * local procedures */ void usage_error P_((void)); void malloc_fail_handler P_((const char *, unsigned)); void load_folder_index P_((FILE *)); int print_patmatch_mssg P_((FILE *, const char *, int, int, int)); int print_mssg P_((FILE *, const struct mssg_info *, int, int)); void weed_headers P_((const char *, int, FILE *)); void setup_weed_info P_((const char *)); int get_mssg_info P_((struct mssg_info *, int)); extern char *optarg; /* for parsing the ... */ extern int optind; /* .. starting arguments */ void usage_error() { fprintf(stderr, catgets(elm_msg_cat, ReadmsgSet, ReadmsgUsage, "Usage: %s [-anhp] [-f Folder] [-w weedlist] {MessageNum ... | pattern | *}\n"), prog); exit(1); } main(argc, argv) int argc; char *argv[]; { struct folder_state fstate; /* information from external state file */ char folder_name[SLEN]; /* pathname to the mail folder */ int fstate_valid; /* does "fstate" have valid info? */ int hdr_disp_level; /* amount of headers to show */ int do_page_breaks; /* true to formfeed between messages */ int do_all_matches; /* true to show all mssgs which match pat*/ int ign_no_request; /* terminate if no actions requested */ int exit_status; /* normally zero, set to one on error */ FILE *fp; /* file stream for opened folder */ struct mssg_info minfo; /* information on message to extract */ char *sel_weed_str; /* header weeding specification */ char buf[SLEN], *cp; int i; initialize_common(); prog = argv[0]; folder_name[0] = '\0'; /* no folder specified yet */ folder_size = -1; /* message index not loaded yet */ sel_weed_str = DFLT_WEED_LIST; /* default header weeding spec */ hdr_disp_level = HDRSEL_WEED; /* only display interesting headers */ do_page_breaks = FALSE; /* suppress formfeed between mssgs */ do_all_matches = FALSE; /* only show 1st mssg which matches pat */ ign_no_request = FALSE; /* no action requested is an error */ exit_status = 0; /* will set nonzero on error */ /* see if an external folder state file exists */ if (load_folder_state_file(&fstate) != 0) { fprintf(stderr, catgets(elm_msg_cat, ReadmsgSet, ReadmsgStateFileCorrupt, "%s: Elm folder state file appears to be corrupt!\n"), prog); exit(1); } fstate_valid = (fstate.folder_name != NULL); /* crack the command line */ while ((i = getopt(argc, argv, "af:hnpw:I")) != EOF) { switch (i) { case 'a' : do_all_matches = TRUE; break; case 'f' : strcpy(folder_name, optarg); if (metachar(folder_name[0]) && !expand(folder_name)) { fprintf(stderr, catgets(elm_msg_cat, ReadmsgSet, ReadmsgCannotExpandFolderName, "%s: Cannot expand folder name \"%s\".\n"), prog, folder_name); exit(1); } folder_size = -1; /* zot out index info from extern state file */ break; case 'h' : hdr_disp_level = HDRSEL_ALL; break; case 'n' : hdr_disp_level = HDRSEL_NONE; break; case 'p' : do_page_breaks = TRUE; break; case 'w': hdr_disp_level = HDRSEL_WEED; sel_weed_str = optarg; break; case 'I': ign_no_request = TRUE; break; default: usage_error(); } } /* figure out where the folder is */ if (folder_name[0] == '\0') { if (fstate_valid) strcpy(folder_name, fstate.folder_name); else strcpy(folder_name, incoming_folder); } /* open up the message folder */ if ((fp = fopen(folder_name, "r")) == NULL) { fprintf(stderr, catgets(elm_msg_cat, ReadmsgSet, ReadmsgFolderEmpty, "%s: Folder \"%s\" is empty.\n"), prog, folder_name); exit(0); } /* if this is the folder in the state file then grab the index */ if (fstate_valid && strcmp(folder_name, fstate.folder_name) == 0) { folder_size = fstate.num_mssgs; folder_idx_list = fstate.idx_list; folder_clen_list = fstate.clen_list; } else { fstate_valid = FALSE; } /* process the header weeding specification */ if (hdr_disp_level == HDRSEL_WEED) setup_weed_info(sel_weed_str); /* if no selections on cmd line then show selected mssgs from state file */ if (argc == optind) { if (!fstate_valid) { /* no applicable state file or it didn't select anything */ if (ign_no_request) exit(0); usage_error(); } if (folder_size < 1) { fprintf(stderr, catgets(elm_msg_cat, ReadmsgSet, ReadmsgFolderEmpty, "%s: Folder \"%s\" is empty.\n"), prog, folder_name); exit(0); } for (i = 0 ; i < fstate.num_sel ; ++i) { if (get_mssg_info(&minfo, fstate.sel_list[i]) != 0) exit_status = 1; else if (print_mssg(fp, &minfo, hdr_disp_level, do_page_breaks)!=0) exit_status = 1; } exit(exit_status); } /* see if we are trying to match a pattern */ if (index("0123456789$*", argv[optind][0]) == NULL) { strcpy(buf, argv[optind]); while (++optind < argc) strcat(strcat(buf, " "), argv[optind]); if (print_patmatch_mssg(fp, buf, do_all_matches, hdr_disp_level, do_page_breaks) != 0) exit_status = 1; exit(exit_status); } /* if we do not have an index from the state file then go build one */ if (folder_size < 0) load_folder_index(fp); /* make sure there is something there to look at */ if (folder_size < 1) { fprintf(stderr, catgets(elm_msg_cat, ReadmsgSet, ReadmsgFolderEmpty, "%s: Folder \"%s\" is empty.\n"), prog, folder_name); exit(0); } /* see if all messages should be shown */ if (argc-optind == 1 && strcmp(argv[optind], "*") == 0) { for (i = 1 ; i <= folder_size ; ++i) { if (get_mssg_info(&minfo, i) != 0) exit_status = 1; else if (print_mssg(fp, &minfo, hdr_disp_level, do_page_breaks)!=0) exit_status = 1; } exit(exit_status); } /* print out all the messages specified on the command line */ for ( ; optind < argc ; ++optind) { /* get the message number */ if (strcmp(argv[optind], "$") == 0 || strcmp(argv[optind], "0") == 0) { i = folder_size; } else if ((i = atoi(argv[optind])) == 0) { fprintf(stderr, catgets(elm_msg_cat, ReadmsgSet, ReadmsgIDontUnderstand, "%s: I don't understand what \"%s\" means.\n"), prog, argv[optind]); exit(1); } /*print it */ if (get_mssg_info(&minfo, i) != 0) exit_status = 1; else if (print_mssg(fp, &minfo, hdr_disp_level, do_page_breaks)!=0) exit_status = 1; } exit(exit_status); /*NOTREACHED*/ } /* * Scan through the entire folder and build an index of seek offsets. */ void load_folder_index(fp) FILE *fp; { long offset; int alloc_size; char buf[SLEN]; int buf_len, last_buf_len, was_empty_line; #ifdef MMDF int newheader = 0; #endif /* MMDF */ /* zero out the folder seek offsets index */ folder_size = 0; alloc_size = 0; folder_idx_list = NULL; /* pretend there was an empty line before the first line of the mailbox */ last_buf_len = 1; /* we are being lazy - we don't bother figuring out content lengths */ folder_clen_list = NULL; /* go through the folder a line at a time looking for messages */ rewind(fp); while (offset = ftell(fp), (buf_len = mail_gets(buf, sizeof(buf), fp)) != 0) { was_empty_line = last_buf_len == 1; last_buf_len = buf_len; #ifdef MMDF if ((strcmp(buf, MSG_SEPARATOR) == 0) && (++newheader % 2) != 0) #else if (was_empty_line && strbegConst(buf, "From ") && real_from(buf, (struct header_rec *)NULL)) #endif { if (folder_size >= alloc_size) { alloc_size += ALLOC_INCR; folder_idx_list = (long *) safe_realloc((char *)folder_idx_list, alloc_size*sizeof(long)); } folder_idx_list[folder_size++] = offset; } } } /* * Scan through a folder and print message(s) which match the pattern. */ int print_patmatch_mssg(fp, pat, do_all_matches, hdr_disp_level, do_page_breaks) FILE *fp; const char *pat; int do_all_matches; int hdr_disp_level; int do_page_breaks; { long offset, mssg_idx; struct mssg_info minfo; char buf[VERY_LONG_STRING]; int look_for_pat; int buf_len, last_buf_len, was_empty_line; #ifdef MMDF int newheader = 0; #endif /* MMDF */ /* * This flag ensures that we don't reprint a message if a single * message matches the pattern several times. We turn it on when * we get to the top of a message. */ look_for_pat = FALSE; /* pretend there was an empty line before the first line of the mailbox */ last_buf_len = 1; rewind(fp); while (offset = ftell(fp), (last_buf_len = mail_gets(buf, sizeof(buf), fp)) != 0) { was_empty_line = last_buf_len == 1; last_buf_len = buf_len; #ifdef MMDF if ((strcmp(buf, MSG_SEPARATOR) == 0) && (++newheader % 2) != 0) #else if (was_empty_line && strbegConst(buf, "From ") && real_from(buf, (struct header_rec *)NULL)) #endif { mssg_idx = offset; look_for_pat = TRUE; } if (look_for_pat && strstr(buf, pat) != NULL) { minfo.folder_pos = ftell(fp); minfo.content_length = 0L; /* we don't know this */ if (print_mssg(fp, &minfo, hdr_disp_level, do_page_breaks) != 0) return -1; fseek(fp, minfo.folder_pos, 0); if (!do_all_matches) break; look_for_pat = FALSE; } } return 0; } /* * Print the message at the indicated location. */ int print_mssg(fp, minfo, hdr_disp_level, do_page_breaks) FILE *fp; const struct mssg_info *minfo; int hdr_disp_level; int do_page_breaks; { char buf[SLEN]; int first_line, is_seperator, in_header, buf_len, last_buf_len, newlines, was_empty_line; long curr_offset, stop_offset; static int num_mssgs_listed = 0; in_header = TRUE; first_line = TRUE; stop_offset = 0L; if (num_mssgs_listed++ == 0) ; /* no seperator before first message */ else if (do_page_breaks) putchar('\f'); else puts("\n------------------------------------------------------------------------------\n"); /* move to the beginning of the selected message */ if (fseek(fp, minfo->folder_pos, 0) != 0) { fprintf(stderr, catgets(elm_msg_cat, ReadmsgSet, ReadmsgCannotSeek, "%s: Cannot seek to selected message. [offset=%ld]\n"), prog, minfo->folder_pos); return -1; } /* we will chop off newlines at the end of the message */ newlines = 0; /* pretend there was an empty line before the first line of the mailbox */ last_buf_len = 1; /* print the message a line at a time */ while (curr_offset = ftell(fp), (buf_len = mail_gets(buf, SLEN, fp)) != 0) { was_empty_line = last_buf_len == 1; last_buf_len = buf_len; if (stop_offset == 0L || curr_offset >= stop_offset) { #ifdef MMDF is_seperator = (strcmp(buf, MSG_SEPARATOR) == 0); #else is_seperator = was_empty_line && strbegConst(buf, "From ") && real_from(buf, (struct header_rec *)NULL); #endif } else { is_seperator = FALSE; } /* the first line of the message better be the seperator */ /* next time we encounter the seperator marks the end of the message */ if (first_line) { if (!is_seperator) { fprintf(stderr, catgets(elm_msg_cat, ReadmsgSet, ReadmsgCannotFindStart, "%s: Cannot find start of selected message. [offset=%ld]\n"), prog, minfo->folder_pos); return -1; } first_line = FALSE; } else { if (is_seperator) break; } /* just accumulate newlines */ if (buf[0] == '\n' && buf[1] == '\0') { if (in_header) { if (minfo->content_length > 0) stop_offset = curr_offset + minfo->content_length; in_header = FALSE; } ++newlines; continue; } if (in_header) { switch (hdr_disp_level) { case HDRSEL_NONE: break; case HDRSEL_WEED: weed_headers(buf, buf_len, stdout); break; case HDRSEL_ALL: default: fwrite(buf, 1, buf_len, stdout); break; } } else { while (--newlines >= 0) putchar('\n'); newlines = 0; fwrite(buf, 1, buf_len, stdout); } } /* make sure we didn't do something like seek beyond the */ /* end of the folder and thus never get anything to print */ if (first_line) { fprintf(stderr, catgets(elm_msg_cat, ReadmsgSet, ReadmsgCannotFindStart, "%s: Cannot find start of selected message. [offset=%ld]\n"), prog, minfo->folder_pos); return -1; } return 0; } /* * Weed out and display selected headers. */ void weed_headers(buf, buflen, fp) const char *buf; int buflen; FILE *fp; { struct weed_header_info *w; static int accept_header = FALSE; /* if this is a new header, see if we want to accept it */ if (!isspace(buf[0])) { accept_header = weed_dflt_accept; for (w = Weedlist.next ; w != NULL ; w = w->next) { if (strincmp(buf, w->hdrname, w->hdrlen) == 0) { accept_header = !w->hdrskip; break; } } } /* output accepted headers */ if (accept_header) fwrite(buf, 1, buflen, fp); } /* * Initialize the header weeding info. * * The header weeding info is a linked list of (struct weed_header_info). */ void setup_weed_info(sel_weed_str) const char *sel_weed_str; { struct weed_header_info *w; char *weed_str, *fld, *s; /* make a copy we can scribble on safely */ weed_str = safe_strdup(sel_weed_str); w = &Weedlist; while ((fld = strtok(weed_str, " \t\n,")) != NULL) { weed_str = NULL; /* convert "_" to " " */ for (s = fld ; *s != '\0' ; ++s) { if (*s == '_') *s = ' '; } /* add weeding info to end of list */ w->next = (struct weed_header_info *) safe_malloc(sizeof(struct weed_header_info)); w = w->next; if (w->hdrskip = (*fld == '!')) ++fld; w->hdrname = fld; w->hdrlen = strlen(fld); w->next = NULL; /* * The default weed action is the opposite of the last list entry. * That is, if the list ends in "foo" (an accept action) then the * default is to reject things not in the weed list. If the list * ends in "!foo" (a reject action) then the default is to accept * things not in the weed list. * * If this doesn't make sense to you, consider the following * two example commands...and what you'd like them to do: * * readmsg -w "From: Date: Subject:" * readmsg -w "!From_ !Received: !Status:" * */ weed_dflt_accept = w->hdrskip; } } /* * Retrieve mailbox location information on a specific message. */ int get_mssg_info(minfo_p, mssgno) struct mssg_info *minfo_p; int mssgno; { if (mssgno < 1 || mssgno > folder_size) { fprintf(stderr, catgets(elm_msg_cat, ReadmsgSet, ReadmsgCannotFindMessage, "%s: Cannot find message number %d.\n"), prog, mssgno); return -1; } minfo_p->folder_pos = folder_idx_list[mssgno-1]; minfo_p->content_length = (folder_clen_list == NULL ? 0L : folder_clen_list[mssgno-1]); return 0; }