/*
* 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