/*
 * Copyright (c) 2004, 2005 Sendmail, Inc. and its suppliers.
 *      All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: adbr.c,v 1.14 2006/06/11 04:17:52 ca Exp $")
#include "sm/types.h"
#include "sm/str.h"
#include "sm/mta.h"
#include "sm/rfc2821.h"
#include "sm/actdb-int.h"

/*
**  AQ_RCPT_SS_INSERT -- insert aq_rcpt_new into ss link list
**
**	Parameters:
**		aq_rcpt_old -- previous aq_rcpt_new
**		aq_rcpt_new -- aq_rcpt_new to insert
**
**	Returns:
**		SM_SUCCESS
**
**	Locking: aq_rcpt_new* must be under control of caller
**
**	Invariant:
**	if not all elements in the ring are the same then there is
**	exactly one element F for which: L=pred(F) > F ("First"/"Head"),
**	(and succ(L)=F: L: Last, F: First)
**	for all others E: pred <= E.
**	for each element E:
**	if E isn't the first nor the last: pred(E) <= E <= succ(E)
**	if E is the first: pred(E) >= E <= succ(E)
**	if E is the last: pred(E) <= E >= succ(E)
**
**	Problem: there's no explicit head (or tail) of the "list"
**	because it is a ring: X -> B -> D -> E -> X
**	hence the code must be prepared to "walk" in any direction
**
**	Possible enhancement: use a tree with sublists as it is
**	done for edbc.
**
**	Last code review:
**	Last code change:
*/

sm_ret_T
aq_rcpt_ss_insert(aq_rcpt_P aq_rcpt_old, aq_rcpt_P aq_rcpt_new)
{
	int direction;
	aq_rcpt_P aq_rcpt_nxt, aq_rcpt_cur;

	SM_IS_AQ_RCPT(aq_rcpt_new);
	if (aq_rcpt_old == NULL)
	{
		AQR_SS_INIT(aq_rcpt_new);
		return SM_SUCCESS;
	}
	SM_IS_AQ_RCPT(aq_rcpt_old);

	/*
	**  Shouldn't happen but let's be nice here.
	**  However, later on a non-NULL domain is required, so this isn't
	**  consistent.
	*/

	if (aq_rcpt_new->aqr_domain == NULL ||
	    aq_rcpt_old->aqr_domain == NULL)
	{
		AQR_SS_APP(aq_rcpt_old, aq_rcpt_new);
		return SM_SUCCESS;
	}

#define DIR(d)	((d > 0) ? 1 : ((d < 0) ? (-1) : 0))

	aq_rcpt_cur = aq_rcpt_nxt = aq_rcpt_old;
	direction = sm_str_casecmp(aq_rcpt_new->aqr_domain,
				aq_rcpt_nxt->aqr_domain);
	if (direction == 0)
	{
		AQR_SS_APP(aq_rcpt_old, aq_rcpt_new);
		return SM_SUCCESS;
	}
	else if (direction < 0)
	{
		/*
		**  initial: new < nxt
		**  go to the "left" (pred)
		**  stop if new >= nxt (can't be on first round)
		**	found place to insert (append to nxt)
		**  stop if nxt > cur (L > F)
		**	this means new is less than every element in the ring
		**	and nxt is the largest element, hence new must be
		**	appended to nxt
		**  stop if elem is reached:
		**	this means new is less than every element in the ring
		**	and old is the largest element, hence new must be
		**	appended to old (= nxt)
		*/

		while ((aq_rcpt_nxt = AQR_SS_PRED(aq_rcpt_nxt)) != aq_rcpt_old
		       && !(sm_str_casecmp(aq_rcpt_new->aqr_domain,
					aq_rcpt_nxt->aqr_domain) >= 0 ||
			    sm_str_casecmp(aq_rcpt_nxt->aqr_domain,
					aq_rcpt_cur->aqr_domain) > 0))
		{
			aq_rcpt_cur = aq_rcpt_nxt;
		}
		AQR_SS_APP(aq_rcpt_nxt, aq_rcpt_new);
	}
	else
	{
		/*
		**  initial: new > nxt
		**  go to the "right" (succ)
		**  stop if new <= nxt (can't be on first round)
		**	found place to insert (prepend to nxt)
		**  stop if nxt < cur (F > L)
		**	this means new is greater than every element in the ring
		**	and next is the smallest element, hence new must be
		**	prepended to nxt
		**  stop if old is reached:
		**	this means new is greater than every element in the ring
		**	and old is the smallest element, hence new must be
		**	prepended to old (= nxt)
		*/

		while ((aq_rcpt_nxt = AQR_SS_SUCC(aq_rcpt_nxt)) != aq_rcpt_old
		       && !(sm_str_casecmp(aq_rcpt_new->aqr_domain,
					aq_rcpt_nxt->aqr_domain) <= 0 ||
			    sm_str_casecmp(aq_rcpt_nxt->aqr_domain,
					aq_rcpt_cur->aqr_domain) < 0))
		{
			aq_rcpt_cur = aq_rcpt_nxt;
		}
		AQR_SS_PRE(aq_rcpt_nxt, aq_rcpt_new);
	}
	return SM_SUCCESS;
}

/*
**  AQ_RCPT_SET_DOMAIN -- create and initialize aqr_domain
**
**	Parameters:
**		aq_rcpt -- AQ recipient
**		defaultdomain -- domain to use if recipient doesn't have one
**
**	Returns:
**		usual sm_error code; ENOMEM, address scan/parse errors
**
**	Side Effects:
**
**	Locking: aq_rcpt_new must be under control of caller
**
**	Last code review: 2005-03-20 04:21:38
**	Last code change: 2006-06-11 04:03:09
*/

sm_ret_T
aq_rcpt_set_domain(aq_rcpt_P aq_rcpt, sm_str_P defaultdomain)
{
	sm_ret_T ret;
	sm_a2821_T a_rcpt, a_domain;
	sm_a2821_P prcpt, pdomain;

	SM_IS_AQ_RCPT(aq_rcpt);
	ret = SM_SUCCESS;
	if (aq_rcpt->aqr_domain != NULL)
		return ret;
	pdomain = &a_domain;	/* pdomain is just pointer to a_domain */
	prcpt = &a_rcpt;	/* prcpt is just pointer to a_rcpt */
	A2821_INIT_RP(prcpt, NULL);
	A2821_INIT_RP(pdomain, NULL);
	ret = t2821_scan((sm_rdstr_P) aq_rcpt->aqr_pa, prcpt, 0);
	if (sm_is_err(ret))
		goto error;
	ret = t2821_parse(prcpt, R2821_CORRECT & ~R2821_AT);
	if (sm_is_err(ret))
		goto error;

	aq_rcpt->aqr_domain = sm_str_new(NULL,
				SM_MIN(MAXDOMAINLEN,
					sm_str_getlen(aq_rcpt->aqr_pa)),
				MAXDOMAINLEN + 2);
	if (aq_rcpt->aqr_domain == NULL)
	{
		ret = sm_error_temp(SM_EM_AQ, ENOMEM);
		goto error;
	}
	ret = t2821_extract_domain(NULL, prcpt, &pdomain);
	if (!sm_is_err(ret))
		ret = t2821_str(pdomain, aq_rcpt->aqr_domain, 0);
	else if (sm_error_perm(SM_EM_ADDR, R2821_ERR_FQDN) == ret)
		ret = sm_str_cpy(aq_rcpt->aqr_domain, defaultdomain);
	if (sm_is_err(ret))
		goto error;
	sm_str2lower(aq_rcpt->aqr_domain);
	a2821_free(pdomain);
	a2821_free(prcpt);
	return ret;

  error:
	a2821_free(pdomain);
	a2821_free(prcpt);
	SM_STR_FREE(aq_rcpt->aqr_domain);
	return ret;
}

/*
**  AQ_RCPT_EQ_DOMAIN -- compare domain part of two recipients
**
**	Parameters:
**		aq_rcpt1 -- aq_rcpt
**		aq_rcpt2 -- aq_rcpt
**		defaultdomain -- domain to use if recipient doesn't have one
**
**	Returns:
**		SM_SUCCESS: domain parts are identical
**		SM_NOMATCH: domain parts are not identical
**		<0: usual sm_error code
**
**	Locking: aq_rcpt{1,2} must be under control of caller
**
**	Side Effects:
**		aqr_domain may be populated.
*/

sm_ret_T
aq_rcpt_eq_domain(aq_rcpt_P aq_rcpt1, aq_rcpt_P aq_rcpt2, sm_str_P defaultdomain)
{
	sm_ret_T ret;

	SM_IS_AQ_RCPT(aq_rcpt1);
	SM_IS_AQ_RCPT(aq_rcpt2);
	ret = SM_SUCCESS;
	if (aq_rcpt1->aqr_domain == NULL)
	{
		ret = aq_rcpt_set_domain(aq_rcpt1, defaultdomain);
		if (sm_is_err(ret))
			return ret;
	}
	if (aq_rcpt2->aqr_domain == NULL)
	{
		ret = aq_rcpt_set_domain(aq_rcpt2, defaultdomain);
		if (sm_is_err(ret))
			return ret;
	}
	if (sm_str_casecmp(aq_rcpt1->aqr_domain, aq_rcpt2->aqr_domain) == 0)
		return SM_SUCCESS;
	return SM_NOMATCH;
}


syntax highlighted by Code2HTML, v. 0.9.1