/*
 * 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: dnsbl.c,v 1.24 2007/11/05 05:50:13 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/mta.h"
#include "sm/net.h"
#include "smar.h"
#include "dnsbl.h"
#include "log.h"

/*
**  SMAR_DNS BL_NEW -- Create a SMAR DNS BL context
**
**	Parameters:
**		smar_addr -- address context
**		dnsblres -- result structure for DNS BL
**		cnfdnsbl -- configuration data
**		cbf --  callback function
**		cb_ctx -- context for callback function
**		psmar_dnsbl -- (pointer to) SMAR DNS BL context (output)
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
smar_dnsbl_new(smar_addr_P smar_addr, smar_dnsblres_P dnsblres, smar_cnfdnsbl_P cnfdnsbl, smar_dnsbl_cb_F *cbf, void *cb_ctx, smar_dnsbl_P *psmar_dnsbl)
{
	smar_dnsbl_P smar_dnsbl;

	SM_REQUIRE(psmar_dnsbl != NULL);
	smar_dnsbl = (smar_dnsbl_P) sm_zalloc(sizeof(*smar_dnsbl));
	if (NULL == smar_dnsbl)
		return sm_error_temp(SM_EM_AR, ENOMEM);
	smar_dnsbl->adbl_addr = smar_addr;
	smar_dnsbl->adbl_res = dnsblres;
	smar_dnsbl->adbl_cnfdnsbl = cnfdnsbl;
	smar_dnsbl->adbl_cbf = cbf;
	smar_dnsbl->adbl_cb_ctx = cb_ctx;
	*psmar_dnsbl = smar_dnsbl;
	return SM_SUCCESS;
}

/*
**  SMAR_DNS BL_FREE -- Free a SMAR DNS BL context
**
**	Parameters:
**		smar_dnsbl -- SMAR DNS BL context
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
smar_dnsbl_free(smar_dnsbl_P smar_dnsbl)
{
	if (NULL == smar_dnsbl)
		return SM_SUCCESS;
	sm_free_size(smar_dnsbl, sizeof(*smar_dnsbl));
	return SM_SUCCESS;
}

/*
**  SMAR_DNSBL_CB -- Callback function for DNS resolver
**
**	Parameters:
**		dns_res -- DNS resolver result
**		ctx -- context: smar_dnsbl
**
**	Returns:
**		usual sm_error code
**
**	Used as callback for dns_req_add() by smar_dnsbl_rslv() and by itself.
**
**	Last code review:
**	Last code change:
**
**	Locking: why does this lock smar_ctx->smar_mutex?
**		just for smar_is_stop(smar_ctx)?
**	Access to smar_dnsbl->adbl_res->{sbdr_res,sdbr_ipv4} is protected
**	indirectly via smar_addr->ara_mutex which protects smar_addr->ara_res_cnt
**	which is used by smar_dnsbl_notify() and smar_dns_getret() to coordinate
**	access to those variables.
*/

static sm_ret_T
smar_dnsbl_cb(dns_res_P dns_res, void *ctx)
{
	dns_type_T type;
	sm_ret_T ret, dnsres, res;
	int r;
	uint u;
	uint8_t flags;
	smar_dnsbl_P smar_dnsbl;
	smar_addr_P smar_addr;
	dns_rese_P dns_rese;
	smar_ctx_P smar_ctx;
	dns_mgr_ctx_P dns_mgr_ctx;

#define SM_CB_INVOKED		0x01

	/* SM_REQUIRE(dns_res != NULL); */
	/* SM_REQUIRE(ctx != NULL); */
	if (NULL == dns_res || NULL == ctx) {
SMAR_LEV_DPRINTF(0, (SMAR_DEBFP, "sev=ERROR, func=smar_dnsbl_cb, dns_res=%p, ctx=%p\n", dns_res, ctx));
		/* XXX Oops... how to do logging if there is no context? */
		sm_log_write(NULL, AR_LCAT_RESOLVER, AR_LMOD_RVRS_CB,
			SM_LOG_CRIT, 0,
			"sev=CRIT, func=smar_dnsbl_cb, dns_res=%p, ctx=%p"
			, dns_res, ctx);
		return SM_FAILURE;
	}

	smar_dnsbl = (smar_dnsbl_P) ctx;
	smar_addr = smar_dnsbl->adbl_addr;
	SM_IS_SMAR_ADDR(smar_addr);
	smar_ctx = smar_addr->ara_smar_clt_ctx->smac_ar_ctx;
	SM_IS_SMAR_CTX(smar_ctx);
	flags = 0;

	/*
	**  Check whether SMAR is shutting down: if yes: don't
	**  do anything with the replies since the tasks are invalid.
	*/

	r = pthread_mutex_lock(&smar_ctx->smar_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* LOG; XXX What to do in this error case? */
		sm_log_write(smar_ctx->smar_lctx, AR_LCAT_RESOLVER,
			AR_LMOD_RVRS_CB, SM_LOG_CRIT, 1,
			"sev=CRIT, func=smar_dnsbl_cb, lock=%d", r);
		return SM_FAILURE;
	}

	if (smar_is_stop(smar_ctx)) {
		sm_log_write(smar_ctx->smar_lctx,
			AR_LCAT_RESOLVER, AR_LMOD_RVRS_CB,
			SM_LOG_NOTICE, 10,
			"sev=INFO, func=smar_dnsbl_cb, status=shutting_down");
		r = pthread_mutex_unlock(&smar_ctx->smar_mutex);
		return SM_SUCCESS;
	}

	dns_mgr_ctx = smar_ctx->smar_dns_mgr_ctx;
	SM_REQUIRE(dns_mgr_ctx != NULL);

	/*
	**  smar_addr can be free()d as soon as
	**  smar_addr->arv_cbf is invoked, hence store
	**  required data in a local variable.
	*/

	ret = SM_SUCCESS;
	dnsres = dns_res->dnsres_ret;
	type = dns_res->dnsres_qtype;

SMAR_LEV_DPRINTF(4, (SMAR_DEBFP, "func=smar_dnsbl_cb, dnsres=%r, type=%#x, entries=%d\n", dnsres, type, dns_res->dnsres_entries));
#if 0
#endif /* 0 */
	if (sm_is_err(dnsres)) {
#if 0
		sm_log_write(smar_ctx->smar_lctx, AR_LCAT_RESOLVER,
			AR_LMOD_RVRS_CB, SM_LOG_NOTICE, 8,
			"sev=NOTICE, func=smar_dnsbl_cb, pa=%S, type=%d, error=%m",
			smar_addr->arv_pa, type, dnsres);
#endif /* 0 */

		switch (dnsres) {
		  case DNSR_TEMP:
		  case DNSR_TIMEOUT:
			/* change error code? */
			smar_dnsbl->adbl_res->sbdr_res = DNSR_TEMP;
			break;
		  case DNSR_REFUSED:
			sm_log_write(smar_ctx->smar_lctx,
				AR_LCAT_RESOLVER, AR_LMOD_DNS,
				SM_LOG_CRIT, 6,
				"sev=CRIT, func=smar_dnsbl_cb, pa=%#S, type=%d, error=%m",
				smar_addr->ara_pa, type, dnsres);
		  case DNSR_NOTFOUND:
		  case DNSR_PERM:
		  case DNSR_NO_DATA:
		  case sm_error_perm(SM_EM_DNS, EINVAL):
			/* change error code? */
			smar_dnsbl->adbl_res->sbdr_res = DNSR_PERM;
			break;
		  default:
			sm_log_write(smar_ctx->smar_lctx,
				AR_LCAT_RESOLVER, AR_LMOD_RVRS_CB,
				SM_LOG_ERROR, 0,
				"sev=ERROR, func=smar_dnsbl_cb, unknown_error=%d",
				dns_res->dnsres_ret);

			/* Which error to return to caller? */
			ret = dnsres; /* sm_error_perm(SM_EM_AR, EINVAL); */
			goto error;
		}

		ret = smar_dnsbl->adbl_cbf(smar_dnsbl, smar_dnsbl->adbl_cb_ctx);
		SM_SET_FLAG(flags, SM_CB_INVOKED);
		if (sm_is_err(ret))
			goto error;
	}
	else {
		uint ui;

		u = dns_res->dnsres_entries;
		if (u-- > 1) {
			size_t l;

			l = sizeof(*smar_dnsbl->adbl_res->sdbr_ipv4s) * u;
			if (l > u && l > sizeof(*smar_dnsbl->adbl_res->sdbr_ipv4s))
				smar_dnsbl->adbl_res->sdbr_ipv4s = (ipv4_T *)sm_zalloc(l);
		}
		for (dns_rese = DRESL_FIRST(dns_res), u = 0, ui = 0;
		     dns_rese != DRESL_END(dns_res) && u < dns_res->dnsres_entries;
		     dns_rese = DRESL_NEXT(dns_rese), u++)
		{
			if (dns_rese->dnsrese_type != T_A)
				continue;
			if (0 == u) {
				smar_dnsbl->adbl_res->sdbr_ipv4 =
					dns_rese->dnsrese_val.dnsresu_a;
			}
			else if (smar_dnsbl->adbl_res->sdbr_ipv4s != NULL) {
				smar_dnsbl->adbl_res->sdbr_ipv4s[ui] = 
					dns_rese->dnsrese_val.dnsresu_a;
				++ui;
			}
			smar_dnsbl->adbl_res->sbdr_res = SM_DNSBL_RES_OK;

			sm_log_write(smar_ctx->smar_lctx,
				AR_LCAT_RESOLVER, AR_LMOD_RVRS_CB,
				SM_LOG_INFO, 10,
				"sev=INFO, func=smar_dnsbl_cb, u=%u, dnsbl=%s, pa=%S, ip=%A",
				u, smar_dnsbl->adbl_cnfdnsbl->scdb_name,
				smar_dnsbl->adbl_addr->ara_pa,
				smar_dnsbl->adbl_res->sdbr_ipv4);
		}
		smar_dnsbl->adbl_res->sdbr_n = ui;

		/* got no A record? */
		if (smar_dnsbl->adbl_res->sbdr_res != SM_DNSBL_RES_OK)
			smar_dnsbl->adbl_res->sbdr_res = DNSR_PERM;

		ret = smar_dnsbl->adbl_cbf(smar_dnsbl, smar_dnsbl->adbl_cb_ctx);
		SM_SET_FLAG(flags, SM_CB_INVOKED);
		if (sm_is_err(ret))
			goto error; /* XXX need more cleanup here? */
	}

	r = pthread_mutex_unlock(&smar_ctx->smar_mutex);
	SM_ASSERT(r == 0);
	smar_dnsbl_free(smar_dnsbl);
	return ret;

  error:
	/* XXX Cleanup? */
	sm_log_write(smar_ctx->smar_lctx,
		AR_LCAT_RESOLVER, AR_LMOD_RVRS_CB,
		SM_LOG_ERROR, 0,
		"sev=ERROR, func=smar_dnsbl_cb, ret=%m", ret);
	if (SM_IS_FLAG(flags, SM_CB_INVOKED))
		goto errunl;
#if 0
	sm_log_write(smar_ctx->smar_lctx,
		AR_LCAT_RESOLVER, AR_LMOD_RVRS_CB,
		SM_LOG_WARN, 1,
		"sev=ERROR, func=smar_dnsbl_cb, pa=%S, type=%d, error=%m, A_records_rcvd=%d, A_queries_sent=%d"
		, smar_addr->arv_pa, type, ret
		, smar_addr->arv_A_qsent, smar_addr->arv_A_rrcvd
		);
#endif /* 0 */
	res = smar_dnsbl->adbl_cbf(smar_dnsbl, smar_dnsbl->adbl_cb_ctx);
	SM_SET_FLAG(flags, SM_CB_INVOKED);
#if 0
	if (sm_is_err(res))
		/* oops? */;
#endif

  errunl:
	r = pthread_mutex_unlock(&smar_ctx->smar_mutex);
	SM_ASSERT(r == 0);
	smar_dnsbl_free(smar_dnsbl);
	return ret;
}

/*
**  SMAR_DNSBL_RSLV -- Lookup IPv4 address in some DNS BL (async)
**
**	Parameters:
**		smar_ctx -- SMAR context
**		smar_dnsbl -- DNS BL context
**		ipv4 -- IPv4 address to check
**		timeout -- timeout (if 0: use default)
**
**	Returns:
**		usual sm_error code
**
**	Last code review:
**	Last code change:
*/

sm_ret_T
smar_dnsbl_rslv(smar_ctx_P smar_ctx, smar_dnsbl_P smar_dnsbl, ipv4_T ipv4, uint timeout)
{
	sm_ret_T ret;
	dns_mgr_ctx_P dns_mgr_ctx;
	sm_str_P q0;
	sm_cstr_P q;
	char *domain;
	uchar *src;
	size_t s;
	ssize_t l;

	SM_IS_SMAR_CTX(smar_ctx);
	SM_ASSERT(smar_dnsbl != NULL);
	ret = SM_SUCCESS;
	q = NULL;
	q0 = NULL;

	dns_mgr_ctx = smar_ctx->smar_dns_mgr_ctx;
	domain = smar_dnsbl->adbl_cnfdnsbl->scdb_name;
	if (NULL == domain) {
		ret = sm_error_temp(SM_EM_AR, EINVAL);
		goto error;
	}
	s = strlen(domain) + 16;	/* 16 = strlen("255.") * 4 */
	q0 = sm_str_new(NULL, s, s + 2);
	if (NULL == q0) {
		ret = sm_error_temp(SM_EM_AR, ENOMEM);
		goto error;
	}
	src = (uchar *) &ipv4;
	l = sm_strprintf(q0, "%u.%u.%u.%u.%s",
		src[3], src[2], src[1], src[0], domain);
	if (l <= 0 || ret >= sm_str_getmax(q0)) {
		ret = sm_error_perm(SM_EM_AR, ENOSPC);
		goto error;
	}
	q = sm_cstr_scpyn0((const uchar *) sm_str_getdata(q0), sm_str_getlen(q0));
	if (NULL == q) {
		ret = sm_error_perm(SM_EM_AR, ENOMEM);
		goto error;
	}

	ret = dns_req_add(dns_mgr_ctx, q, T_A, timeout, smar_dnsbl_cb, smar_dnsbl);
	if (sm_is_err(ret)) {
		smar_dnsbl_free(smar_dnsbl);
		/* smar_dnsbl = NULL; done below */
	}

	/*
	**  Unless dns_req_add() failed smar_dnsbl_cb() is now responsible
	**  for smar_dnsbl.  Even though the subsequent assignment is currently
	**  not necessary it is done for clarity: if later on code is added
	**  which may "goto error" smar_dnsbl must not be free()d here anymore.
	*/

	smar_dnsbl = NULL;

	/*
	**  query has been "copied" by dns_req_add().
	**  Maybe we should let dns_req_add() just "own" the query
	**  instead of "copying" it?
	*/

	SM_STR_FREE(q0);
	SM_CSTR_FREE(q);
	return ret;

  error:
	smar_dnsbl_free(smar_dnsbl);
	smar_dnsbl = NULL;
	sm_log_write(smar_ctx->smar_lctx,
		AR_LCAT_RESOLVER, AR_LMOD_RESOLVER,
		SM_LOG_ERROR, 0,
		"func=smar_dnsbl_rslv, ret=%m", ret);
	SM_STR_FREE(q0);
	SM_CSTR_FREE(q);
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1