/* Delivery User Functions
 * Aaron Stone, 9 Feb 2004 */
/*
  $Id: dsn.c 1904 2005-10-18 08:11:39Z 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 <stdlib.h>
#include <string.h>

#include "dsn.h"
#include "list.h"
#include "auth.h"
#include "debug.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->userids = (struct list *) dm_malloc(sizeof(struct list));
	if (dsnuser->userids == NULL)
		return -1;
	dsnuser->forwards = (struct list *) dm_malloc(sizeof(struct list));
	if (dsnuser->forwards == NULL) {
		dm_free(dsnuser->userids);
		return -1;
	}

	list_init(dsnuser->userids);
	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;

	/* These are nominally const, but
	 * we really do want to free them. */
	dm_free((char *) dsnuser->address);
	dm_free((char *) dsnuser->mailbox);

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

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

	trace(TRACE_DEBUG, "%s, %s: dsnuser freed",
	      __FILE__, __func__);
}


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

	/* Loop through the users list */
	for (element = 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)
{
	u64_t userid;
	int alias_count = 0, domain_count = 0;
	char *domain = NULL;
	char *username = NULL;

	/* If the userid is already set, then we're doing direct-to-userid. */
	if (delivery->useridnr != 0) {
		/* This seems to be the only way to see if a useridnr is valid. */
		username = auth_get_userid(delivery->useridnr);
		if (username != NULL) {
			/* Free the username, we don't actually need it. */
			dm_free(username);

			/* Copy the delivery useridnr into the userids list. */
			if (list_nodeadd(delivery->userids,
			     &delivery->useridnr,
			     sizeof(delivery->useridnr)) == 0) {
				trace(TRACE_ERROR,
				      "%s, %s: out of memory",
				      __FILE__, __func__);
				return -1;
			}

			/* The userid was valid... */
			delivery->dsn.class = DSN_CLASS_OK;	/* Success. */
			delivery->dsn.subject = 1;	/* Address related. */
			delivery->dsn.detail = 5;	/* Valid. */
		} else {	/* from: 'if (username != NULL)' */

			/* The userid was invalid... */
			delivery->dsn.class = DSN_CLASS_FAIL;	/* Permanent failure. */
			delivery->dsn.subject = 1;	/* Address related. */
			delivery->dsn.detail = 1;	/* Does not exist. */
		}
	}
	/* We don't have a useridnr, so we have either a username or an alias. */
	/* Here's the process: 
	   if (address has alias) 
	       resolve aliases
	   else if (address is username)
	       deliver to username
	   else if (address' domain matches a catch-all)
	       deliver to domain-user
	 */
	else {		/* from: 'if (delivery->useridnr != 0)' */

		if (! strlen(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) {
			/* The address has aliases. */
			delivery->dsn.class = DSN_CLASS_OK;	/* Success. */
			delivery->dsn.subject = 1;	/* Address related. */
			delivery->dsn.detail = 5;	/* Valid. */
		} else {
			/* No aliases found for the address. */
			int user_exists;
			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;
			} else if (user_exists == 1) {
				if (list_nodeadd(delivery->userids, &userid,
				     sizeof(u64_t)) == 0) {
					trace(TRACE_ERROR, "%s, %s: out of memory",
					      __FILE__, __func__);
					return -1;
				} else {
                        
					trace(TRACE_DEBUG,
					      "%s, %s: added user [%s] id [%llu] to delivery list",
					      __FILE__, __func__, delivery->address, userid);
					/* The userid was valid... */
					delivery->dsn.class = DSN_CLASS_OK;	/* Success. */
					delivery->dsn.subject = 1;	/* Address related. */
					delivery->dsn.detail = 5;	/* Valid. */
				}
			} else {	/* from: if (user_exists < 0) || if (user_exists == 1)... */
				trace(TRACE_INFO,
				      "%s, %s: user [%s] checking for domain forwards.",
				      __FILE__, __func__, delivery->address);

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

				if (domain == NULL) {
					/* That's it, we're done here. */
					/* Permanent failure... */
					delivery->dsn.class = DSN_CLASS_FAIL;	/* Permanent failure. */
					delivery->dsn.subject = 1;	/* Address related. */
					delivery->dsn.detail = 1;	/* Does not exist. */
				} else {
					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) {
						/* Permanent failure... */
						delivery->dsn.class = DSN_CLASS_FAIL;	/* Permanent failure. */
						delivery->dsn.subject = 1;	/* Address related. */
						delivery->dsn.detail = 1;	/* Does not exist. */
					} else {	/* from: 'if (domain_count == 0)' */

						/* The userid was valid... */
						delivery->dsn.class = DSN_CLASS_OK;	/* Success. */
						delivery->dsn.subject = 1;	/* Address related. */
						delivery->dsn.detail = 5;	/* Valid. */
					}	/* from: 'if (domain_count == 0)' */
				}	/* from: 'if (domain == NULL)' */
			}	/* from: 'switch (auth_user_exists(delivery->address, &userid))' */
		}	/* from: 'if (alias_count > 0) || else' */
	}	/* from: 'if (delivery->useridnr != 0)' */

	return 0;
}

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

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

	list_freelist(&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 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 = 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);
}



syntax highlighted by Code2HTML, v. 0.9.1