static char rcsid[] = "@(#)$Id: readmsg.c,v 1.9 2006/05/30 16:33:21 hurtta Exp $"; /****************************************************************************** * The Elm (ME+) Mail System - $Revision: 1.9 $ $State: Exp $ * * Modified by: Kari Hurtta * (was hurtta+elm@ozone.FMI.FI) * or Kari Hurtta ****************************************************************************** * The Elm Mail System * * Copyright (c) 1988-1992 USENET Community Trust * Copyright (c) 1986,1987 Dave Taylor *****************************************************************************/ /** 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. **/ #include "def_readmsg.h" #include "s_readmsg.h" #include "readmsg.h" DEBUG_VAR(Debug,__FILE__,"readmsg"); #define metachar(c) (c == '=' || c == '+' || c == '%') /* program name for diagnostics */ char *prog; static void malloc_fail_handler P_((char *proc, unsigned size)); static void malloc_fail_handler(proc, size) char *proc; unsigned size; { /* NOTE: Can't use elm_fprintf (or routines of lib/output.c) because here also malloc may fail, therefore can not use CATGETS macro; And can't use catgets because it may have given incorrent format string from catalog ... */ fprintf(stderr,"%s: Out of memory [malloc of %d bytes failed].\n", prog,size); exit(1); } VOLATILE int pipe_abort; /* set to TRUE if receive SIGPIPE */ static SIGHAND_TYPE pipe_signal P_((int sig)); static SIGHAND_TYPE pipe_signal(sig) int sig; { SIGDPRINT(Debug,2, (&Debug, "*** received SIGPIPE ***\n\n")); pipe_abort = TRUE; /* internal signal ... wheeee! */ signal(SIGPIPE, pipe_signal); } static void usage_error P_((void)); static void usage_error() { lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgUsage, "Usage: %s [-ah] [-np|-r] [-f Folder] {MessageNum ... | pattern | *}\n"), prog); exit(1); } /* * Process interesting mail headers. * * When in WEED mode, this routine should be called with each header line * in sequence, and with a NULL at the end of the header. If the header * is interesting then we will print it out. Continued header lines (i.e. * ones starting with whitespace) are also handled. When the end of the * header is reached, if either the From: or Date: was missing then we * will generate replacements from the From_ header. * * Strict intepretation of RFC822 says { '\\' '\n' } at the end of a header * line should (at least in some cases) continue to the next line, but we * don't handle that. */ static int weed_header_filter P_((header_list_ptr hdr, int flag)); static int weed_header_filter(hdr,flag) header_list_ptr hdr; int flag; { CONST char * hdr_name = give_header_name(hdr->header_name); switch (flag) { case NONE: return 0; case WEED: if (0 == istrcmp(hdr_name, "To") || 0 == istrcmp(hdr_name, "Subject") || 0 == istrcmp(hdr_name, "Cc") || 0 == istrcmp(hdr_name, "Date") || 0 == istrcmp(hdr_name, "From")) return 1; return 0; case ALL: default: return 1; } } #if ANSI_C static type_mismatch_prompt mime_mismatch; #endif static int mime_mismatch P_((mime_t *ptr, int displaying)); static int mime_mismatch(ptr,displaying) mime_t *ptr; int displaying; { return 0; } /* Return 1 on success */ static int print_index_message P_((struct folder_handler * folder, int idx, enum hdr_disp_level hdr_disp_level, int do_raw_output, int print_separator)); static int print_index_message(folder,idx,hdr_disp_level,do_raw_output, print_separator) struct folder_handler * folder; int idx; enum hdr_disp_level hdr_disp_level; int do_raw_output; int print_separator; { struct header_rec *entry = NULL; long content_length; long left; enum message_error err = error_none; header_list_ptr hdrs = NULL; FILE *F = give_message_from_folder(folder,idx, &content_length, &err,print_separator,NULL, &entry); int buf_len; char buf[SLEN]; in_state_t state_in; out_state_t state_out; charset_t charset_vector[2]; in_state_clear(&state_in, STATE_in_file); out_state_clear(&state_out, STATE_out_file); switch (err) { case error_seek: lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgCannotSeek0, "%s: Cannot seek or retrieve to selected message.\n"), prog); break; case error_start: lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgCannotFindStart0, "%s: Cannot find start of selected message.\n"), prog); break; } if (!F) return 0; set_in_state_file(F,&state_in); set_out_state_file(stdout,&state_out); charset_vector[0] = system_charset; charset_vector[1] = NULL; state_out.display_charset = charset_vector; state_out.prefix = NULL; state_out.displaying = 0; hdrs = file_read_headers(F,RHL_MARK_FOLDING); if (hdr_disp_level != NONE) { if (do_raw_output) state_write_raw_headers(&state_out,hdrs, weed_header_filter, hdr_disp_level); else state_write_headers(&state_out,hdrs, weed_header_filter, hdr_disp_level, !(entry -> status & NOHDRENCODING), entry->header_charset); state_printf(&state_out, FRM("\n")); } delete_headers(&hdrs); if (do_raw_output || 0 == (entry->status & MIME_MESSAGE)) { left = content_length; /* print body it is assumed that content_length matches to whole lines .. no partial lines on end ... */ while (left > 0 && (buf_len = mail_gets(buf, SLEN, F)) > 0) { fwrite(buf, 1, buf_len, stdout); left -= buf_len; } } else { if (!entry->mime_parsed) { DPRINT(Debug,5,(&Debug,"mime_parse_routine was not called\n")); mime_parse_routine(NULL,entry,F); } mime_decode(&(entry->mime_rec), &state_in, &state_out,entry->header_charset, entry, mime_mismatch); } in_state_destroy(&state_in); out_state_destroy(&state_out); return 1; } /* Return 1 on success */ static int print_matching_messages P_((struct folder_handler * folder, const char * match, int do_all_matches, enum hdr_disp_level hdr_disp_level, int do_page_breaks, int do_raw_output)); static int print_matching_messages (folder,match,do_all_matches, hdr_disp_level, do_page_breaks, do_raw_output) struct folder_handler * folder; const char * match; int do_all_matches; enum hdr_disp_level hdr_disp_level; int do_page_breaks; int do_raw_output; { int ret = 1; int i; int found_count = 0; int print_separator = 0; if (do_raw_output) print_separator = 1; for (i = 0; i < folder->num_messages; i++) { enum message_error err = error_none; long content_length, left; int buf_len; char buf[SLEN]; int is_seperator = 0; FILE *F; char * env_buffer = NULL; if (pipe_abort) { ret = 0; break; } F = give_message_from_folder(folder,i, &content_length, &err,0,&env_buffer,NULL); if (!F) { goto clean; } if (env_buffer && strstr(env_buffer, match) != NULL) { goto do_print; } /* Headers */ while ((buf_len = mail_gets(buf, SLEN, F)) > 0) { if (1 == buf_len && buf[0] == '\n' || 2 == buf_len && buf[0] == '\r' && buf[1] == '\n') { break; } if (strstr(buf, match) != NULL) { goto do_print; } } /* Body */ left = content_length; while (left > 0 && (buf_len = mail_gets(buf, SLEN, F)) > 0) { if (strstr(buf, match) != NULL) { goto do_print; } left -= buf_len; } if (0) { do_print: if (print_separator) { /* OK */ } else if (found_count++ == 0) ; /* no seperator before first message */ else if (do_page_breaks) putchar(FORMFEED); else puts("\n------------------------------------------------------------------------------\n"); if (! print_index_message(folder,i,hdr_disp_level, do_raw_output, print_separator)) ret = 0; if (!do_all_matches) break; } clean: if (env_buffer) free(env_buffer); } if (!found_count) { lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgPatternNotFound, "%s: Search pattern not found: %s"), prog,match); ret = 0; } return ret; } /* Return 1 on success */ int print_number_message (folder,num, hdr_disp_level, do_page_breaks, do_raw_output) struct folder_handler * folder; int num; enum hdr_disp_level hdr_disp_level; int do_page_breaks; int do_raw_output; { static int num_mssgs_listed = 0; int print_separator = 0; if (do_raw_output) print_separator = 1; if (print_separator) { /* OK */ } else if (num_mssgs_listed++ == 0) ; /* no seperator before first message */ else if (do_page_breaks) putchar(FORMFEED); else puts("\n------------------------------------------------------------------------------\n"); return print_index_message(folder,num-1,hdr_disp_level, do_raw_output, print_separator); } int main P_((int argc, char *argv[])); int main(argc, argv) int argc; char *argv[]; { int is_fstate; int do_all_matches = 0; /* true to show all mssgs which match pat*/ enum hdr_disp_level hdr_disp_level = BADLEV; /* amount of headers to show */ int do_page_breaks = 0; /* true to FORMFEED between messages */ int ign_no_request = 0; /* terminate if no actions requested */ char * folder_name = NULL; /* pathname to the mail folder */ int i; int exit_status = 0; int do_raw_output = 0; /* do raw output */ /* install trap for safe_malloc() failure */ safe_malloc_fail_handler = malloc_fail_handler; #if DEBUG init_debugfile("READMSG"); #endif locale_init(); prog = argv[0]; user_init(); init_mboxlib(); init_defaults(); read_rc_file(0); signal(SIGPIPE, pipe_signal); is_fstate = have_fstate(); DPRINT(Debug,10,(&Debug,"is_fstate=%d\n",is_fstate)); if (is_fstate < 0) { lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgStateFileCorrupt, "%s: Elm folder state file appears to be corrupt!\n"), prog); exit(1); } /* crack the command line */ while ((i = getopt(argc, argv, "anhf:prId:")) != EOF) { switch (i) { case 'a' : do_all_matches = 1; break; case 'd': #if DEBUG set_debugging(optarg); #else lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgArgsIngoringDebug, "Warning: system created without debugging enabled - request ignored\n")); #endif case 'n' : hdr_disp_level = NONE; break; case 'h' : hdr_disp_level = ALL; break; case 'f' : folder_name = optarg; if (metachar(folder_name[0])) { static char buffer[LONG_STRING]; strfcpy(buffer,optarg, sizeof buffer); if (!expand(buffer, sizeof buffer)) { lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgCannotExpandFolderName, "%s: Cannot expand folder name \"%s\".\n"), prog, folder_name); exit(1); } folder_name = buffer; } break; case 'p' : do_page_breaks = 1; break; case 'r': do_raw_output = 1; break; case 'I': ign_no_request = 1; break; default: usage_error(); } } elm_sfprintf(version_buff, sizeof version_buff, FRM("%s PL%s"), VERSION, PATCHLEVEL); #ifdef DEBUG { int d = panic_dprint("\n\ ======================================================\n\ Debug output of the READMSG program (version %s).\n", version_buff); if (d >= 50) { #if 0 printf("WARNING: Debug file may include passwords -- edit it!\n"); panic_dprint("WARNING: Edit manually out sensitive information from that file!"); #endif } } #endif if (do_raw_output && do_page_breaks) usage_error(); if (BADLEV == hdr_disp_level) { if (do_raw_output) hdr_disp_level = ALL; else hdr_disp_level = WEED; } if (do_raw_output && hdr_disp_level != ALL) usage_error(); if (optind == argc) { if (folder_name) usage_error(); if (is_fstate) { if (!process_fstate_print(hdr_disp_level, do_page_breaks, do_raw_output)) exit_status = 1; /* Failure */ } else { if (!ign_no_request) usage_error(); } } else if (optind < argc) { struct folder_handler * folder = NULL; if (folder_name) { /* 1) given file */ folder = open_normal_folder(folder_name); if (!folder) { lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgNoPermRead, "%s: You have no permission to read %s!\n\r"), prog, folder_name); exit (1); } } else if (is_fstate) { /* 2) fstate file */ folder = open_fstate(); if (!folder) { lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgNoPermReadFS, "%s: You have no permission to read folder state.\n\r"), prog); exit (1); } } else { /* 3) default folder/mailbox */ char * default_val = give_dt_estr_as_str(&defaultfile_e, "incoming-mailbox"); if (!default_val) { lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgCannotGetIncomingName, "%s: Cannot figure out name of your incoming mail folder.\n"), prog); exit(1); } folder = open_normal_folder(default_val); if (!folder) { lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgNoPermReadDefault, "%s: You have no permission to read default folder.\n\r"), prog); exit (1); } } if (!parse_folder(folder)) { if (folder_name) lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgFailedParse, "%s: Failed to parse folder \"%s\".\n"), prog, folder_name); else lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgFailedParse0, "%s: Failed to parse folder.\n"), prog); exit_status = 0; goto failure; } if (folder->num_messages < 1) { if (folder_name) lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgFolderEmpty, "%s: Folder \"%s\" is empty.\n"), prog, folder_name); else lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgFolderEmpty0, "%s: Folder is empty.\n"), prog); exit_status = 0; goto failure; } /* see if we are trying to match a pattern */ if (index("0123456789$*", argv[optind][0]) == NULL) { char * buffer = safe_strdup(argv[optind]); for (optind++ ; optind < argc ; ++optind) { buffer = strmcat(buffer," "); buffer = strmcat(buffer,argv[optind]); } if (!print_matching_messages(folder,buffer, do_all_matches, hdr_disp_level, do_page_breaks, do_raw_output)) exit_status = 1; free(buffer); /* see if all messages should be shown */ } else if (argc-optind == 1 && strcmp(argv[optind], "*") == 0) { int i; int print_separator = 0; if (do_page_breaks) print_separator = 1; for (i = 0; i < folder->num_messages; i++) { if (pipe_abort) { exit_status = 1; break; } if (i > 0) { if (print_separator) { /* OK */ } else if (do_page_breaks) putchar(FORMFEED); else puts("\n------------------------------------------------------------------------------\n"); } if (!print_index_message(folder,i, hdr_disp_level, do_raw_output, print_separator)) exit_status = 1; } /* print out all the messages specified on the command line */ } else for ( ; optind < argc ; ++optind) { int num; if (strcmp(argv[optind], "$") == 0 || strcmp(argv[optind], "0") == 0) num = folder->num_messages; else if ((num = atoi(argv[optind])) == 0) { lib_error(CATGETS(elm_msg_cat, ReadmsgSet, ReadmsgIDontUnderstand, "%s: I don't understand what \"%s\" means.\n"), prog, argv[optind]); exit_status = 1; goto failure; } if (pipe_abort) { exit_status = 1; break; } if (!print_number_message(folder,num, hdr_disp_level, do_page_breaks, do_raw_output)) exit_status = 1; } failure: if (folder) free_folder_handler(&folder); } else { panic("READMSG PANIC",__FILE__,__LINE__,"main", "Bad optind",0); } exit(exit_status); } /* * Local Variables: * mode:c * c-basic-offset:4 * buffer-file-coding-system: iso-8859-1 * End: */