/* 

 Copyright (C) 1999-2004 Aaron Stone aaron at serendipity dot cx

 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 running user defined sorting rules
 * on a message in the temporary store, usually
 * just delivering the message to the user's INBOX
 * ...unless they have fancy rules defined, that is :-)
 * 
 */

#include "dbmail.h"

#define THIS_MODULE "sort"

/* Used by us to keep track of libSieve. */
struct sort_context {
	char *s_buf;
	char *script;
	u64_t user_idnr;
	struct DbmailMessage *message;
	struct sort_result *result;
	struct dm_list freelist;
};

/* Returned opaquely as type sort_result_t. */
struct sort_result {
	int cancelkeep;
	const char *mailbox;
	int reject;
	GString *rejectmsg;
	int error_runtime;
	int error_parse;
	GString *errormsg;
};

/* [DELIVERY] SIEVE_* settings in dbmail.conf */
struct sort_sieve_config {
	int vacation;
	int notify;
	int debug;
};

static void sort_sieve_get_config(struct sort_sieve_config *sieve_config)
{
	field_t val;

	assert(sieve_config != NULL);

	sieve_config->vacation = 0;
	sieve_config->notify = 0;
	sieve_config->debug = 0;

	config_get_value("SIEVE_VACATION", "DELIVERY", val);
	if (strcasecmp(val, "yes") == 0) {
		sieve_config->vacation = 1;
	}

	config_get_value("SIEVE_NOTIFY", "DELIVERY", val);
	if (strcasecmp(val, "yes") == 0) {
		sieve_config->notify= 1;
	}

	config_get_value("SIEVE_DEBUG", "DELIVERY", val);
	if (strcasecmp(val, "yes") == 0) {
		sieve_config->debug = 1;
	}
}


/* SIEVE CALLBACKS */

/*
From http://www.ietf.org/internet-drafts/draft-ietf-sieve-vacation-06.txt

   Usage:   vacation [":days" number] [":subject" string]
                     [":from" string] [":addresses" string-list]
                     [":mime"] [":handle" string] <reason: string>

   The parameters that an implementation needs to know about are:
   days, subject, from, mime, handle, reason. Addresses is used
   internally by libSieve implementation to comply with RFCs.

We need to make sure to respect the implementation requirements.
*/
int sort_vacation(sieve2_context_t *s, void *my)
{
	struct sort_context *m = (struct sort_context *)my;
	const char *message, *subject, *fromaddr, *handle;
	const char *rc_to, *rc_from, *rc_handle;
	char *md5_handle = NULL;
	int days, mime;

	days = sieve2_getvalue_int(s, "days");
	mime = sieve2_getvalue_int(s, "mime"); // mime: 1 if message is mime coded. FIXME.
	message = sieve2_getvalue_string(s, "message");
	subject = sieve2_getvalue_string(s, "subject");
	fromaddr = sieve2_getvalue_string(s, "fromaddr"); // From: specified by the script.
	handle = sieve2_getvalue_string(s, "hash");

	/* Default to a week, upper limit of a month.
	 * This is our only loop prevention mechanism! The value must be
	 * greater than 0, else the replycache code will always indicate
	 * that we haven't seen anything since 0 days ago... */
	if (days == 0) days = 7;
	if (days < 1) days = 1;
	if (days > 30) days = 30;

	if (handle) {
		rc_handle = handle;
	} else {
		char *tmp;
		tmp = g_strconcat(subject, message, NULL);
		rc_handle = md5_handle = dm_md5((const unsigned char * const) tmp);
		g_free(tmp);
	}

	// FIXME: should be validated as a user might try
	// to forge an address from their script.
	rc_from = fromaddr;
	if (!rc_from)
		rc_from = dbmail_message_get_header(m->message, "Delivered-To");
	if (!rc_from)
		rc_from = m->message->envelope_recipient->str;

	rc_to = dbmail_message_get_header(m->message, "Reply-To");
	if (!rc_to)
		rc_to = dbmail_message_get_header(m->message, "Return-Path");

	if (db_replycache_validate(rc_to, rc_from, rc_handle, days) == DM_SUCCESS) {
		if (send_vacation(m->message, rc_to, rc_from, subject, message, rc_handle) == 0)
			db_replycache_register(rc_to, rc_from, rc_handle);
		TRACE(TRACE_INFO, "Sending vacation to [%s] from [%s] handle [%s] repeat days [%d]",
			rc_to, rc_from, rc_handle, days);
	} else {
		TRACE(TRACE_INFO, "Vacation suppressed to [%s] from [%s] handle [%s] repeat days [%d]",
			rc_to, rc_from, rc_handle, days);
	}

	if (md5_handle)
		g_free(md5_handle);

	return SIEVE2_OK;
}

/*
From http://www.ietf.org/internet-drafts/draft-ietf-sieve-notify-07.txt

   Usage:  notify [":from" string] [":importance" <"1" / "2" / "3">]
                  [":options" string-list] [":message" string] <method: string>
*/
int sort_notify(sieve2_context_t *s, void *my)
{
	struct sort_context *m = (struct sort_context *)my;
	const char *message, *fromaddr, *method;
	const char *rc_to, *rc_from;
	int importance;
	char * const * options;

	fromaddr = sieve2_getvalue_string(s, "fromaddr");
	method = sieve2_getvalue_string(s, "method");
	message = sieve2_getvalue_string(s, "message");
	importance = sieve2_getvalue_int(s, "importance");
	options = sieve2_getvalue_stringlist(s, "options");

	// FIXME: should be validated as a user might try
	// to forge an address from their script.
	rc_from = fromaddr;
	if (!rc_from)
		rc_from = dbmail_message_get_header(m->message, "Delivered-To");
	if (!rc_from)
		rc_from = m->message->envelope_recipient->str;

	rc_to = dbmail_message_get_header(m->message, "Reply-To");
	if (!rc_to)
		rc_to = dbmail_message_get_header(m->message, "Return-Path");

//	send_notification(m->message, rc_to, rc_from, method, message);

	TRACE(TRACE_INFO, "Notification from [%s] to [%s] was not sent as notify is not supported in this release.", rc_from, rc_to);

	return SIEVE2_OK;
}

int sort_redirect(sieve2_context_t *s, void *my)
{
	struct sort_context *m = (struct sort_context *)my;
	const char *to;
	const char *from;

	to = sieve2_getvalue_string(s, "address");

	TRACE(TRACE_INFO, "Action is REDIRECT: REDIRECT destination is [%s].",
		to);

	/* According to a clarification from the ietf-mta-filter mailing list,
	 * the redirect is supposed to be absolutely transparent: the envelope
	 * sender is the original envelope sender, with only the envelope
	 * recipient changed. As a fallback, we'll use the redirecting user. */
	from = dbmail_message_get_header(m->message, "Return-Path");
	if (!from)
		from = m->message->envelope_recipient->str;

	if (send_redirect(m->message, to, from) != 0) {
		return SIEVE2_ERROR_FAIL;
	}

	m->result->cancelkeep = 1;
	return SIEVE2_OK;
}

int sort_reject(sieve2_context_t *s, void *my)
{
	struct sort_context *m = (struct sort_context *)my;
	const char *message;

	message = sieve2_getvalue_string(s, "message");

	TRACE(TRACE_INFO, "Action is REJECT: REJECT message is [%s].", message);

	m->result->rejectmsg = g_string_new(message);

	/* Reject also discards. */
	m->result->cancelkeep = 1;
	m->result->reject = 1;
	return SIEVE2_OK;
}

int sort_discard(sieve2_context_t *s UNUSED, void *my)
{
	struct sort_context *m = (struct sort_context *)my;

	TRACE(TRACE_INFO, "Action is DISCARD.");

	m->result->cancelkeep = 1;
	return SIEVE2_OK;
}

int sort_fileinto(sieve2_context_t *s, void *my)
{
	struct sort_context *m = (struct sort_context *)my;
	extern const char * imap_flag_desc[];
	char * const * flags;
	const char * mailbox;
	int msgflags[IMAP_NFLAGS];
	int *has_msgflags = NULL;

	mailbox = sieve2_getvalue_string(s, "mailbox");
	flags = sieve2_getvalue_stringlist(s, "flags");

	/* This condition exists for the KEEP callback. */
	if (! mailbox) {
		mailbox = "INBOX";
	}

	/* If there were any imapflags, set them. */
	if (flags) {
		int i, j;
		memset(msgflags, 0, IMAP_NFLAGS * sizeof(int));

		// Loop through all script/user-specified flags.
		for (i = 0; flags[i]; i++) {
			int supported = 0;
			// Find the ones we support.
			for (j = 0; imap_flag_desc[j] && j < IMAP_NFLAGS; j++) {
				// Point to the first character after the last '\'
				char *flag = strrchr(flags[i], '\\');
				if (g_strcasestr(imap_flag_desc[j], flag ? ++flag : flags[i])) {
					supported = 1;
					msgflags[j] = 1;
					// Only pass msgflags if we found something.
					has_msgflags = msgflags;
				}
			}
			if (supported) {
				TRACE(TRACE_DEBUG, "Adding flag [%s]", flags[i]);
			} else {
				TRACE(TRACE_DEBUG, "Unsupported flag [%s]", flags[i]);
			}
		}
	}

	if (has_msgflags) {
		// TODO: Don't do all this at lower trace levels
		int i;
		char text_msgflags[IMAP_NFLAGS * 10];
		memset(text_msgflags, 0, IMAP_NFLAGS * 10);
		extern const char * imap_flag_desc_escaped[];
		for (i = 0; imap_flag_desc_escaped[i] && i < IMAP_NFLAGS; i++) {
			if (msgflags[i]) {
				g_strlcat(text_msgflags, imap_flag_desc_escaped[i], IMAP_NFLAGS * 10);
				g_strlcat(text_msgflags, " ", IMAP_NFLAGS * 10);
			}
		}
		TRACE(TRACE_INFO, "Action is FILEINTO: mailbox is [%s] flags are [%s]", mailbox, text_msgflags);
	} else {
		TRACE(TRACE_INFO, "Action is FILEINTO: mailbox is [%s] no flags", mailbox);
	}

	/* Don't cancel the keep if there's a problem storing the message. */
	if (sort_deliver_to_mailbox(m->message, m->user_idnr,
			mailbox, BOX_SORTING, has_msgflags) != DSN_CLASS_OK) {
		TRACE(TRACE_ERROR, "Could not file message into mailbox; not cancelling keep.");
		m->result->cancelkeep = 0;
	} else {
		m->result->cancelkeep = 1;
	}

	return SIEVE2_OK;
}

/* This should only happen if the user has uploaded an invalid script.
 * Possible causes are a homebrew script uploader that does not do proper
 * validation, libSieve's removal of a deprecated Sieve language feature,
 * or perhaps some bugginess elsewhere. */
int sort_errparse(sieve2_context_t *s, void *my)
{
	struct sort_context *m = (struct sort_context *)my;
	const char *message;
	int lineno;

	lineno = sieve2_getvalue_int(s, "lineno");
	message = sieve2_getvalue_string(s, "message");

	TRACE(TRACE_INFO, "Error is PARSE: Line is [%d], Message is [%s]", lineno, message);

	g_string_append_printf(m->result->errormsg, "Parse error on line [%d]: %s", lineno, message);

	if (m->message) {
		char *alertbody = g_strdup_printf(
			"Your Sieve script [%s] failed to parse correctly.\n"
			"Messages will be delivered to your INBOX for now.\n"
			"The error message is:\n"
			"%s\n",
			m->script, message);
		send_alert(m->user_idnr, "Sieve script parse error", alertbody);
		g_free(alertbody);
	}

	m->result->error_parse = 1;
	return SIEVE2_OK;
}

int sort_errexec(sieve2_context_t *s, void *my)
{
	struct sort_context *m = (struct sort_context *)my;
	const char *message;

	message = sieve2_getvalue_string(s, "message");

	TRACE(TRACE_INFO, "Error is EXEC: Message is [%s]", message);

	/* This turns out to be incredibly annoying, as libSieve
	 * throws execution errors on malformed addresses coming
	 * from the wild. As you might guess, that happens with
	 * greater than trivial frequency.

	g_string_append_printf(m->result->errormsg, "Execution error: %s", message);

	if (m->message) {
		char *alertbody = g_strdup_printf(
			"Your Sieve script [%s] failed to run correctly.\n"
			"Messages will be delivered to your INBOX for now.\n"
			"The error message is:\n"
			"%s\n",
			m->script, message);
		send_alert(m->user_idnr, "Sieve script run error", alertbody);
		g_free(alertbody);
	}
	*/

	m->result->error_runtime = 1;
	return SIEVE2_OK;
}

int sort_getscript(sieve2_context_t *s, void *my)
{
	struct sort_context *m = (struct sort_context *)my;
	const char * path, * name;
	int res;

	/* Path could be :general, :personal, or empty. */
	path = sieve2_getvalue_string(s, "path");

	/* If no file is named, we're looking for the main file. */
	name = sieve2_getvalue_string(s, "name");

	if (path == NULL || name == NULL)
		return SIEVE2_ERROR_BADARGS;

	if (strlen(path) && strlen(name)) {
		/* TODO: handle included files. */
		TRACE(TRACE_INFO, "Include requested from [%s] named [%s]", path, name);
	} else
	if (!strlen(path) && !strlen(name)) {
		/* Read the script file given as an argument. */
		TRACE(TRACE_INFO, "Getting default script named [%s]", m->script);
		res = db_get_sievescript_byname(m->user_idnr, m->script, &m->s_buf);
		if (res != SIEVE2_OK) {
			TRACE(TRACE_ERROR, "sort_getscript: read_file() returns %d\n", res);
			return SIEVE2_ERROR_FAIL;
		}
		sieve2_setvalue_string(s, "script", m->s_buf);
	} else {
		return SIEVE2_ERROR_BADARGS;
	}

	return SIEVE2_OK;
}

int sort_getheader(sieve2_context_t *s, void *my)
{
	struct sort_context *m = (struct sort_context *)my;
	char *header;
	char **bodylist;
	GTuples *headers;
	unsigned i;

	header = (char *)sieve2_getvalue_string(s, "header");
	
	headers = dbmail_message_get_header_repeated(m->message, header);
	
	bodylist = g_new0(char *,headers->len+1);
	for (i=0; i<headers->len; i++)
		bodylist[i] = (char *)g_tuples_index(headers,i,1);
	g_tuples_destroy(headers);

	/* We have to free the header array, but not its contents. */
	dm_list_nodeadd(&m->freelist, &bodylist, sizeof(char **));

	for (i = 0; bodylist[i] != NULL; i++) {
		TRACE(TRACE_INFO, "Getting header [%s] returning value [%s]",
			header, bodylist[i]);
	}

	sieve2_setvalue_stringlist(s, "body", bodylist);

	return SIEVE2_OK;
}

/* Return both the to and from headers. */
int sort_getenvelope(sieve2_context_t *s, void *my)
{
	struct sort_context *m = (struct sort_context *)my;

	char *to = m->message->envelope_recipient->str;
	char *from  = (char *)dbmail_message_get_header(m->message, "Return-Path");

	TRACE(TRACE_INFO, "Getting envelope, to [%s] from [%s]", to, from);

	sieve2_setvalue_string(s, "to", to);
	sieve2_setvalue_string(s, "from", from);

	return SIEVE2_OK;
}

int sort_getbody(sieve2_context_t *s UNUSED, void *my UNUSED)
{
	return SIEVE2_ERROR_UNSUPPORTED;
}

int sort_getsize(sieve2_context_t *s, void *my)
{
	struct sort_context *m = (struct sort_context *)my;
	int rfcsize;

	rfcsize = dbmail_message_get_rfcsize(m->message);

	TRACE(TRACE_INFO, "Getting message size [%d]", rfcsize);

	sieve2_setvalue_int(s, "size", rfcsize);

	return SIEVE2_OK;
}

int sort_getsubaddress(sieve2_context_t *s, void *my)
{
	struct sort_context *m = (struct sort_context *)my;
	const char *address;
	char *user = NULL, *detail = NULL,
	     *localpart = NULL, *domain = NULL;

	address = sieve2_getvalue_string(s, "address");

	/* Simple address parsing. libSieve only shows us
	 * the localpart@domain portion, so we don't need
	 * to handle anything exciting or exotic here. */
	// TODO: Unify with the delivery chain subaddress code.

	localpart = strdup(address);
	domain = strchr(localpart, '@');
	if (domain) {
		*domain = '\0';
		domain++;
	} else {
		// Malformed address.
	}

	user = strdup(localpart);
	detail = strchr(user, '+');
	if (detail) {
		*detail = '\0';
		detail++;
	} else {
		// No detail present.
	}

	sieve2_setvalue_string(s, "user", user);
	sieve2_setvalue_string(s, "detail", detail);
	sieve2_setvalue_string(s, "localpart", localpart);
	sieve2_setvalue_string(s, "domain", domain);

	dm_list_nodeadd(&m->freelist, &user, sizeof(char *));
	dm_list_nodeadd(&m->freelist, &localpart, sizeof(char *));

	return SIEVE2_OK;
}

int sort_debugtrace(sieve2_context_t *s, void *my UNUSED)
{
	int trace_level;

	switch (sieve2_getvalue_int(s, "level")) {
	case 0:
	case 1:
	case 2:
		trace_level = TRACE_INFO;
		break;
	case 3:
	case 4:
	case 5:
	default:
		trace_level = TRACE_DEBUG;
		break;
	}

	TRACE(trace_level, "libSieve: module [%s] file [%s] function [%s] message [%s]\n",
			sieve2_getvalue_string(s, "module"),
			sieve2_getvalue_string(s, "file"),
			sieve2_getvalue_string(s, "function"),
			sieve2_getvalue_string(s, "message"));

	return SIEVE2_OK;
}

/* END OF CALLBACKS */


sieve2_callback_t vacation_callbacks[] = {
	{ SIEVE2_ACTION_VACATION,       sort_vacation      },
	{ 0, 0 } };

sieve2_callback_t notify_callbacks[] = {
	{ SIEVE2_ACTION_NOTIFY,         sort_notify        },
	{ 0, 0 } };

sieve2_callback_t debug_callbacks[] = {
	{ SIEVE2_DEBUG_TRACE,           sort_debugtrace    },
	{ 0, 0 } };

sieve2_callback_t sort_callbacks[] = {
	{ SIEVE2_ERRCALL_RUNTIME,       sort_errexec       },
	{ SIEVE2_ERRCALL_PARSE,         sort_errparse      },

	{ SIEVE2_ACTION_REDIRECT,       sort_redirect      },
	{ SIEVE2_ACTION_DISCARD,        sort_discard       },
	{ SIEVE2_ACTION_REJECT,         sort_reject        },
	{ SIEVE2_ACTION_FILEINTO,       sort_fileinto      },
	{ SIEVE2_ACTION_KEEP,           sort_fileinto      },

	{ SIEVE2_SCRIPT_GETSCRIPT,      sort_getscript     },
	{ SIEVE2_MESSAGE_GETHEADER,     sort_getheader     },
	{ SIEVE2_MESSAGE_GETENVELOPE,   sort_getenvelope   },
	{ SIEVE2_MESSAGE_GETBODY,       sort_getbody       },
	{ SIEVE2_MESSAGE_GETSIZE,       sort_getsize       },
	{ SIEVE2_MESSAGE_GETSUBADDRESS, sort_getsubaddress },
	{ 0, 0 } };


static int sort_teardown(sieve2_context_t **s2c,
		struct sort_context **sc)
{
	assert(s2c != NULL);
	assert(sc != NULL);

	sieve2_context_t *sieve2_context = *s2c;
	struct sort_context *sort_context = *sc;
	int res;

	dm_list_free(&sort_context->freelist.start);

	if (sort_context) {
		g_free(sort_context);
	}

	res = sieve2_free(&sieve2_context);
	if (res != SIEVE2_OK) {
		TRACE(TRACE_ERROR, "Error [%d] when calling sieve2_free: [%s]",
			res, sieve2_errstr(res));
		return DM_EGENERAL;
	}

	*s2c = NULL;
	*sc = NULL;

	return DM_SUCCESS;
}

static int sort_startup(sieve2_context_t **s2c,
		struct sort_context **sc)
{
	assert(s2c != NULL);
	assert(sc != NULL);

	sieve2_context_t *sieve2_context = NULL;
	struct sort_context *sort_context = NULL;
	struct sort_sieve_config sieve_config;
	int res;

	res = sieve2_alloc(&sieve2_context);
	if (res != SIEVE2_OK) {
		TRACE(TRACE_ERROR, "Error [%d] when calling sieve2_alloc: [%s]",
			res, sieve2_errstr(res));
		return DM_EGENERAL;
	}

	sort_sieve_get_config(&sieve_config);

	res = sieve2_callbacks(sieve2_context, sort_callbacks);
	if (res != SIEVE2_OK) {
		TRACE(TRACE_ERROR, "Error [%d] when calling sieve2_callbacks: [%s]",
			res, sieve2_errstr(res));
		sort_teardown(&sieve2_context, &sort_context);
		return DM_EGENERAL;
	}
	if (sieve_config.vacation) {
		TRACE(TRACE_DEBUG, "Sieve vacation enabled.");
		res = sieve2_callbacks(sieve2_context, vacation_callbacks);
		if (res != SIEVE2_OK) {
			TRACE(TRACE_ERROR, "Error [%d] when calling sieve2_callbacks: [%s]",
				res, sieve2_errstr(res));
			sort_teardown(&sieve2_context, &sort_context);
			return DM_EGENERAL;
		}
	}
	if (sieve_config.notify) {
		TRACE(TRACE_ERROR, "Sieve notify is not supported in this release.");
		res = sieve2_callbacks(sieve2_context, notify_callbacks);
		if (res != SIEVE2_OK) {
			TRACE(TRACE_ERROR, "Error [%d] when calling sieve2_callbacks: [%s]",
				res, sieve2_errstr(res));
			sort_teardown(&sieve2_context, &sort_context);
			return DM_EGENERAL;
		}
	}
	if (sieve_config.debug) {
		TRACE(TRACE_DEBUG, "Sieve debugging enabled.");
		res = sieve2_callbacks(sieve2_context, debug_callbacks);
		if (res != SIEVE2_OK) {
			TRACE(TRACE_ERROR, "Error [%d] when calling sieve2_callbacks: [%s]",
				res, sieve2_errstr(res));
			sort_teardown(&sieve2_context, &sort_context);
			return DM_EGENERAL;
		}
	}

	sort_context = g_new0(struct sort_context, 1);
	if (!sort_context) {
		sort_teardown(&sieve2_context, &sort_context);
		return DM_EGENERAL;
	}
	memset(sort_context, 0, sizeof(struct sort_context));

	dm_list_init(&sort_context->freelist);

	*s2c = sieve2_context;
	*sc = sort_context;

	return DM_SUCCESS;
}

/* The caller is responsible for freeing memory here. */
const char * sort_listextensions(void)
{
	sieve2_context_t *sieve2_context;
	const char * extensions;
	struct sort_sieve_config sieve_config;

	if (sieve2_alloc(&sieve2_context) != SIEVE2_OK) 
		return NULL;

	if (sieve2_callbacks(sieve2_context, sort_callbacks))
		return NULL;

	sort_sieve_get_config(&sieve_config);

	if (sieve_config.vacation) {
		TRACE(TRACE_DEBUG, "Sieve vacation enabled.");
		sieve2_callbacks(sieve2_context, vacation_callbacks);
	}
	if (sieve_config.notify) {
		TRACE(TRACE_ERROR, "Sieve notify is not supported in this release.");
		sieve2_callbacks(sieve2_context, notify_callbacks);
	}
	if (sieve_config.debug) {
		TRACE(TRACE_DEBUG, "Sieve debugging enabled.");
		sieve2_callbacks(sieve2_context, debug_callbacks);
	}

	/* This will be freed by sieve2_free. */
	extensions = sieve2_listextensions(sieve2_context);

	/* So we'll make our own copy. */
	if (extensions)
		extensions = g_strdup(extensions);

	/* If this fails, then we don't care about the
	 * memory leak, because the program has to bomb out.
	 * It will not be possible to start libSieve up again
	 * if it does not free properly. */
	if (sieve2_free(&sieve2_context) != SIEVE2_OK)
		return NULL;

	return extensions;
}

/* Return 0 on script OK, 1 on script error, 2 on misc error. */
sort_result_t *sort_validate(u64_t user_idnr, char *scriptname)
{
	int res, exitnull = 0;
	struct sort_result *result = NULL;
	sieve2_context_t *sieve2_context;
	struct sort_context *sort_context;

	/* The contents of this function are taken from
	 * the libSieve distribution, sv_test/example.c,
	 * and are provided under an "MIT style" license.
	 * */

	if (sort_startup(&sieve2_context, &sort_context) != DM_SUCCESS) {
		return NULL;
	}

	sort_context->script = scriptname;
	sort_context->user_idnr = user_idnr;
	sort_context->result = g_new0(struct sort_result, 1);
	if (! sort_context->result) {
		return NULL;
	}
	sort_context->result->errormsg = g_string_new("");

	res = sieve2_validate(sieve2_context, sort_context);
	if (res != SIEVE2_OK) {
		TRACE(TRACE_ERROR, "Error %d when calling sieve2_validate: %s",
			res, sieve2_errstr(res));
		exitnull = 1;
		goto freesieve;
	}

	/* At this point the callbacks are called from within libSieve. */

freesieve:
	if (sort_context->s_buf)
		g_free(sort_context->s_buf);

	if (exitnull)
		result = NULL;
	else
		result = sort_context->result;

	sort_teardown(&sieve2_context, &sort_context);

	return result;
}

/* Pull up the relevant sieve scripts for this
 * user and begin running them against the header
 * and possibly the body of the message.
 *
 * Returns 0 on success, -1 on failure,
 * and +1 on success but with memory leaking.
 * In the +1 case, if called from a daemon
 * such as dbmail-lmtpd, the daemon should
 * finish storing the message and restart.
 * */
sort_result_t *sort_process(u64_t user_idnr, struct DbmailMessage *message)
{
	int res, exitnull = 0;
	struct sort_result *result = NULL;
	sieve2_context_t *sieve2_context;
	struct sort_context *sort_context;

	/* The contents of this function are taken from
	 * the libSieve distribution, sv_test/example.c,
	 * and are provided under an "MIT style" license.
	 * */

	if (sort_startup(&sieve2_context, &sort_context) != DM_SUCCESS) {
		return NULL;
	}

	sort_context->message = message;
	sort_context->user_idnr = user_idnr;
	sort_context->result = g_new0(struct sort_result, 1);
	if (! sort_context->result) {
		exitnull = 1;
		goto freesieve;
	}
	sort_context->result->errormsg = g_string_new("");

	res = db_get_sievescript_active(user_idnr, &sort_context->script);
	if (res != 0) {
		TRACE(TRACE_ERROR, "Error [%d] when calling db_getactive_sievescript", res);
		exitnull = 1;
		goto freesieve;
	}
	if (sort_context->script == NULL) {
		TRACE(TRACE_INFO, "User doesn't have any active sieve scripts.");
		exitnull = 1;
		goto freesieve;
	}

	res = sieve2_execute(sieve2_context, sort_context);
	if (res != SIEVE2_OK) {
		TRACE(TRACE_ERROR, "Error [%d] when calling sieve2_execute: [%s]",
			res, sieve2_errstr(res));
		exitnull = 1;
	}

	/* At this point the callbacks are called from within libSieve. */

	if (! sort_context->result->cancelkeep) {
		TRACE(TRACE_INFO, "No actions taken; message must be kept.");
	}

freesieve:
	if (sort_context->s_buf)
		g_free(sort_context->s_buf);
	if (sort_context->script)
		g_free(sort_context->script);

	if (exitnull)
		result = NULL;
	else
		result = sort_context->result;

	sort_teardown(&sieve2_context, &sort_context);

	return result;
}

/* SORT RESULT INTERFACE */

void sort_free_result(sort_result_t *result)
{
	if (result == NULL) return;
	if (result->errormsg) g_string_free(result->errormsg, TRUE);
	if (result->rejectmsg) g_string_free(result->rejectmsg, TRUE);
	g_free(result);
}

int sort_get_cancelkeep(sort_result_t *result)
{
	if (result == NULL) return 0;
	return result->cancelkeep;
}

const char * sort_get_mailbox(sort_result_t *result)
{
	if (result == NULL) return NULL;
	return result->mailbox;
}

int sort_get_reject(sort_result_t *result)
{
	if (result == NULL) return 0;
	return result->reject;
}

const char *sort_get_rejectmsg(sort_result_t *result)
{
	if (result == NULL) return NULL;
	if (result->rejectmsg == NULL) return NULL;
	return result->rejectmsg->str;
}

int sort_get_error(sort_result_t *result)
{
	if (result == NULL) return 0;
	if (result->errormsg == NULL) return 0;
	return result->errormsg->len;
}

const char * sort_get_errormsg(sort_result_t *result)
{
	if (result == NULL) return NULL;
	if (result->errormsg == NULL) return NULL;
	return result->errormsg->str;
}



syntax highlighted by Code2HTML, v. 0.9.1