/*
 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.
*/

/* 
 *
 * Functions for reading the pipe from the MTA */

#include "dbmail.h"

#define THIS_MODULE "delivery"

#define HEADER_BLOCK_SIZE 1024
#define QUERY_SIZE 255
#define MAX_U64_STRINGSIZE 40
#define MAX_COMM_SIZE 512
#define RING_SIZE 6

static int valid_sender(const char *addr) 
{
	int ret = 1;
	char *testaddr;
	testaddr = g_ascii_strdown(addr, -1);
	if (strstr(testaddr, "mailer-daemon@"))
		ret = 0;
	if (strstr(testaddr, "daemon@"))
		ret = 0;
	if (strstr(testaddr, "postmaster@"))
		ret = 0;
	g_free(testaddr);
	return ret;
}

static int parse_and_escape(const char *in, char **out)
{
	InternetAddressList *ialist;
	InternetAddress *ia;

	TRACE(TRACE_DEBUG, "parsing address [%s]", in);
	ialist = internet_address_parse_string(in);
	if (!ialist) {
                TRACE(TRACE_MESSAGE, "unable to parse email address [%s]", in);
                return -1;
	}

        ia = ialist->address;
        if (!ia || ia->type != INTERNET_ADDRESS_NAME) {
		TRACE(TRACE_MESSAGE, "unable to parse email address [%s]", in);
		internet_address_list_destroy(ialist);
		return -1;
	}

	if (! (*out = dm_shellesc(ia->value.addr))) {
		TRACE(TRACE_ERROR, "out of memory calling dm_shellesc");
		internet_address_list_destroy(ialist);
		return -1;
	}

	internet_address_list_destroy(ialist);

	return 0;
}

// Either convert the message struct to a
// string, or send the database rows raw.
enum sendwhat {
	SENDMESSAGE     = 0,
	SENDRAW         = 1
};

// Use the system sendmail binary.
#define SENDMAIL        NULL

/* Sends a message. */
static int send_mail(struct DbmailMessage *message,
		const char *to, const char *from,
		const char *preoutput,
		enum sendwhat sendwhat, char *sendmail_external)
{
	FILE *mailpipe = NULL;
	char *escaped_to = NULL;
	char *escaped_from = NULL;
	char *message_string = NULL;
	char *sendmail_command = NULL;
	field_t sendmail, postmaster;
	int result;

	if (!from || strlen(from) < 1) {
		if (config_get_value("POSTMASTER", "DBMAIL", postmaster) < 0) {
			TRACE(TRACE_MESSAGE, "no config value for POSTMASTER");
		}
		if (strlen(postmaster))
			from = postmaster;
		else
			from = DEFAULT_POSTMASTER;
	}

	if (config_get_value("SENDMAIL", "DBMAIL", sendmail) < 0) {
		TRACE(TRACE_ERROR, "error getting value for SENDMAIL in DBMAIL section of dbmail.conf.");
		return -1;
	}

	if (strlen(sendmail) < 1) {
		TRACE(TRACE_ERROR, "SENDMAIL not set in DBMAIL section of dbmail.conf.");
		return -1;
	}

	if (!sendmail_external) {
		if (parse_and_escape(to, &escaped_to) < 0) {
			TRACE(TRACE_MESSAGE, "could not prepare 'to' address.");
			return 1;
		}
		if (parse_and_escape(from, &escaped_from) < 0) {
			g_free(escaped_to);
			TRACE(TRACE_MESSAGE, "could not prepare 'from' address.");
			return 1;
		}
		sendmail_command = g_strconcat(sendmail, " -f ", escaped_from, " ", escaped_to, NULL);
		g_free(escaped_to);
		g_free(escaped_from);
		if (!sendmail_command) {
			TRACE(TRACE_ERROR, "out of memory calling g_strconcat");
			return -1;
		}
	} else {
		sendmail_command = sendmail_external;
	}

	TRACE(TRACE_INFO, "opening pipe to [%s]", sendmail_command);

	if (!(mailpipe = popen(sendmail_command, "w"))) {
		TRACE(TRACE_ERROR, "could not open pipe to sendmail");
		g_free(sendmail_command);
		return 1;
	}

	TRACE(TRACE_DEBUG, "pipe opened");

	switch (sendwhat) {
	case SENDRAW:
		// This is a hack so forwards can give a From line.
		if (preoutput)
			fprintf(mailpipe, "%s\n", preoutput);
		// This function will dot-stuff the message.
		db_send_message_lines(mailpipe, message->id, -2, 1);
		break;
	case SENDMESSAGE:
		message_string = dbmail_message_to_string(message);
		fprintf(mailpipe, "%s", message_string);
		g_free(message_string);
		break;
	default:
		TRACE(TRACE_ERROR, "invalid sendwhat in call to send_mail: [%d]", sendwhat);
		break;
	}

	result = pclose(mailpipe);
	TRACE(TRACE_DEBUG, "pipe closed");

	/* Adapted from the Linux waitpid 2 man page. */
	if (WIFEXITED(result)) {
		result = WEXITSTATUS(result);
		TRACE(TRACE_INFO, "sendmail exited normally");
	} else if (WIFSIGNALED(result)) {
		result = WTERMSIG(result);
		TRACE(TRACE_INFO, "sendmail was terminated by signal");
	} else if (WIFSTOPPED(result)) {
		result = WSTOPSIG(result);
		TRACE(TRACE_INFO, "sendmail was stopped by signal");
	}

	if (result != 0) {
		TRACE(TRACE_ERROR, "sendmail error return value was [%d]", result);

		if (!sendmail_external)
			g_free(sendmail_command);
		return 1;
	}

	if (!sendmail_external)
		g_free(sendmail_command);
	return 0;
} 

int send_redirect(struct DbmailMessage *message, const char *to, const char *from)
{
	if (!to || !from) {
		TRACE(TRACE_ERROR, "both To and From addresses must be specified");
		return -1;
	}

	return send_mail(message, to, from, NULL, SENDRAW, SENDMAIL);
}

int send_forward_list(struct DbmailMessage *message,
		struct dm_list *targets, const char *from)
{
	int result = 0;
	struct element *target;
	field_t postmaster;

	TRACE(TRACE_INFO, "delivering to [%ld] external addresses", dm_list_length(targets));

	if (!from) {
		if (config_get_value("POSTMASTER", "DBMAIL", postmaster) < 0) {
			TRACE(TRACE_MESSAGE, "no config value for POSTMASTER");
		}
		if (strlen(postmaster))
			from = postmaster;
		else
			from = DEFAULT_POSTMASTER;
	}

	target = dm_list_getstart(targets);
	while (target != NULL) {
		char *to = (char *)target->data;

		if (!to || strlen(to) < 1) {
			TRACE(TRACE_ERROR, "forwarding address is zero length, message not forwarded.");
		} else {
			if (to[0] == '!') {
				// The forward is a command to execute.
				// Prepend an mbox From line.
				char timestr[50];
				time_t td;
				struct tm tm;
				char *fromline;
                        
				time(&td);		/* get time */
				tm = *localtime(&td);	/* get components */
				strftime(timestr, sizeof(timestr), "%a %b %e %H:%M:%S %Y", &tm);
                        
				TRACE(TRACE_DEBUG, "prepending mbox style From header to pipe returnpath: %s", from);
                        
				/* Format: From<space>address<space><space>Date */
				fromline = g_strconcat("From ", from, "  ", timestr, NULL);

				result |= send_mail(message, "", "", fromline, SENDRAW, to+1);
				g_free(fromline);
			} else if (to[0] == '|') {
				// The forward is a command to execute.
				result |= send_mail(message, "", "", NULL, SENDRAW, to+1);

			} else {
				// The forward is an email address.
				result |= send_mail(message, to, from, NULL, SENDRAW, SENDMAIL);
			}
		}

		target = target->nextnode;
	}

	return result;
}

/* 
 * Send an automatic notification.
 */
static int send_notification(struct DbmailMessage *message UNUSED, const char *to)
{
	field_t from = "";
	field_t subject = "";
	int result;

	if (config_get_value("POSTMASTER", "DBMAIL", from) < 0) {
		TRACE(TRACE_MESSAGE, "no config value for POSTMASTER");
	}

	if (config_get_value("AUTO_NOTIFY_SENDER", "DELIVERY", from) < 0) {
		TRACE(TRACE_MESSAGE, "no config value for AUTO_NOTIFY_SENDER");
	}

	if (config_get_value("AUTO_NOTIFY_SUBJECT", "DELIVERY", subject) < 0) {
		TRACE(TRACE_MESSAGE, "no config value for AUTO_NOTIFY_SUBJECT");
	}

	if (strlen(from) < 1)
		g_strlcpy(from, AUTO_NOTIFY_SENDER, FIELDSIZE);

	if (strlen(subject) < 1)
		g_strlcpy(subject, AUTO_NOTIFY_SUBJECT, FIELDSIZE);

	struct DbmailMessage *new_message = dbmail_message_new();
	new_message = dbmail_message_construct(new_message, to, from, subject, "");

	result = send_mail(new_message, to, from, NULL, SENDMESSAGE, SENDMAIL);

	dbmail_message_free(new_message);

	return result;
}

/*
 * Send a vacation message. FIXME: this should provide
 * MIME support, to comply with the Sieve-Vacation spec.
 */
int send_vacation(struct DbmailMessage *message,
		const char *to, const char *from,
		const char *subject, const char *body, const char *handle)
{
	int result;
	const char *x_dbmail_vacation = dbmail_message_get_header(message, "X-Dbmail-Vacation");

	if (x_dbmail_vacation) {
		TRACE(TRACE_MESSAGE, "vacation loop detected [%s]", x_dbmail_vacation);
		return 0;
	}

	struct DbmailMessage *new_message = dbmail_message_new();
	new_message = dbmail_message_construct(new_message, to, from, subject, body);
	dbmail_message_set_header(new_message, "X-DBMail-Vacation", handle);

	result = send_mail(new_message, to, from, NULL, SENDMESSAGE, SENDMAIL);

	dbmail_message_free(new_message);

	return result;
}
	
/*
 * Send an automatic reply.
 */
#define REPLY_DAYS 7
static int send_reply(struct DbmailMessage *message, const char *body)
{
	const char *from, *to, *subject;
	const char *x_dbmail_reply;
	int result;

	x_dbmail_reply = dbmail_message_get_header(message, "X-Dbmail-Reply");
	if (x_dbmail_reply) {
		TRACE(TRACE_MESSAGE, "reply loop detected [%s]", x_dbmail_reply);
		return 0;
	}
	
	subject = dbmail_message_get_header(message, "Subject");

	from = dbmail_message_get_header(message, "Delivered-To");
	if (!from)
		from = message->envelope_recipient->str;
	if (!from)
		from = ""; // send_mail will change this to DEFAULT_POSTMASTER

	to = dbmail_message_get_header(message, "Reply-To");
	if (!to)
		to = dbmail_message_get_header(message, "Return-Path");
	if (!to) {
		TRACE(TRACE_ERROR, "no address to send to");
		return 0;
	}
	if (!valid_sender(to)) {
		TRACE(TRACE_DEBUG, "sender invalid. skip auto-reply.");
		return 0;
	}

	if (db_replycache_validate(to, from, "replycache", REPLY_DAYS) != DM_SUCCESS) {
		TRACE(TRACE_DEBUG, "skip auto-reply");
		return 0;
	}

	char *newsubject = g_strconcat("Re: ", subject, NULL);

	struct DbmailMessage *new_message = dbmail_message_new();
	new_message = dbmail_message_construct(new_message, from, to, newsubject, body);
	dbmail_message_set_header(new_message, "X-DBMail-Reply", from);

	result = send_mail(new_message, to, from, NULL, SENDMESSAGE, SENDMAIL);

	if (result == 0) {
		db_replycache_register(to, from, "replycache");
	}

	g_free(newsubject);
	dbmail_message_free(new_message);

	return result;
}


/* Yeah, RAN. That's Reply And Notify ;-) */
static int execute_auto_ran(struct DbmailMessage *message, u64_t useridnr)
{
	field_t val;
	int do_auto_notify = 0, do_auto_reply = 0;
	char *reply_body = NULL;
	char *notify_address = NULL;

	/* message has been succesfully inserted, perform auto-notification & auto-reply */
	if (config_get_value("AUTO_NOTIFY", "DELIVERY", val) < 0) {
		TRACE(TRACE_ERROR, "error getting config value for AUTO_NOTIFY");
		return -1;
	}

	if (strcasecmp(val, "yes") == 0)
		do_auto_notify = 1;

	if (config_get_value("AUTO_REPLY", "DELIVERY", val) < 0) {
		TRACE(TRACE_ERROR, "error getting config value for AUTO_REPLY");
		return -1;
	}

	if (strcasecmp(val, "yes") == 0)
		do_auto_reply = 1;

	if (do_auto_notify != 0) {
		TRACE(TRACE_DEBUG, "starting auto-notification procedure");

		if (db_get_notify_address(useridnr, &notify_address) != 0)
			TRACE(TRACE_ERROR, "error fetching notification address");
		else {
			if (notify_address == NULL)
				TRACE(TRACE_DEBUG, "no notification address specified, skipping");
			else {
				TRACE(TRACE_DEBUG, "sending notifcation to [%s]", notify_address);
				if (send_notification(message, notify_address) < 0) {
					TRACE(TRACE_ERROR, "error in call to send_notification.");
					g_free(notify_address);
					return -1;
				}
				g_free(notify_address);
			}
		}
	}

	if (do_auto_reply != 0) {
		TRACE(TRACE_DEBUG, "starting auto-reply procedure");

		if (db_get_reply_body(useridnr, &reply_body) != 0)
			TRACE(TRACE_ERROR, "error fetching reply body");
		else {
			if (reply_body == NULL || reply_body[0] == '\0')
				TRACE(TRACE_DEBUG, "no reply body specified, skipping");
			else {
				if (send_reply(message, reply_body) < 0) {
					TRACE(TRACE_ERROR, "error in call to send_reply");
					g_free(reply_body);
					return -1;
				}
				g_free(reply_body);
				
			}
		}
	}

	return 0;
}


int store_message_in_blocks(const char *message, u64_t message_size,
			    u64_t msgidnr) 
{
	u64_t tmp_messageblk_idnr;
	u64_t rest_size = message_size;
	u64_t block_size = 0;
	unsigned block_nr = 0;
	size_t offset;

	while (rest_size > 0) {
		offset = block_nr * READ_BLOCK_SIZE;
		block_size = (rest_size < READ_BLOCK_SIZE ? 
			      rest_size : READ_BLOCK_SIZE);
		rest_size = (rest_size < READ_BLOCK_SIZE ?
			     0 : rest_size - READ_BLOCK_SIZE);
		TRACE(TRACE_DEBUG, "inserting message [%s]", &message[offset]);
		if (db_insert_message_block(&message[offset],
					    block_size, msgidnr,
					    &tmp_messageblk_idnr,0) < 0) {
			TRACE(TRACE_ERROR, "db_insert_message_block() failed");
			return -1;
		}
		
			
		block_nr += 1;
	}

	return 1;
}

/* Here's the real *meat* of this source file!
 *
 * Function: insert_messages()
 * What we get:
 *   - A pointer to the incoming message stream
 *   - The header of the message 
 *   - A list of destination addresses / useridnr's
 *   - The default mailbox to delivery to
 *
 * What we do:
 *   - Read in the rest of the message
 *   - Store the message to the DBMAIL user
 *   - Process the destination addresses into lists:
 *     - Local useridnr's
 *     - External forwards
 *     - No such user bounces
 *   - Store the local useridnr's
 *     - Run the message through each user's sorting rules
 *     - Potentially alter the delivery:
 *       - Different mailbox
 *       - Bounce
 *       - Reply with vacation message
 *       - Forward to another address
 *     - Check the user's quota before delivering
 *       - Do this *after* their sorting rules, since the
 *         sorting rules might not store the message anyways
 *   - Send out the no such user bounces
 *   - Send out the external forwards
 *   - Delete the temporary message from the database
 * What we return:
 *   - 0 on success
 *   - -1 on full failure
 */
int insert_messages(struct DbmailMessage *message, 
		struct dm_list *dsnusers)
{
	u64_t bodysize, rfcsize;
	u64_t tmpid;
	struct element *element;
	u64_t msgsize;

 	delivery_status_t final_dsn;

	/* first start a new database transaction */
	if (db_begin_transaction() < 0) {
		TRACE(TRACE_ERROR, "error executing db_begin_transaction(). aborting delivery...");
		return -1;
	}

	switch (dbmail_message_store(message)) {
	case -1:
		TRACE(TRACE_ERROR, "failed to store temporary message.");
		db_rollback_transaction();
		return -1;
	default:
		TRACE(TRACE_DEBUG, "temporary msgidnr is [%llu]", message->id);
		break;
	}
	/* if committing the transaction fails, a rollback is performed */
	if (db_commit_transaction() < 0) 
		return -1;

	tmpid = message->id; // for later removal

	bodysize = (u64_t)dbmail_message_get_body_size(message, FALSE);
	rfcsize = (u64_t)dbmail_message_get_rfcsize(message);
	msgsize = (u64_t)dbmail_message_get_size(message, FALSE);

	// TODO: Run a Sieve script associated with the internal delivery user.
	// Code would go here, after we've inserted the message blocks but
	// before we've started delivering the message.

	/* Loop through the users list. */
	for (element = dm_list_getstart(dsnusers); element != NULL; element = element->nextnode) {
		
		struct element *userid_elem;
		int has_2 = 0, has_4 = 0, has_5 = 0, has_5_2 = 0;
		
		deliver_to_user_t *delivery = (deliver_to_user_t *) element->data;
		
		/* Each user may have a list of user_idnr's for local
		 * delivery. */
		for (userid_elem = dm_list_getstart(delivery->userids); userid_elem != NULL; userid_elem = userid_elem->nextnode) {
			u64_t useridnr = *(u64_t *) userid_elem->data;
			TRACE(TRACE_DEBUG, "calling sort_and_deliver for useridnr [%llu]", useridnr);

			switch (sort_and_deliver(message,
					delivery->address, useridnr,
					delivery->mailbox, delivery->source)) {
			case DSN_CLASS_OK:
				/* Indicate success. */
				TRACE(TRACE_INFO, "successful sort_and_deliver for useridnr [%llu]", useridnr);
				has_2 = 1;
				break;
			case DSN_CLASS_FAIL:
				/* Indicate permanent failure. */
				TRACE(TRACE_ERROR, "permanent failure sort_and_deliver for useridnr [%llu]", useridnr);
				has_5 = 1;
				break;
			case DSN_CLASS_QUOTA:
			/* Indicate over quota. */
				TRACE(TRACE_MESSAGE, "mailbox over quota, message rejected for useridnr [%llu]", useridnr);
				has_5_2 = 1;
				break;
			case DSN_CLASS_TEMP:
			default:
				/* Assume a temporary failure */
				TRACE(TRACE_ERROR, "unknown temporary failure in sort_and_deliver for useridnr [%llu]", useridnr);
				has_4 = 1;
				break;
			}

			/* Automatic reply and notification */
			if (execute_auto_ran(message, useridnr) < 0) {
				TRACE(TRACE_ERROR, "error in execute_auto_ran(), but continuing delivery normally.");
			}
		} /* from: the useridnr for loop */

		final_dsn.class = dsnuser_worstcase_int(has_2, has_4, has_5, has_5_2);
		switch (final_dsn.class) {
		case DSN_CLASS_OK:
			/* Success. Address related. Valid. */
			set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5);
			break;
		case DSN_CLASS_TEMP:
			/* sort_and_deliver returns TEMP is useridnr is 0, aka,
			 * if nothing was delivered at all, or for any other failures. */	

			/* If there's a problem with the delivery address, but
			 * there are proper forwarding addresses, we're OK. */
			if (dm_list_length(delivery->forwards) > 0) {
				/* Success. Address related. Valid. */
				set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5);
				break;
			}
			/* Fall through to FAIL. */
		case DSN_CLASS_FAIL:
			/* Permanent failure. Address related. Does not exist. */
			set_dsn(&delivery->dsn, DSN_CLASS_FAIL, 1, 1);
			break;
		case DSN_CLASS_QUOTA:
			/* Permanent failure. Mailbox related. Over quota limit. */
			set_dsn(&delivery->dsn, DSN_CLASS_FAIL, 2, 2);
			break;
		case DSN_CLASS_NONE:
			/* Leave the DSN status at whatever dsnuser_resolve set it at. */
			break;
		}

		TRACE(TRACE_DEBUG, "deliver [%ld] messages to external addresses",
			dm_list_length(delivery->forwards));

		/* Each user may also have a list of external forwarding addresses. */
		if (dm_list_length(delivery->forwards) > 0) {

			TRACE(TRACE_DEBUG, "delivering to external addresses");

			/* Forward using the temporary stored message. */
			if (send_forward_list(message, delivery->forwards,
					dbmail_message_get_header(message, "Return-Path")) < 0) {
				/* If forward fails, tell the sender that we're
				 * having a transient error. They'll resend. */
				TRACE(TRACE_MESSAGE, "forwaring failed, reporting transient error.");
				set_dsn(&delivery->dsn, DSN_CLASS_TEMP, 1, 1);
			}
		}
	}			/* from: the delivery for loop */

	/* Always delete the temporary message, even if the delivery failed.
	 * It is the MTA's job to requeue or bounce the message,
	 * and our job to keep a tidy database ;-) */
	if (db_delete_message(tmpid) < 0) 
		TRACE(TRACE_ERROR, "failed to delete temporary message [%llu]", message->id);
	TRACE(TRACE_DEBUG, "temporary message deleted from database. Done.");

	return 0;
}

int send_alert(u64_t user_idnr, char *subject, char *body)
{
	struct DbmailMessage *new_message;
	field_t postmaster;
	char *from;
	int msgflags[IMAP_NFLAGS];

	// Only send each unique alert once a day.
	char *tmp = g_strconcat(subject, body, NULL);
	char *handle = dm_md5((unsigned char *)tmp);
	char *userchar = g_strdup_printf("%llu", user_idnr);
	if (db_replycache_validate(userchar, "send_alert", handle, 1) != DM_SUCCESS) {
		TRACE(TRACE_INFO, "Already sent alert [%s] to user [%llu] today", subject, user_idnr);
		g_free(userchar);
		g_free(handle);
		g_free(tmp);
		return 0;
	} else {
		TRACE(TRACE_INFO, "Sending alert [%s] to user [%llu]", subject, user_idnr);
		db_replycache_register(userchar, "send_alert", handle);
		g_free(userchar);
		g_free(handle);
		g_free(tmp);
	}

	// From the Postmaster.
	if (config_get_value("POSTMASTER", "DBMAIL", postmaster) < 0) {
		TRACE(TRACE_MESSAGE, "no config value for POSTMASTER");
	}
	if (strlen(postmaster))
		from = postmaster;
	else
		from = DEFAULT_POSTMASTER;

	// Set the \Flagged flag.
	memset(msgflags, 0, sizeof(int) * IMAP_NFLAGS);
	msgflags[IMAP_FLAG_FLAGGED] = 1;

	// Get the user's login name.
	char *to = auth_get_userid(user_idnr);

	new_message = dbmail_message_new();
	new_message = dbmail_message_construct(new_message, to, from, subject, body);

	// Pre-insert the message and get a new_message->id
	dbmail_message_store(new_message);
	u64_t tmpid = new_message->id;

	if (sort_deliver_to_mailbox(new_message, user_idnr,
			"INBOX", BOX_BRUTEFORCE, msgflags) != DSN_CLASS_OK) {
		TRACE(TRACE_ERROR, "Unable to deliver alert [%s] to user [%llu]", subject, user_idnr);
	}

	g_free(to);
	db_delete_message(tmpid);
	dbmail_message_free(new_message);

	return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1