/* Delivery User Functions
 * Aaron Stone, 9 Feb 2004 */
/*
  $Id: dsn.c 2207 2006-07-24 15:35:35Z paul $

 Copyright (C) 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.
*/

#include "dbmail.h"

/* Enhanced Status Codes from RFC 1893
 * Nota Bene: should be updated to include
 * its successor, RFC 3463, too.
 * */


/* Top level codes */
static const char * const DSN_STRINGS_CLASS[] = {
/* nop */	"", "",
/* 2.. */	"Success",
/* nop */	"",
/* 4.. */	"Persistent Transient Failure",
/* 5.. */	"Permanent Failure"
};

/* Second the Third level codes */
static const char * const DSN_STRINGS_SUBJECT[] = {
/* nop */	"",
/* .1. */	"Address Status",
/* .2. */	"Mailbox Status",
/* .3. */	"Mail System Status",
/* .4. */	"Network and Routing Status",
/* .5. */	"Mail Delivery Protocol Status",
/* .6. */	"Message Content or Message Media Status",
/* .7. */	"Security or Policy Status"
};

/* Address Status */
static const char * const DSN_STRINGS_DETAIL_ONE[] = {
/* .1.0 */	"Other address status",
/* .1.1 */	"Bad destination mailbox address",
/* .1.2 */	"Bad destination system address",
/* .1.3 */	"Bad destination mailbox address syntax",
/* .1.4 */	"Destination mailbox address ambiguous",
/* .1.5 */	"Destination mailbox address valid",
/* .1.6 */	"Mailbox has moved",
/* .1.7 */	"Bad sender's mailbox address syntax",
/* .1.8 */	"Bad sender's system address"
};

/* Mailbox Status */
static const char * const DSN_STRINGS_DETAIL_TWO[] = {
/* .2.0 */	"Other or undefined mailbox status",
/* .2.1 */	"Mailbox disabled, not accepting messages",
/* .2.2 */	"Mailbox full",
/* .2.3 */	"Message length exceeds administrative limit",
/* .2.4 */	"Mailing list expansion problem"
};

/* Mail System Status */
static const char * const DSN_STRINGS_DETAIL_THREE[] = {
/* .3.0 */	"Other or undefined mail system status",
/* .3.1 */	"Mail system full",
/* .3.2 */	"System not accepting network messages",
/* .3.3 */	"System not capable of selected features",
/* .3.4 */	"Message too big for system"
};

/* Network and Routing Status */
static const char * const DSN_STRINGS_DETAIL_FOUR[] = {
/* .4.0 */	"Other or undefined network or routing status",
/* .4.1 */	"No answer from host",
/* .4.2 */	"Bad connection",
/* .4.3 */	"Routing server failure",
/* .4.4 */	"Unable to route",
/* .4.5 */	"Network congestion",
/* .4.6 */	"Routing loop detected",
/* .4.7 */	"Delivery time expired"
};

/* Mail Delivery Protocol Status */
static const char * const DSN_STRINGS_DETAIL_FIVE[] = {
/* .5.0 */	"Other or undefined protocol status",
/* .5.1 */	"Invalid command",
/* .5.2 */	"Syntax error",
/* .5.3 */	"Too many recipients",
/* .5.4 */	"Invalid command arguments",
/* .5.5 */	"Wrong protocol version"
};

/* Message Content or Message Media Status */
static const char * const DSN_STRINGS_DETAIL_SIX[] = {
/* .6.0 */	"Other or undefined media error",
/* .6.1 */	"Media not supported",
/* .6.2 */	"Conversion required and prohibited",
/* .6.3 */	"Conversion required but not supported",
/* .6.4 */	"Conversion with loss performed",
/* .6.5 */	"Conversion failed"
};

/* Security or Policy Status */
static const char * const DSN_STRINGS_DETAIL_SEVEN[] = {
/* .7.0 */	"Other or undefined security status",
/* .7.1 */	"Delivery not authorized, message refused",
/* .7.2 */	"Mailing list expansion prohibited",
/* .7.3 */	"Security conversion required but not possible",
/* .7.4 */	"Security features not supported",
/* .7.5 */	"Cryptographic failure",
/* .7.6 */	"Cryptographic algorithm not supported",
/* .7.7 */	"Message integrity failure"
};

/* Convert the DSN code into a descriptive message. 
 * Returns 0 on success, -1 on failure. */
int dsn_tostring(delivery_status_t dsn, const char ** const class,
                 const char ** const subject, const char ** const detail)
{
	if (dsn.class == 2 || dsn.class == 4 || dsn.class == 5)
		*class = DSN_STRINGS_CLASS[dsn.class];
	else
		return -1;

	switch (dsn.subject) {
		case 1:
			if (dsn.detail >= 0 && dsn.detail <= 8)
				*detail = DSN_STRINGS_DETAIL_ONE[dsn.detail];
			else
				return -1;
			break;
		case 2:
			if (dsn.detail >= 0 && dsn.detail <= 4)
				*detail = DSN_STRINGS_DETAIL_TWO[dsn.detail];
			else
				return -1;
			break;
		case 3:
			if (dsn.detail >= 0 && dsn.detail <= 4)
				*detail = DSN_STRINGS_DETAIL_THREE[dsn.detail];
			else
				return -1;
			break;
		case 4:
			if (dsn.detail >= 0 && dsn.detail <= 7)
				*detail = DSN_STRINGS_DETAIL_FOUR[dsn.detail];
			else
				return -1;
			break;
		case 5:
			if (dsn.detail >= 0 && dsn.detail <= 5)
				*detail = DSN_STRINGS_DETAIL_FIVE[dsn.detail];
			else
				return -1;
			break;
		case 6:
			if (dsn.detail >= 0 && dsn.detail <= 5)
				*detail = DSN_STRINGS_DETAIL_SIX[dsn.detail];
			else
				return -1;
			break;
		case 7:
			if (dsn.detail >= 0 && dsn.detail <= 7)
				*detail = DSN_STRINGS_DETAIL_SEVEN[dsn.detail];
			else
				return -1;
			break;
		default:
			return -1;
	}

	/* If we made it this far, then the subject was valid. */
	*subject = DSN_STRINGS_SUBJECT[dsn.subject];

	return 0;
}

int dsnuser_init(deliver_to_user_t * dsnuser)
{
	dsnuser->useridnr = 0;
	dsnuser->dsn.class = 0;
	dsnuser->dsn.subject = 0;
	dsnuser->dsn.detail = 0;

	dsnuser->address = NULL;
	dsnuser->mailbox = NULL;
	dsnuser->source = BOX_NONE;

	dsnuser->userids = (struct dm_list *) dm_malloc(sizeof(struct dm_list));
	if (dsnuser->userids == NULL)
		return -1;
	dsnuser->forwards = (struct dm_list *) dm_malloc(sizeof(struct dm_list));
	if (dsnuser->forwards == NULL) {
		dm_free(dsnuser->userids);
		return -1;
	}

	dm_list_init(dsnuser->userids);
	dm_list_init(dsnuser->forwards);

	trace(TRACE_DEBUG, "%s, %s: dsnuser initialized",
	      __FILE__, __func__);
	return 0;
}


void dsnuser_free(deliver_to_user_t * dsnuser)
{
	dsnuser->useridnr = 0;
	dsnuser->dsn.class = 0;
	dsnuser->dsn.subject = 0;
	dsnuser->dsn.detail = 0;
	dsnuser->source = BOX_NONE;

	dm_list_free(&dsnuser->userids->start);
	dm_list_free(&dsnuser->forwards->start);

	if (dsnuser->userids)	
		g_free(dsnuser->userids);
	if (dsnuser->forwards)
		g_free(dsnuser->forwards);

	dsnuser->address = NULL;
	dsnuser->mailbox = NULL;
	dsnuser->userids = NULL;
	dsnuser->forwards = NULL;
	
	trace(TRACE_DEBUG, "%s, %s: dsnuser freed",
	      __FILE__, __func__);
}

void set_dsn(delivery_status_t *dsn,
		int foo, int bar, int qux)
{
	dsn->class = foo;
	dsn->subject = bar;
	dsn->detail = qux;
}

static int address_has_alias(deliver_to_user_t *delivery)
{
	int alias_count;

	if (!delivery->address)
		return 0;

	alias_count = auth_check_user_ext(delivery->address,
				delivery->userids,
				delivery->forwards, 0);
	trace(TRACE_DEBUG, "%s, %s: user [%s] found total of [%d] aliases",
	      __FILE__, __func__, delivery->address, alias_count);

	if (alias_count > 0)
		return 1;

	return 0;
}

// address is in format username+mailbox@domain
// and we want to cut out the mailbox and produce
// an address in format username@domain, then check it.
static int address_has_alias_mailbox(deliver_to_user_t *delivery)
{
	int alias_count;
	char *newaddress;
	size_t newaddress_len, zapped_len;

	if (!delivery->address)
		return 0;

	if (zap_between(delivery->address, -'+', '@', &newaddress,
			&newaddress_len, &zapped_len) != 0)
		return 0;

	alias_count = auth_check_user_ext(newaddress,
				delivery->userids,
				delivery->forwards, 0);
	trace(TRACE_DEBUG, "%s, %s: user [%s] found total of [%d] aliases",
	      __FILE__, __func__, newaddress, alias_count);

	dm_free(newaddress);

	if (alias_count > 0)
		return 1;

	return 0;
}

static int address_is_username_mailbox(deliver_to_user_t *delivery)
{
	int user_exists;
	u64_t userid;
	char *newaddress;
	size_t newaddress_len, zapped_len;

	if (!delivery->address)
		return 0;

	if (zap_between(delivery->address, -'+', '@', &newaddress,
			&newaddress_len, &zapped_len) != 0)
		return 0;

	user_exists = auth_user_exists(newaddress, &userid);

	if (user_exists < 0) {
		/* An error occurred. */
		trace(TRACE_ERROR, "%s, %s: error checking user [%s]",
		      __FILE__, __func__, newaddress);
		dm_free(newaddress);
		return -1;
	}

	if (user_exists == 0) {
		/* User does not exist. */
		trace(TRACE_INFO, "%s, %s: username not found [%s]",
		      __FILE__, __func__, newaddress);
		dm_free(newaddress);
		return 0;
	}

	if (dm_list_nodeadd(delivery->userids, &userid, sizeof(u64_t)) == 0) {
		trace(TRACE_ERROR, "%s, %s: out of memory",
		      __FILE__, __func__);
		dm_free(newaddress);
		return -1;
	}

	trace(TRACE_DEBUG, "%s, %s: added user [%s] id [%llu] to delivery list",
		__FILE__, __func__, newaddress, userid);

	dm_free(newaddress);
	return 1;
}

static int address_is_username(deliver_to_user_t *delivery)
{
	int user_exists;
	u64_t userid;

	if (!delivery->address)
		return 0;

	user_exists = auth_user_exists(delivery->address, &userid);

	if (user_exists < 0) {
		/* An error occurred. */
		trace(TRACE_ERROR, "%s, %s: error checking user [%s]",
		      __FILE__, __func__, delivery->address);
		return -1;
	}

	if (user_exists == 0) {
		/* User does not exist. */
		trace(TRACE_INFO, "%s, %s: username not found [%s]",
		      __FILE__, __func__, delivery->address);
		return 0;
	}

	if (dm_list_nodeadd(delivery->userids, &userid, sizeof(u64_t)) == 0) {
		trace(TRACE_ERROR, "%s, %s: out of memory",
		      __FILE__, __func__);
		return -1;
	}

	trace(TRACE_DEBUG, "%s, %s: added user [%s] id [%llu] to delivery list",
		__FILE__, __func__, delivery->address, userid);

	return 1;
}

static int address_is_domain_catchall(deliver_to_user_t *delivery)
{
	char *domain;
	int domain_count;

	if (!delivery->address)
		return 0;

	trace(TRACE_INFO, "%s, %s: user [%s] checking for domain forwards.",
		__FILE__, __func__, delivery->address);

	domain = strchr(delivery->address, '@');

	if (domain == NULL) {
		return 0;
	}

	trace(TRACE_DEBUG, "%s, %s: domain [%s] checking for domain forwards",
		__FILE__, __func__, domain);

	/* Checking for domain aliases */
	domain_count = auth_check_user_ext(domain, delivery->userids,
			delivery->forwards, 0);
	trace(TRACE_DEBUG, "%s, %s: domain [%s] found total of [%d] aliases",
		__FILE__, __func__, domain, domain_count);

	if (domain_count == 0) {
		return 0;
	}

	return 1;
}

static int address_is_userpart_catchall(deliver_to_user_t *delivery)
{
	char *userpart = dm_strdup(delivery->address);
	char *userpartcut;
	int userpart_count;

	if (!delivery->address)
		return 0;

	trace(TRACE_INFO, "%s, %s: user [%s] checking for userpart forwards.",
		__FILE__, __func__, userpart);

	userpartcut = strchr(userpart, '@');

	if (userpartcut == NULL) {
		return 0;
	}

	/* Stomp _after_ the @-sign. */
	*(userpartcut + 1) = '\0';

	trace(TRACE_DEBUG, "%s, %s: userpart [%s] checking for userpart forwards",
		__FILE__, __func__, userpart);

	/* Checking for userpart aliases */
	userpart_count = auth_check_user_ext(userpart, delivery->userids,
			delivery->forwards, 0);
	trace(TRACE_DEBUG, "%s, %s: userpart [%s] found total of [%d] aliases",
		__FILE__, __func__, userpart, userpart_count);

	if (userpart_count == 0) {
		return 0;
	}

	return 1;
}

void dsnuser_free_list(struct dm_list *deliveries)
{
	struct element *tmp;

	for (tmp = dm_list_getstart(deliveries); tmp != NULL;
	     tmp = tmp->nextnode)
		dsnuser_free((deliver_to_user_t *) tmp->data);

	dm_list_free(&deliveries->start);
}

delivery_status_t dsnuser_worstcase_int(int ok, int temp, int fail, int fail_quota)
{
	delivery_status_t dsn;
	
	dsn.class = DSN_CLASS_NONE;
	dsn.subject = 0;
	dsn.detail = 0;

	if (ok)
		dsn.class = DSN_CLASS_OK;
	if (fail_quota)
		dsn.class = DSN_CLASS_QUOTA;
	if (fail)
		dsn.class = DSN_CLASS_FAIL;
	if (temp)
		dsn.class = DSN_CLASS_TEMP;
	
	return dsn;
}

delivery_status_t dsnuser_worstcase_list(struct dm_list * deliveries)
{
	delivery_status_t dsn;
	struct element *tmp;
	int ok = 0, temp = 0, fail = 0, fail_quota = 0;
	

	/* Get one reasonable error code for everyone. */
	for (tmp = dm_list_getstart(deliveries); tmp != NULL;
	     tmp = tmp->nextnode) {
		dsn = ((deliver_to_user_t *) tmp->data)->dsn;
		switch (dsn.class) {
		case DSN_CLASS_OK:
			/* Success. */
			ok = 1;
			break;
		case DSN_CLASS_TEMP:
			/* Temporary transient failure. */
			temp = 1;
			break;
		case DSN_CLASS_FAIL:
		case DSN_CLASS_QUOTA:
			/* Permanent failure. */
			if (dsn.subject == 2)
				fail_quota = 1;
			else
				fail = 1;
			break;
		case DSN_CLASS_NONE:
			/* Nothing doing. */
			break;
		}
	}

	/* If we never made it into the list, all zeroes will
	 * yield a temporary failure, which is pretty reasonable. */
	return dsnuser_worstcase_int(ok, temp, fail, fail_quota);
}

int dsnuser_resolve_list(struct dm_list *deliveries)
{
	int ret;
	struct element *element;

	/* Loop through the users list */
	for (element = dm_list_getstart(deliveries); element != NULL;
	     element = element->nextnode) {
		if ((ret = dsnuser_resolve((deliver_to_user_t *) element->data)) != 0) {
			return ret;
		}
	}

	return 0;
}

int dsnuser_resolve(deliver_to_user_t *delivery)
{
	/* If the userid is already set, then we're doing direct-to-userid.
	 * We just want to make sure that the userid actually exists... */
	if (delivery->useridnr != 0) {

		trace(TRACE_INFO, "%s, %s: checking if [%llu] is a valid useridnr.",
				__FILE__, __func__, delivery->useridnr);

		switch (auth_check_userid(delivery->useridnr)) {
		case -1:
			/* Temp fail. Address related. D.N.E. */
			set_dsn(&delivery->dsn, DSN_CLASS_TEMP, 1, 1);
			trace(TRACE_INFO, "%s, %s: useridnr [%llu] temporary lookup failure.",
					__FILE__, __func__, delivery->useridnr);
			break;
		case 1:
			/* Failure. Address related. D.N.E. */
			set_dsn(&delivery->dsn, DSN_CLASS_FAIL, 1, 1);
			trace(TRACE_INFO, "%s, %s: useridnr [%llu] does not exist.",
					__FILE__, __func__, delivery->useridnr);
			break;
		case 0:
			/* Copy the delivery useridnr into the userids list. */
			if (dm_list_nodeadd(delivery->userids, &delivery->useridnr,
			     sizeof(delivery->useridnr)) == 0) {
				trace(TRACE_ERROR, "%s, %s: out of memory",
				      __FILE__, __func__);
				return -1;
			}

			/* Success. Address related. Valid. */
			set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5);
			trace(TRACE_INFO, "%s, %s: delivery [%llu] directly to a useridnr.",
					__FILE__, __func__, delivery->useridnr);
			break;
		}
	/* Ok, we don't have a useridnr, maybe we have an address? */
	} else if (strlen(delivery->address) > 0) {

		trace(TRACE_INFO, "%s, %s: checking if [%s] is a valid username, alias, or catchall.",
				__FILE__, __func__, delivery->address);

		if (address_has_alias(delivery))  {
			/* The address had aliases and they've
			 * been resolved into the delivery struct. */
			/* Success. Address related. Valid. */
			set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5);
			trace(TRACE_INFO, "%s, %s: delivering [%s] as an alias.",
					__FILE__, __func__, delivery->address);

		} else if (address_has_alias_mailbox(delivery)) {
			/* Success. Address related. Valid. */
			set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5);
			trace(TRACE_INFO, "%s, %s: delivering [%s] as an alias with mailbox.",
					__FILE__, __func__, delivery->address);

		} else if (address_is_username(delivery)) {
			/* Success. Address related. Valid. */
			set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5);
			trace(TRACE_INFO, "%s, %s: delivering [%s] as a username.",
					__FILE__, __func__, delivery->address);

		} else if (address_is_username_mailbox(delivery)) {
			/* Success. Address related. Valid. */
			set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5);
			trace(TRACE_INFO, "%s, %s: delivering [%s] as a username with mailbox.",
					__FILE__, __func__, delivery->address);
			
		} else if (address_is_domain_catchall(delivery)) {
			/* Success. Address related. Valid. */
			set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5);
			trace(TRACE_INFO, "%s, %s: delivering [%s] as a domain catchall.",
					__FILE__, __func__, delivery->address);

		} else if (address_is_userpart_catchall(delivery)) {
			/* Success. Address related. Valid. */
			set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5);
			trace(TRACE_INFO, "%s, %s: delivering [%s] as a userpart catchall.",
					__FILE__, __func__, delivery->address);

		} else {
			/* Failure. Address related. D.N.E. */
			set_dsn(&delivery->dsn, DSN_CLASS_FAIL, 1, 1);
			trace(TRACE_INFO, "%s, %s: could not find [%s] at all.",
					__FILE__, __func__, delivery->address);
		}

	/* Neither useridnr nor address.
	 * Something is wrong upstream. */
	} else {

		trace(TRACE_ERROR, "%s, %s: this delivery had neither useridnr nor address.",
				__FILE__, __func__);

		return -1;
	}

	return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1