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