/*
 Copyright (C) 1999-2004 IC & S  dbmail@ic-s.nl

 This program is free software; you can redistribute it and/or 
 modify it under the terms of the GNU General Public License 
 as published by the Free Software Foundation; either 
 version 2 of the License, or (at your option) any later 
 version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/* $Id: main.c 1943 2005-12-22 12:30:47Z paul $
 * 
 * main file for dbmail-smtp  */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "dbmail.h"
#include "main.h"
#include "pipe.h"
#include "list.h"
#include "auth.h"
#include "header.h"
#include "dsn.h"
#include <string.h>
#include <stdlib.h>
#include "dm_getopt.h"
/* For exit codes */
#include <sysexits.h>

#define MESSAGEIDSIZE 100
#define NORMAL_DELIVERY 1
#define SPECIAL_DELIVERY 2

#define INDEX_DELIVERY_MODE 1

/* value for the size of the blocks to read from the input stream.
   this can be any value (so 8192 bytes is just a raw guess.. */
#define READ_CHUNK_SIZE 8192
/* syslog */
#define PNAME "dbmail/smtp"

struct list returnpath;		/* returnpath (should aways be just 1 hop) */
struct list mimelist;		/* raw unformatted mimefields and values */
struct list dsnusers;		/* list of deliver_to_user_t structs */
struct list users;		/* list of email addresses in message */
struct element *tmp;

char *configFile = DEFAULT_CONFIG_FILE;

extern db_param_t _db_params;	/* set up database login data */

deliver_to_user_t dsnuser;

//char *header = NULL;
char *deliver_to_header = NULL;
char *deliver_to_mailbox = NULL;
u64_t headersize, headerrfcsize;

/* loudness and assumptions */
int yes_to_all = 0;
int no_to_all = 0;
int verbose = 0;
/* Don't be helpful. */
int quiet = 0;
/* Don't print errors. */
int reallyquiet = 0;

/**
 * read the whole message from the instream
 * \param[in] instream input stream (stdin)
 * \param[out] whole_message pointer to string which will hold the whole message
 * \param[out] whole_message_size will hold size of message
 * \return
 *      - -1 on error
 *      -  0 on success
 */
static int read_whole_message_pipe(FILE *instream, char **whole_message,
				   u64_t *whole_message_size)
{
	char *tmpmessage = NULL;
	size_t read_len = 0;
	size_t totalmem = 0; 
	size_t current_pos = 0;
	char read_buffer[READ_CHUNK_SIZE];
	int error = 0;
	
	while(!feof(instream) && !ferror(instream)) {
		read_len = fread(read_buffer, sizeof(char), READ_CHUNK_SIZE,
				 instream);
		
		if (read_len < READ_CHUNK_SIZE && ferror(instream)) {
			error = 1;
			break;
		}
		if (!(tmpmessage = dm_realloc(tmpmessage, totalmem + read_len))) {
			error = 1;
			break;
		}
		if (!(memcpy((void *) &tmpmessage[current_pos], 
			     (void *) read_buffer, 
			     read_len))) {
			error = 1;
			break;
		}
		totalmem += read_len;
		current_pos += read_len;
	}
	
	if (ferror(instream)) {
		trace(TRACE_ERROR, "%s,%s: error reading from instream",
		      __FILE__, __func__);
		error = 1;
	}
	/* add '\0' to tmpmessage, because fread() will not do that for us.*/
	totalmem++;
	if (!(tmpmessage = dm_realloc(tmpmessage, totalmem))) 
		error = 1;
	else 
		tmpmessage[current_pos] = '\0';
	
	if (error) {
		trace(TRACE_ERROR, "%s,%s: error reading message",
		      __FILE__, __func__);
		dm_free(tmpmessage);
		tmpmessage=NULL;
		return -1;
	}
	
	*whole_message = tmpmessage;
	*whole_message_size = totalmem;
	return 0;
}

int do_showhelp(void) {
	printf("*** dbmail-smtp ***\n");

	printf("Use this program to deliver mail from your MTA or on the command line.\n");
	printf("See the man page for more info. Summary:\n\n");
	printf("     -t [headerfield]   for normal deliveries (default is \"deliver-to\")\n");
	printf("     -d [addresses]     for delivery without using scanner\n");
	printf("     -u [usernames]     for direct delivery to users\n");
	printf("     -m \"mailbox\"       for delivery to a specific mailbox\n");
	printf("     -r return path     for address of bounces and other error reports\n");

	printf("\nCommon options for all DBMail utilities:\n");
	printf("     -f file   specify an alternative config file\n");
	printf("     -q        quietly skip interactive prompts\n"
	       "               use twice to suppress error messages\n");
	printf("     -n        show the intended action but do not perform it, no to all\n");
	printf("     -y        perform all proposed actions, as though yes to all\n");
	printf("     -v        verbose details\n");
	printf("     -V        show the version\n");
	printf("     -h        show this help message\n");

	return 0;
}

int main(int argc, char *argv[])
{
	int exitcode = 0;
	int c, c_prev = 0, usage_error = 0;
	u64_t dummyidx = 0, dummysize = 0;
	char *whole_message = NULL;
	u64_t whole_message_size;
	const char *body;
	u64_t body_size;
	u64_t body_rfcsize;
	char *header = NULL;
	
	openlog(PNAME, LOG_PID, LOG_MAIL);
	

	list_init(&users);
	list_init(&dsnusers);
	list_init(&mimelist);
	list_init(&returnpath);

	/* Check for commandline options.
	 * The initial '-' means that arguments which are not associated
	 * with an immediately preceding option are return with option 
	 * value '1'. We will use this to allow for multiple values to
	 * follow after each of the supported options. */
	while ((c = dm_getopt(argc, argv, "-t::m:u:d:r: f:qnyvVh")) != EOF) {
		/* Received an n-th value following the last option,
		 * so recall the last known option to be used in the switch. */
		if (c == 1)
			c = c_prev;
		c_prev = c;
		/* Do something with this option. */
		switch (c) {
		case 't':
			trace(TRACE_INFO, "main(): using NORMAL_DELIVERY");

			if (dm_optarg) {
				if (deliver_to_header) {
					printf("Only one header field may be specified.\n");
					usage_error = 1;
				} else
					deliver_to_header = dm_optarg;
			} else
				deliver_to_header = "deliver-to";

			break;

		case 'm':
			trace(TRACE_INFO, "main(): using SPECIAL_DELIVERY to mailbox");

			if (deliver_to_mailbox) {
				printf("Only one mailbox may be specified.\n");
				usage_error = 1;
			} else
				deliver_to_mailbox = dm_optarg;

			break;

		case 'r':
			trace(TRACE_INFO, "main(): using RETURN_PATH for bounces");

			/* Add argument onto the returnpath list. */
			if (list_nodeadd(&returnpath, dm_optarg, strlen(dm_optarg) + 1) == 0) {
				trace(TRACE_ERROR,
				      "main(): list_nodeadd reports out of memory"
				      " while adding to returnpath");
				exitcode = EX_TEMPFAIL;
				goto freeall;
			}

			break;

		case 'u':
			trace(TRACE_INFO,
			      "main(): using SPECIAL_DELIVERY to usernames");

			dsnuser_init(&dsnuser);
			dsnuser.address = dm_strdup(dm_optarg);

			/* Add argument onto the users list. */
			if (list_nodeadd
			    (&dsnusers, &dsnuser,
			     sizeof(deliver_to_user_t)) == 0) {
				trace(TRACE_ERROR,
				      "main(): list_nodeadd reports out of memory"
				      " while adding usernames");
				exitcode = EX_TEMPFAIL;
				goto freeall;
			}

			break;

		case 'd':
			trace(TRACE_INFO,
			      "main(): using SPECIAL_DELIVERY to email addresses");

			dsnuser_init(&dsnuser);
			dsnuser.address = dm_strdup(dm_optarg);

			/* Add argument onto the users list. */
			if (list_nodeadd(&dsnusers, &dsnuser, sizeof(deliver_to_user_t)) == 0) {
				trace(TRACE_ERROR,
				      "main(): list_nodeadd reports out of memory"
				      " while adding email addresses");
				exitcode = EX_TEMPFAIL;
				goto freeall;
			}

			break;

		/* Common command line options. */
		case 'f':
			if (dm_optarg && strlen(dm_optarg) > 0)
				configFile = dm_optarg;
			else {
				fprintf(stderr,
					"dbmail-smtp: -f requires a filename\n\n" );
				return 1;
			}
			break;

		case 'v':
			verbose = 1;
			break;

		case 'V':
			/* We must return non-zero in case someone put -V
			 * into the mail server config and thus may lose mail. */
			printf("DBMail: dbmail-smtp\n"
			       "Version: %s\n"
			       "$Revision: 1943 $\n"
			       "Copyright: %s\n", VERSION, COPYRIGHT);
			return 1;

		default:
			usage_error = 1;
			break;
		}

		/* At the end of each round of options, check
		 * to see if there were any errors worth stopping for. */
		if (usage_error) {
			do_showhelp();
			trace(TRACE_DEBUG,
			      "main(): usage error; setting EX_USAGE and aborting");
			exitcode = EX_USAGE;
			goto freeall;
		}
	}

	/* ...or if there weren't any command line arguments at all. */
	if (argc < 2) {
		do_showhelp();
		trace(TRACE_DEBUG,
		      "main(): no arguments; setting EX_USAGE and aborting");
		exitcode = EX_USAGE;
		goto freeall;
	}

	/* Read in the config file; do it after getopt
	 * in case -f config.alt was specified. */
	if (ReadConfig("DBMAIL", configFile) == -1 || ReadConfig("SMTP", configFile) == -1) {
		trace(TRACE_ERROR, "main(): error reading alternate config file [%s]", configFile);
		exitcode = EX_TEMPFAIL;
		goto freeall;

	}
	SetTraceLevel("SMTP");
	GetDBParams(&_db_params);

	if (db_connect() != 0) {
		trace(TRACE_ERROR, "main(): database connection failed");
		exitcode = EX_TEMPFAIL;
		goto freeall;
	}

	if (auth_connect() != 0) {
		trace(TRACE_ERROR,
		      "main(): authentication connection failed");
		exitcode = EX_TEMPFAIL;
		goto freeall;
	}
	/* reset trace-level after auth_connect */
	SetTraceLevel("SMTP");
	
	if (db_check_version() != 0) {
		exitcode = EX_TEMPFAIL;
		goto freeall;
	}
	
	/* read the whole message */
	if (read_whole_message_pipe(stdin, &whole_message, 
				    &whole_message_size) < 0) {
		trace(TRACE_ERROR, "%s,%s: read_whole_message_pipe() failed",
		      __FILE__, __func__);
		exitcode = EX_TEMPFAIL;
		goto freeall;
	}

	/* get pointer to header and to body */
	if (split_message(whole_message, whole_message_size - 1,
			  &header, &headersize, &headerrfcsize, 
			  &body, &body_size, &body_rfcsize) < 0) {
		trace(TRACE_ERROR, "%s,%s splitmessage failed",
		      __FILE__, __func__);
		dm_free(whole_message);
		whole_message=NULL;
		exitcode = EX_TEMPFAIL;
		goto freeall;
	}
	
	if (headersize > READ_BLOCK_SIZE) {
		trace(TRACE_ERROR,
		      "%s,%s: failed to read header because header is "
		      "too big (bigger than READ_BLOCK_SIZE (%llu))",
		      __FILE__, __func__, (u64_t) READ_BLOCK_SIZE);
		exitcode = EX_DATAERR;
		goto freeall;
	}

	/* parse the list and scan for field and content */
	if (mime_readheader(header, &dummyidx, &mimelist, &dummysize) < 0) {
		trace(TRACE_ERROR, "%s,%s: mime_readheader failed", 
				__FILE__, __func__);
		exitcode = EX_TEMPFAIL;
		goto freeall;
	}

	/* parse returnpath from header */
	if (returnpath.total_nodes == 0)
		mail_adr_list("Return-Path", &returnpath, &mimelist);
	if (returnpath.total_nodes == 0)
		mail_adr_list("From", &returnpath, &mimelist);
	if (returnpath.total_nodes == 0)
		trace(TRACE_DEBUG, "%s,%s: no return path found.",
				__FILE__,__func__);

	/* If the NORMAL delivery mode has been selected... */
	if (deliver_to_header != NULL) {
		/* parse for destination addresses */
		trace(TRACE_DEBUG, "main(): scanning for [%s]", deliver_to_header);
		if (mail_adr_list(deliver_to_header, &users, &mimelist) != 0) {
			trace(TRACE_STOP, "%s,%s: no email addresses found (scanned for %s)",
					__FILE__,__func__, deliver_to_header);
			exitcode = EX_NOUSER;
			goto freeall;
		}

		/* Loop through the users list, moving the entries into the dsnusers list. */
		for (tmp = list_getstart(&users); tmp != NULL; tmp = tmp->nextnode) {

			if (! (strlen((char *)tmp->data) > 0))
				continue;

			dsnuser_init(&dsnuser);
			dsnuser.address = dm_strdup((char *) tmp->data);

			if (! list_nodeadd(&dsnusers, &dsnuser, sizeof(deliver_to_user_t))) {
				trace(TRACE_ERROR,"%s,%s: out of memory in list_nodeadd", 
						__FILE__, __func__);
				exitcode = EX_TEMPFAIL;
				goto freeall;
			}
			
		}
	}

	/* If the MAILBOX delivery mode has been selected... */
	if (deliver_to_mailbox != NULL) {
		trace(TRACE_DEBUG, "main(): setting mailbox for all deliveries to [%s]",
		      deliver_to_mailbox);
		
		/* Loop through the dsnusers list, setting the destination mailbox. */
		for (tmp = list_getstart(&dsnusers); tmp != NULL; tmp = tmp->nextnode) 
			((deliver_to_user_t *)tmp->data)->mailbox = dm_strdup(deliver_to_mailbox);
	}

	if (dsnuser_resolve_list(&dsnusers) == -1) {
		trace(TRACE_ERROR, "main(): dsnuser_resolve_list failed");
		/* Most likely a random failure... */
		exitcode = EX_TEMPFAIL;
		goto freeall;
	}

	/* inserting messages into the database */
	if (insert_messages(header, body, headersize, headerrfcsize,
			    body_size, body_rfcsize,
			    &mimelist, &dsnusers, &returnpath) == -1) {
		trace(TRACE_ERROR, "main(): insert_messages failed");
		/* Most likely a random failure... */
		exitcode = EX_TEMPFAIL;
	}

      freeall:			/* Goto's here! */
	
	/* If there wasn't already an EX_TEMPFAIL from insert_messages(),
	 * then see if one of the status flags was marked with an error. */
	if (!exitcode) {
		delivery_status_t final_dsn;

		/* Get one reasonable error code for everyone. */
		final_dsn = dsnuser_worstcase_list(&dsnusers);

		switch (final_dsn.class) {
		case DSN_CLASS_OK:
			exitcode = EX_OK;
			break;
		case DSN_CLASS_TEMP:
			exitcode = EX_TEMPFAIL;
			break;
		case DSN_CLASS_NONE:
		case DSN_CLASS_QUOTA:
		case DSN_CLASS_FAIL:
			/* If we're over-quota, say that,
			 * else it's a generic user error. */
			if (final_dsn.subject == 2)
				exitcode = EX_CANTCREAT;
			else
				exitcode = EX_NOUSER;
			break;
		}
	}

	trace(TRACE_DEBUG, "main(): freeing dsnuser list");
	dsnuser_free_list(&dsnusers);

	trace(TRACE_DEBUG, "main(): freeing all other lists");
	list_freelist(&mimelist.start);
	list_freelist(&returnpath.start);
	list_freelist(&users.start);

	trace(TRACE_DEBUG, "main(): freeing memory blocks");
	if (header != NULL)
		dm_free(header);
	if (whole_message != NULL)
		dm_free(whole_message);

	trace(TRACE_DEBUG, "main(): they're all free. we're done.");

	db_disconnect();
	auth_disconnect();
	config_free();

	trace(TRACE_DEBUG, "main(): exit code is [%d].", exitcode);
	return exitcode;
}


syntax highlighted by Code2HTML, v. 0.9.1