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 <hurtta+elm@posti.FMI.FI> 
 *                           (was hurtta+elm@ozone.FMI.FI)
 *           or  Kari Hurtta <elm@elmme-mailer.org>
 ******************************************************************************
 *  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:
 */


syntax highlighted by Code2HTML, v. 0.9.1