/* * Copyright (c) 2002-2006 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: dnstsk.c,v 1.88 2006/12/29 01:25:35 ca Exp $") #include "sm/ctype.h" #include "sm/io.h" /* sm_fd_nonblock() */ #include "sm/dns.h" #include "sm/dns-int.h" #include "sm/dnstsk.h" #include "dns.h" #define LIBDNS_LOG_DEFINES 1 #include "log.h" /* ** Note: for some unknown reasons the query in the answer packet ** does not contain a trailing dot even if the original query did. ** Hence the code that generates a key removes trailing dots. ** ToDo: It would be nice to put that key generation code into one place ** (macro/function). */ /* when to wakeup dns_tsk_cleanup; should be "never" by default, see comments */ #define TSK_CLEANUP_SLP 60 #define MIN_TSK_CLEANUP_SLP 1 static sm_ret_T dns_tsk_cleanup(sm_evthr_task_P _tsk); #if !MTA_USE_PTHREADS /* ** ToDo: Build a version for state threads... add macros to deal with the ** differences between event threads and state threads... */ /* HACK */ # define EVTHR_DEL 1 # define EVTHR_WAITQ 0 # define sm_evthr_task_P void * #endif /* !MTA_USE_PTHREADS */ /* ** ToDo: ** - fix hash key (query + type); done, but it's still ugly. ** - deal with partial reads (requeue, store state in dns_tsk) */ /* ** DNS_TSK_DEL -- delete a DNS task ** ** Parameters: ** dns_tsk -- DNS task context ** ** Return value: ** usual sm_error code */ sm_ret_T dns_tsk_del(dns_tsk_P dns_tsk) { if (NULL == dns_tsk) return SM_SUCCESS; if (is_valid_socket(dns_tsk->dnstsk_fd)) { close(dns_tsk->dnstsk_fd); dns_tsk->dnstsk_fd = INVALID_SOCKET; } SM_STR_FREE(dns_tsk->dnstsk_wr); SM_STR_FREE(dns_tsk->dnstsk_rd); #if MTA_USE_PTHREADS if (DNS_TSK_IS_FLAG(dns_tsk, DNS_TSK_FL_HAS_MTX)) { DNS_TSK_CLR_FLAG(dns_tsk, DNS_TSK_FL_HAS_MTX); (void) pthread_mutex_destroy(&dns_tsk->dnstsk_mutex); } #endif sm_free_size(dns_tsk, sizeof(*dns_tsk)); return SM_SUCCESS; } /* ** DNS_TSK_NEW -- make a dns_tsk ** ** Parameters: ** dns_mgr_ctx -- DNS manager context ** flags -- flags ** ipv4 -- IPv4 address of DNS resolver ** pdns_tsk -- (pointer to) DNS task context (output) ** ** Return value: ** usual sm_error code */ sm_ret_T dns_tsk_new(dns_mgr_ctx_P dns_mgr_ctx, uint flags, uint32_t ipv4, dns_tsk_P *pdns_tsk) { sm_ret_T ret; uint u; dns_tsk_P dns_tsk; #if MTA_USE_PTHREADS int r; #endif SM_REQUIRE(pdns_tsk != NULL); u = dns_mgr_ctx->dnsmgr_ntsks; if (u >= SM_DNS_MAX_TSKS) return sm_error_perm(SM_EM_DNS, SM_E_FULL); dns_tsk = (dns_tsk_P) sm_zalloc(sizeof(*dns_tsk)); if (NULL == dns_tsk) return sm_error_temp(SM_EM_DNS, ENOMEM); dns_tsk->dnstsk_fd = INVALID_SOCKET; dns_tsk->dnstsk_flags = flags & DNS_TSK_FL_OPT_MSK; dns_tsk->dnstsk_mgr = dns_mgr_ctx; dns_tsk->dnstsk_wr = sm_str_new(NULL, HFIXEDSZ + MAXPACKET, MAX_QUERY_SIZE); if (NULL == dns_tsk->dnstsk_wr) { ret = sm_error_temp(SM_EM_DNS, ENOMEM); goto error; } dns_tsk->dnstsk_rd = sm_str_new(NULL, HFIXEDSZ + MAXPACKET, MAX_QUERY_SIZE); if (NULL == dns_tsk->dnstsk_rd) { ret = sm_error_temp(SM_EM_DNS, ENOMEM); goto error; } /* Set up the socket description */ sm_memzero(&dns_tsk->dnstsk_sin, sizeof(dns_tsk->dnstsk_sin)); dns_tsk->dnstsk_sin.sin_family = AF_INET; dns_tsk->dnstsk_sin.sin_port = htons(DNS_PORT); sm_memcpy(&dns_tsk->dnstsk_sin.sin_addr.s_addr, &ipv4, sizeof(ipv4)); #if MTA_USE_PTHREADS r = pthread_mutex_init(&dns_tsk->dnstsk_mutex, SM_PTHREAD_MUTEXATTR); if (r != 0) { ret = sm_error_perm(SM_EM_DNS, r); goto error; } DNS_TSK_SET_FLAG(dns_tsk, DNS_TSK_FL_HAS_MTX); #endif /* MTA_USE_PTHREADS */ dns_tsk->dnstsk_fd = socket(dns_tsk->dnstsk_sin.sin_family, (flags & DNS_TSK_FL_USETCP) ? SOCK_STREAM : SOCK_DGRAM, 0); if (!is_valid_socket(dns_tsk->dnstsk_fd)) { ret = sm_error_temp(SM_EM_DNS, errno); goto error; } if ((flags & (DNS_TSK_FL_USETCP|DNS_TSK_FL_CONNECTUDP)) != 0) { if (connect(dns_tsk->dnstsk_fd, (sockaddr_P) &dns_tsk->dnstsk_sin, sizeof(sockaddr_in_T)) != 0) { ret = sm_error_temp(SM_EM_DNS, errno); goto error; } } ret = sm_fd_nonblock(dns_tsk->dnstsk_fd, true); if (sm_is_err(ret)) goto error; DNSTRQL_INIT(dns_tsk); dns_tsk->dnstsk_idx = u; dns_tsk->dnstsk_maxtimeouts = DNS_MAXTIMEOUTS; dns_mgr_ctx->dnsmgr_dnstsks[u] = dns_tsk; dns_mgr_ctx->dnsmgr_tskstatus[u] = DNSTSK_ST_INIT; ++dns_mgr_ctx->dnsmgr_ntsks; *pdns_tsk = dns_tsk; return SM_SUCCESS; error: (void) dns_tsk_del(dns_tsk); return ret; } /* ** DNS_TSK_START -- start all(?) DNS tasks ** ** Parameters: ** dns_mgr_ctx -- DNS manager context ** evthr_ctx -- Event thread system context ** ** Return value: ** usual sm_error code */ sm_ret_T dns_tsk_start(dns_mgr_ctx_P dns_mgr_ctx, sm_evthr_ctx_P evthr_ctx) { sm_ret_T ret; uint u; sm_evthr_task_P task; timeval_T sleept; for (u = 0; u < dns_mgr_ctx->dnsmgr_ntsks; u++) { task = NULL; ret = evthr_task_new(evthr_ctx, &task, EVTHR_EV_RD, dns_mgr_ctx->dnsmgr_dnstsks[u]->dnstsk_fd, NULL, dns_comm_tsk, (void *) dns_mgr_ctx->dnsmgr_dnstsks[u]); if (sm_is_err(ret)) goto error; dns_mgr_ctx->dnsmgr_tsk[u] = task; dns_mgr_ctx->dnsmgr_tskstatus[u] = DNSTSK_ST_OK; } ret = evthr_timeval(evthr_ctx, &sleept); sleept.tv_sec += TSK_CLEANUP_SLP; ret = evthr_task_new(evthr_ctx, &task, EVTHR_EV_SL, INVALID_FD, &sleept, dns_tsk_cleanup, (void *) dns_mgr_ctx); if (sm_is_err(ret)) goto error; dns_mgr_ctx->dnsmgr_cleanup = task; return ret; error: /* Is this ok?? How to properly terminate task?? */ for (u = 0; u < dns_mgr_ctx->dnsmgr_ntsks; u++) { task = dns_mgr_ctx->dnsmgr_tsk[u]; if (task != NULL) { evthr_task_del(evthr_ctx, task, THR_LOCK_UNLOCK); dns_mgr_ctx->dnsmgr_tsk[u] = NULL; } dns_mgr_ctx->dnsmgr_tskstatus[u] = DNSTSK_ST_NONE; } task = dns_mgr_ctx->dnsmgr_cleanup; if (task != NULL) { evthr_task_del(evthr_ctx, task, THR_LOCK_UNLOCK); dns_mgr_ctx->dnsmgr_cleanup = NULL; } return ret; } /* ** DNS_REQ_FREE -- free a DNS request, ** remove it from list if dns_mgr_ctx is not NULL. ** ** Parameters: ** dns_req -- DNS request ** dns_mgr_ctx -- DNS manager context ** ** Return value: ** usual sm_error code ** ** Locking: dns_mgr_ctx must be locked by caller. */ static sm_ret_T dns_req_free(dns_req_P dns_req, dns_mgr_ctx_P dns_mgr_ctx) { MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_req_free, dns_req=%p, ctx=%p, inlist=%d\n", dns_req, dns_mgr_ctx, NULL == dns_mgr_ctx ? -1 : DNSREQ_IS_INANYLIST(dns_req))); if (NULL == dns_req) return SM_SUCCESS; MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_req_free, dns_req=%p, key=%p, query=%p\n", dns_req, dns_req->dnsreq_key, dns_req->dnsreq_query)); SM_CSTR_FREE(dns_req->dnsreq_query); SM_STR_FREE(dns_req->dnsreq_key); if (dns_mgr_ctx != NULL) { if (DNSREQ_IS_ININCLIST(dns_req)) DNSIRQL_REMOVE(dns_mgr_ctx, dns_req); else if (DNSREQ_IS_INWAITLIST(dns_req)) DNSWRQL_REMOVE(dns_mgr_ctx, dns_req); } sm_free_size(dns_req, sizeof(*dns_req)); return SM_SUCCESS; } /* ** DNS_REQ_DEL -- free a DNS request ** (wrapper for bht_*(); still necessary?) ** ** Parameters: ** value -- DNS request ** key -- key (ignored) ** ctx -- DNS manager context ** ** Return value: ** usual sm_error code ** ** Locking: dns_mgr_ctx must be locked by caller. */ void dns_req_del(void *value, void *key, void *ctx) { MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_req_del, dns_req=%p\n", value)); (void) dns_req_free((dns_req_P) value, (dns_mgr_ctx_P) ctx); } /* ** DNS_CRT_KEY -- create a key for a DNS query ** ** Parameters: ** query -- DNS query ** type -- DNS type ** pkey -- (pointer to) key (output) ** pkeylen -- (pointer to) keylen (output) ** ** Return value: ** usual sm_error code */ static sm_ret_T dns_crt_key(sm_cstr_P query, dns_type_T type, sm_str_P *pkey, size_t *pkeylen) { sm_str_P key; size_t keylen; sm_ret_T ret; SM_REQUIRE(pkey != NULL); SM_REQUIRE(pkeylen != NULL); keylen = sm_cstr_getlen(query) + sizeof(type); key = sm_str_new(NULL, keylen, keylen + 4); if (NULL == key) { ret = sm_error_temp(SM_EM_DNS, ENOMEM); goto error; } ret = sm_str_scatn(key, (const char *) sm_cstr_data(query), sm_cstr_getlen(query)); if (sm_is_err(ret)) goto error; ret = sm_str_rm_trail(key, "."); if (sm_is_err(ret)) goto error; keylen -= ret; /* decrement keylen if '.' has been removed */ /* always lower case?! */ sm_str2lower(key); ret = sm_str_scatn(key, (const char *) &type, sizeof(type)); if (sm_is_err(ret)) goto error; SM_ASSERT(keylen == sm_str_getlen(key)); *pkey = key; *pkeylen = keylen; return SM_SUCCESS; error: if (key != NULL) sm_str_free(key); *pkey = NULL; *pkeylen = 0; return ret; } /* ** DNS_TSK_SELECT -- select a DNS task ** ** Parameters: ** dns_mgr_ctx -- DNS manager context ** now -- current time ** wakeup -- "wake up" the selected DNS task? ** pidx -- (pointer to) selected DNS task (output) ** locktype -- kind of locking ** ** Return value: ** usual sm_error code ** ** Locking: locks dns_mgr_ctx if requested */ static sm_ret_T dns_tsk_select(dns_mgr_ctx_P dns_mgr_ctx, time_T now, bool wakeup, ushort *pidx, thr_lock_T locktype) { sm_ret_T ret; #if MTA_USE_PTHREADS int r; #endif ushort u, j, i; time_T chg; #if MTA_USE_PTHREADS if (thr_lock_it(locktype)) { r = pthread_mutex_lock(&dns_mgr_ctx->dnsmgr_mutex); SM_LOCK_OK(r); if (r != 0) { sm_log_write(dns_mgr_ctx->dnsmgr_lctx, LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER, SM_LOG_CRIT, 4, "sev=CRIT, func=dns_tsk_select, lock=%d", r); return sm_error_perm(SM_EM_DNS, r); } } #endif /* MTA_USE_PTHREADS */ /* round robin?? or select server with least open queries? */ chg = TIME_T_MAX; i = USHRT_MAX; u = dns_mgr_ctx->dnsmgr_ctsk; ret = SM_SUCCESS; for (j = 0; j < SM_DNS_MAX_TSKS; j++) { /* found a working DNS task? */ if (dns_mgr_ctx->dnsmgr_tsk[u] != NULL && dns_mgr_ctx->dnsmgr_tskstatus[u] == DNSTSK_ST_OK) break; if (dns_mgr_ctx->dnsmgr_tskchg[u] > 0 && dns_mgr_ctx->dnsmgr_tskstatus[u] == DNSTSK_ST_DEAD && dns_mgr_ctx->dnsmgr_tskchg[u] < chg) { chg = dns_mgr_ctx->dnsmgr_tskchg[u]; i = u; } u = ++dns_mgr_ctx->dnsmgr_ctsk; if (u >= dns_mgr_ctx->dnsmgr_ntsks) u = dns_mgr_ctx->dnsmgr_ctsk = 0; } if (j >= SM_DNS_MAX_TSKS) { if (i < dns_mgr_ctx->dnsmgr_ntsks) { u = i; dns_mgr_ctx->dnsmgr_tskstatus[u] = DNSTSK_ST_OK; dns_mgr_ctx->dnsmgr_tskchg[u] = now; sm_log_write(dns_mgr_ctx->dnsmgr_lctx, LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER, SM_LOG_WARN, 4, "sev=WARN, func=dns_req_add, status=all dns servers considered unresponsive, action=selected one with oldest status change"); } else { u = dns_mgr_ctx->dnsmgr_ctsk; sm_log_write(dns_mgr_ctx->dnsmgr_lctx, LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER, SM_LOG_ERROR, 1, "sev=ERROR, func=dns_req_add, status=all dns servers considered unresponsive, action=selected one round robin"); } } /* ** Ignore return value: in the worst case the request will ** not be sent to a DNS server but a timeout error will be ** returned to the caller, hence we'll ignore any error ** (for now). */ if (wakeup) { dns_tsk_P dns_tsk; dns_tsk = dns_mgr_ctx->dnsmgr_dnstsks[u]; #if MTA_USE_PTHREADS if (thr_lock_it_s1(locktype)) { r = pthread_mutex_lock(&dns_tsk->dnstsk_mutex); SM_LOCK_OK(r); if (r != 0) { sm_log_write(dns_mgr_ctx->dnsmgr_lctx, LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER, SM_LOG_CRIT, 4, "sev=CRIT, func=dns_tsk_select, lock_tsk=%d" , r); ret = sm_error_perm(SM_EM_DNS, r); goto unlock; } } #endif /* MTA_USE_PTHREADS */ /* todo: access should be locked */ if (!DNS_TSK_IS_FLAG(dns_tsk, DNS_TSK_FL_WR_EN)) { (void) evthr_en_wr(dns_mgr_ctx->dnsmgr_tsk[u]); DNS_TSK_SET_FLAG(dns_tsk, DNS_TSK_FL_WR_EN); } #if MTA_USE_PTHREADS if (thr_unl_always_s1(locktype)) { r = pthread_mutex_unlock(&dns_tsk->dnstsk_mutex); SM_ASSERT(r == 0); } #endif /* MTA_USE_PTHREADS */ } #if MTA_USE_PTHREADS unlock: if (thr_unl_always(locktype)) { r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex); SM_ASSERT(r == 0); } #endif /* MTA_USE_PTHREADS */ if (pidx != NULL) *pidx = u; return ret; } /* ** DNS_REQ_ADD -- Add a DNS request to the queue. ** This adds a DNS request to the hash table and if it isn't yet in the ** request list also to that. ** ** Parameters: ** dns_mgr_ctx -- DNS manager context ** query -- DNS query (MUST be '\0' terminated; SM_CSTR_DUP()ed) ** type -- DNS type ** timeout -- timeout (if 0: use default) ** fct -- callback to invoke with DNS result ** ctx -- context to pass to callback ** ** Return value: ** usual sm_error code: an error will only be returned ** if the request was NOT added to the request list and hence ** the caller can't expect a reply. ** ** Locking: dns_mgr_ctx is locked as necessary. */ sm_ret_T dns_req_add(dns_mgr_ctx_P dns_mgr_ctx, sm_cstr_P query, dns_type_T type, uint timeout, dns_callback_F *fct, void *ctx) { sm_ret_T ret; #if MTA_USE_PTHREADS int r; #endif bool add2ht; dns_req_P dns_req; bht_entry_P bhte; sm_str_P key; size_t keylen; time_T now; timeval_T nowt; SM_REQUIRE(query != NULL); dns_req = (dns_req_P) sm_zalloc(sizeof(*dns_req)); if (NULL == dns_req) return sm_error_temp(SM_EM_DNS, ENOMEM); ret = SM_SUCCESS; key = NULL; add2ht = false; dns_req->dnsreq_query = SM_CSTR_DUP(query); dns_req->dnsreq_type = type; (void) evthr_timeval(dns_mgr_ctx->dnsmgr_cleanup->evthr_t_ctx, &nowt); #if DNS_STATISTICS dns_req->dnsreq_startt = nowt; #endif now = nowt.tv_sec; dns_req->dnsreq_endt = now + ((timeout == 0) ? dns_mgr_ctx->dnsmgr_timeout : timeout); MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_req_add, now=%ld, endt=%ld\n", (long) now, (long) dns_req->dnsreq_endt)); /* ** Create key from query and type... this is still ugly... ** Note: we can't use the output of res_mkquery... */ ret = dns_crt_key(query, type, &key, &keylen); if (sm_is_err(ret)) goto error; dns_req->dnsreq_key = key; dns_req->dnsreq_fct = fct; dns_req->dnsreq_ctx = ctx; #if MTA_USE_PTHREADS r = pthread_mutex_lock(&dns_mgr_ctx->dnsmgr_mutex); SM_LOCK_OK(r); if (r != 0) { sm_log_write(dns_mgr_ctx->dnsmgr_lctx, LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER, SM_LOG_CRIT, 4, "sev=CRIT, func=dns_req_add, lock=%d", r); ret = sm_error_perm(SM_EM_DNS, r); goto error; } #endif /* Is there already a request for this? */ bhte = bht_locate(dns_mgr_ctx->dnsmgr_req_ht, (const char *) sm_str_data(key), (uint) sm_str_getlen(key)); add2ht = (NULL == bhte); /* Always add the request to the hash table so it can be answered */ ret = bht_add(dns_mgr_ctx->dnsmgr_req_ht, (char *) sm_str_data(dns_req->dnsreq_key), (uint) sm_str_getlen(dns_req->dnsreq_key), dns_req, &bhte); if (sm_is_err(ret)) { sm_log_write(dns_mgr_ctx->dnsmgr_lctx, LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER, SM_LOG_ERROR, 4, "sev=ERROR, func=dns_req_add, bhte=%p, bht_add=%r, key='%s'[%p], len=%d\n", bhte, ret, sm_str_data(dns_req->dnsreq_key), dns_req->dnsreq_key, sm_str_getlen(dns_req->dnsreq_key)); } else if (add2ht) { /* entry was not in hash table: add it to the list */ DNSIRQL_INSERT_TAIL(dns_mgr_ctx, dns_req); DNSREQ_SET_INLIST(dns_req, DNSREQ_FL_IN_INC); } MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_req_add, bht_add bhte=%p, ret=%x, key='%s'[%p], len=%d\n", bhte, ret, sm_str_data(dns_req->dnsreq_key), dns_req->dnsreq_key, sm_str_getlen(dns_req->dnsreq_key))); #if MTA_USE_PTHREADS r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex); SM_ASSERT(r == 0); /* ignore error; see Return values above for an explanation */ if (sm_is_success(ret) && add2ht) (void) dns_tsk_select(dns_mgr_ctx, now, true, NULL, THR_LOCK_UNLOCK|THR_LOCK_UNLOCK_S1); if (sm_is_success(ret) && dns_mgr_ctx->dnsmgr_cleanup != NULL) { timeval_T slpt, nowt, newt; /* set timeout for cleanup task */ (void) evthr_timeval(dns_mgr_ctx->dnsmgr_cleanup->evthr_t_ctx, &nowt); slpt.tv_usec = 0; slpt.tv_sec = dns_mgr_ctx->dnsmgr_timeout; timeradd(&nowt, &slpt, &newt); (void) evthr_new_sl(dns_mgr_ctx->dnsmgr_cleanup, newt, false); } #endif /* MTA_USE_PTHREADS */ return ret; error: if (dns_req != NULL) dns_req_free(dns_req, NULL); return ret; } /* context for dns_bht2reql_fct() */ struct bht2reql_S { dns_mgr_ctx_P b2r_dns_mgr_ctx; dns_rql_P b2r_rql_hd; }; typedef struct bht2reql_S bht2reql_T, *bht2reql_P; /* ** DNS_BHT2REQL_FCT -- move DNS requests from hash table into local list. ** callback for bht_rm_all() used in dns_bht2reql() below. ** ** Parameters: ** value -- DNS request ** key -- key (ignored, for compatibility with bhfree_F) ** ctx -- context (see above: bht2reql_P) ** ** Return value: ** usual sm_error code ** ** Locking: dns_mgr_ctx must be locked by caller. */ static void dns_bht2reql_fct(void *value, void *key, void *ctx) { dns_req_P dns_req; bht2reql_P bht2reql; dns_mgr_ctx_P dns_mgr_ctx; dns_rql_P rql_hd; dns_req = (dns_req_P) value; bht2reql = (bht2reql_P) ctx; if (NULL == dns_req || bht2reql == NULL) { /* abort for now */ SM_ASSERT(dns_req != NULL); SM_ASSERT(bht2reql != NULL); MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=ERROR, func=dns_bht2reql_fct, dns_req=%p, bht2reql=%p\n", dns_req, bht2reql)); return; } dns_mgr_ctx = bht2reql->b2r_dns_mgr_ctx; rql_hd = bht2reql->b2r_rql_hd; if (NULL == dns_mgr_ctx || rql_hd == NULL) { /* abort for now */ SM_ASSERT(dns_mgr_ctx != NULL); SM_ASSERT(rql_hd != NULL); MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=ERROR, func=dns_bht2reql_fct, dns_mgr_ctx=%p, rql_hd=%p\n", dns_mgr_ctx, rql_hd)); return; } MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_bht2reql_fct, dns_req=%p, inlist=%d\n", dns_req, DNSREQ_IS_INANYLIST(dns_req))); /* Remove entry from dns_mgr list (if it is in that list) */ if (DNSREQ_IS_ININCLIST(dns_req)) { DNSIRQL_REMOVE(dns_mgr_ctx, dns_req); DNSREQ_CLR_INLIST(dns_req); } else if (DNSREQ_IS_INWAITLIST(dns_req)) { /* possible?? */ DNSWRQL_REMOVE(dns_mgr_ctx, dns_req); DNSREQ_CLR_INLIST(dns_req); } /* Add to local list */ RQL_INSERT_TAIL(rql_hd, dns_req); } /* ** DNS_BHT2REQL -- move DNS requests that match key from hash table ** into local list. ** ** Parameters: ** dns_mgr_ctx -- DNS manager context ** key -- key to match ** keylen -- length of key ** rql_hd -- head of DNS request list to which items are moved ** ** Return value: ** usual sm_error code ** ** Locking: dns_mgr_ctx must be locked by caller. */ static sm_ret_T dns_bht2reql(dns_mgr_ctx_P dns_mgr_ctx, sm_str_P key, size_t keylen, dns_rql_P rql_hd) { bht2reql_T bht2reql; SM_ASSERT(dns_mgr_ctx != NULL); SM_ASSERT(rql_hd != NULL); SM_ASSERT(key != NULL); bht2reql.b2r_dns_mgr_ctx = dns_mgr_ctx; bht2reql.b2r_rql_hd = rql_hd; /* ** Remove all entries that match key/keylen from hash table; ** remove them from DNS manager list, add to local list. ** Use the bht_rm_all() function callback to achieve this ** instead of accessing the data directly. */ bht_rm_all(dns_mgr_ctx->dnsmgr_req_ht, (const char *) sm_str_data(key), keylen, dns_bht2reql_fct, &bht2reql); return SM_SUCCESS; } /* ** DNS_TSK_RD -- DNS communication task: read replies from DNS server ** ** Parameters: ** tsk -- evthr task ** ** Return value: ** usual sm_error code ** ** Locking: dns_mgr_ctx is locked when necessary. */ static sm_ret_T dns_tsk_rd(sm_evthr_task_P tsk) { sm_ret_T ret, res; int r; dns_tsk_P dns_tsk; dns_res_P dns_res; dns_req_P dns_req, dns_req_next; dns_mgr_ctx_P dns_mgr_ctx; bht_entry_P bhte; uchar query[MAXHOSTNAMELEN]; ushort type; sm_str_P key; size_t keylen; dns_rql_T rql_hd; #if MTA_USE_PTHREADS SM_IS_EVTHR_TSK(tsk); #endif dns_tsk = (dns_tsk_P) tsk->evthr_t_actx; if (NULL == dns_tsk) return EVTHR_DEL; if (!is_valid_fd(tsk->evthr_t_fd)) return EVTHR_DEL; key = NULL; res = dns_receive(dns_tsk); MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_tsk_rd, receive=%x\n", res)); if (sm_is_err(res)) { /* XXX deal with it... */; ret = res; goto error; } if (res == 0) goto done; ret = dns_res_new(MAXRESHOSTS, &dns_res); if (sm_is_err(ret)) { /* XXX deal with it... */; goto error; } ret = dns_decode(dns_tsk->dnstsk_rd, query, sizeof(query), &type, dns_res); MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_tsk_rd, decode=%x, query=%s\n", ret, query)); #if MTA_LIBDNS_TEST /* introduce some errors for some requests; ToDo: add others! */ #define SMDT_ARPA_TEMP "190.0.0.127.in-addr.arpa" #define SMDT_ARPA_PERM "191.0.0.127.in-addr.arpa" #define SMDT_ARPA_EINVAL "192.0.0.127.in-addr.arpa" #define SMDT_MX_EINVAL "mxinvalid.sm9.org" if (sm_memeq(query, SMDT_ARPA_TEMP, sizeof(SMDT_ARPA_TEMP) - 1)) dns_res->dnsres_ret = DNSR_TEMP; else if (sm_memeq(query, SMDT_ARPA_PERM, sizeof(SMDT_ARPA_PERM) - 1)) dns_res->dnsres_ret = DNSR_PERM; else if (sm_memeq(query, SMDT_ARPA_EINVAL, sizeof(SMDT_ARPA_EINVAL) - 1)) dns_res->dnsres_ret = sm_error_perm(SM_EM_DNS, EINVAL); else if (sm_memeq(query, SMDT_MX_EINVAL, sizeof(SMDT_MX_EINVAL) - 1)) dns_res->dnsres_ret = DNSR_MXINVALID; #endif /* MTA_LIBDNS_TEST */ dns_mgr_ctx = dns_tsk->dnstsk_mgr; /* Create key from query and type... this is still ugly... */ keylen = strlen((char *) query) + sizeof(type); key = sm_str_new(NULL, keylen, keylen + 4); if (NULL == key) { ret = sm_error_temp(SM_EM_DNS, ENOMEM); goto error; } ret = sm_str_scatn(key, (const char *) query, strlen((char *) query)); if (sm_is_err(ret)) goto error; ret = sm_str_rm_trail(key, "."); if (sm_is_err(ret)) goto error; keylen -= ret; /* decrement keylen if '.' has been removed */ /* always lower case?! */ sm_str2lower(key); ret = sm_str_scatn(key, (const char *) &type, sizeof(type)); if (sm_is_err(ret)) goto error; SM_ASSERT(keylen == sm_str_getlen(key)); RQL_INIT(&rql_hd); #if MTA_USE_PTHREADS r = pthread_mutex_lock(&dns_mgr_ctx->dnsmgr_mutex); SM_LOCK_OK(r); if (r != 0) { sm_log_write(dns_mgr_ctx->dnsmgr_lctx, LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER, SM_LOG_CRIT, 4, "sev=CRIT, func=dns_tsk_rd, lock=%d", r); ret = sm_error_perm(SM_EM_DNS, r); goto error; } #endif /* MTA_USE_PTHREADS */ /* ** A two step approach is required here: ** 1. Build a new list of requests (removing them from dns_mgr_ctx: ** request list and hash table) ** 2. Use that list to invoke the callbacks ** This is required to get around locking/concurrent access issues: ** the table/list can't be locked when invoking the callback ** since otherwise the callback can't invoke a function like ** dns_req_add() which locks the dns_mgr_ctx table/list. ** ** First lookup the entry and check whether the request id ** matches the result id. If it doesn't: ignore it. ** Note: bhtable prepends new entries, hence it is necessary to ** walk to the end of the chain. This is ugly and slow; a better ** method should be used! */ bhte = bht_locate(dns_mgr_ctx->dnsmgr_req_ht, (const char *) sm_str_data(key), keylen); dns_req = NULL; while (bhte != NULL) { dns_req = (dns_req_P) (bhte->bhe_value); SM_ASSERT(dns_req != NULL); if (dns_req->dnsreq_id == dns_res->dnsres_id) break; bhte = bhte->bhe_next; #if 0 if (dns_req->dnsreq_id != 0) { sm_io_fprintf(smioerr, "sev=WARN, func=dns_tsk_rd, dns_req=%p, dns_req_id=%d, dnsres_id=%d, status=id_mismatch, where=in_while\n", dns_req, NULL == dns_req ? -1 : dns_req->dnsreq_id, dns_res->dnsres_id); } #endif } if (NULL == bhte || dns_req == NULL) { #if MTA_USE_PTHREADS r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex); SM_ASSERT(r == 0); /* COMPLAIN if error */ #endif if (dns_req != NULL) { sm_io_fprintf(smioerr, "sev=WARN, func=dns_tsk_rd, dns_req=%p, dns_req_id=%d, dnsres_id=%d, status=id_mismatch/not_found, key=%#N\n", dns_req, NULL == dns_req ? -1 : dns_req->dnsreq_id, dns_res->dnsres_id, key); } goto freeit; } ret = dns_bht2reql(dns_mgr_ctx, key, keylen, &rql_hd); /* result ignored for now; it's always SUCCESS */ #if MTA_USE_PTHREADS r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex); SM_ASSERT(r == 0); /* COMPLAIN if error */ #endif /* Go through list of "done" requests and invoke callbacks */ for (dns_req = RQL_FIRST(&rql_hd); dns_req != RQL_END(&rql_hd); dns_req = dns_req_next) { MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_tsk_rd, dns_req=%p, inlist=%d\n", dns_req, DNSREQ_IS_INANYLIST(dns_req))); dns_req_next = RQL_NEXT(dns_req); if (dns_req->dnsreq_id != 0 && dns_req->dnsreq_id != dns_res->dnsres_id) { sm_log_write(dns_mgr_ctx->dnsmgr_lctx, LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER, SM_LOG_WARN, 7, "sev=WARN, func=dns_tsk_rd, dns_req_id=%d, dnsrpl_id=%d\n", dns_req->dnsreq_id, dns_res->dnsres_id); } if (dns_req->dnsreq_fct != NULL) { dns_res->dnsres_query = dns_req->dnsreq_query; /* Ignore return value? */ dns_req->dnsreq_fct(dns_res, dns_req->dnsreq_ctx); } /* Unlink entry from list */ RQL_REMOVE(&rql_hd, dns_req); ret = dns_req_free(dns_req, NULL); } freeit: /* Free dns_res */ ret = dns_res_free(dns_res); sm_str_free(key); done: return EVTHR_WAITQ; error: MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=ERROR, func=dns_tsk_rd, ret=%x\n", ret)); if (key != NULL) sm_str_free(key); return ret; } /* ** DNS_REQ_INS_WRQL -- insert DNS request into wait list ** ** Parameters: ** dns_mgr_ctx -- DNS manager context ** dns_req -- DNS request ** ** Return value: ** SM_SUCCESS ** ** Locking: dns_mgr_ctx must be locked by caller. */ sm_ret_T dns_req_ins_wrql(dns_mgr_ctx_P dns_mgr_ctx, dns_req_P dns_req) { dns_req_P dns_reqh; if (DNSWRQL_EMPTY(dns_mgr_ctx)) { DNSWRQL_INSERT_TAIL(dns_mgr_ctx, dns_req); MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_tsk_wr, writelist=empty\n")); } else { /* ** Insert dns_req at the right place in the list: ** start from the back and go to the front until ** an entry is reached whose "end time" is not ** greater than the one of the entry to be inserted. */ dns_reqh = DNSWRQL_LAST(dns_mgr_ctx); while (dns_reqh != DNSWRQL_FIRST(dns_mgr_ctx) && dns_req->dnsreq_endt < dns_reqh->dnsreq_endt) { dns_reqh = DNSWRQL_PREV(dns_reqh); } MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_tsk_wr, req=%p, reqh=%p, req->endt=%ld, reqh->ednt=%ld\n", dns_req, dns_reqh, (long) dns_req->dnsreq_endt, (long) dns_reqh->dnsreq_endt)); if (dns_req->dnsreq_endt >= dns_reqh->dnsreq_endt) DNSWRQL_INSERT_AFTER(dns_mgr_ctx, dns_reqh, dns_req); else DNSWRQL_INSERT_BEFORE(dns_mgr_ctx, dns_reqh, dns_req); } DNSREQ_SET_INLIST(dns_req, DNSREQ_FL_IN_WAIT); return SM_SUCCESS; } /* ** DNS_TSK_WR -- DNS communication task: send queries to DNS server ** ** Parameters: ** tsk -- evthr task ** ** Return value: ** usual sm_error code */ static sm_ret_T dns_tsk_wr(sm_evthr_task_P tsk) { sm_ret_T ret; int r; dns_tsk_P dns_tsk; dns_mgr_ctx_P dns_mgr_ctx; dns_req_P dns_req; bool empty; SM_IS_EVTHR_TSK(tsk); dns_tsk = (dns_tsk_P) tsk->evthr_t_actx; if (NULL == dns_tsk) return EVTHR_DEL; if (!is_valid_fd(tsk->evthr_t_fd)) return EVTHR_DEL; dns_mgr_ctx = dns_tsk->dnstsk_mgr; dns_req = NULL; do { /* get value alternatively from the two lists?? */ #if MTA_USE_PTHREADS r = pthread_mutex_lock(&dns_mgr_ctx->dnsmgr_mutex); SM_LOCK_OK(r); if (r != 0) { sm_log_write(dns_mgr_ctx->dnsmgr_lctx, LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER, SM_LOG_CRIT, 4, "sev=CRIT, func=dns_tsk_wr, lock=%d", r); ret = sm_error_perm(SM_EM_DNS, r); goto error; } #endif #if MTA_USE_DNSTSK_LIST /* ** Disabled: currently no entries are added ** to the task-specific queues, hence we don't need to ** check them. */ #if MTA_USE_PTHREADS r = pthread_mutex_lock(&dns_tsk->dnstsk_mutex); SM_LOCK_OK(r); if (r != 0) { sm_log_write(dns_mgr_ctx->dnsmgr_lctx, LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER, SM_LOG_CRIT, 4, "sev=CRIT, func=dns_tsk_wr, lock=%d", r); ret = sm_error_perm(SM_EM_DNS, r); goto error; } #endif /* first: try to get entry from task-specific list */ empty = DNSTRQL_EMPTY(dns_tsk); MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_tsk_wr, empty=%d\n", empty)); if (!empty) { /* move first element from incoming to wait list */ dns_req = DNSTRQL_FIRST(dns_tsk); DNSTRQL_REMOVE(dns_tsk, dns_req); dns_req->dnsreq_idx = dns_tsk->dnstsk_idx; (void) dns_req_ins_wrql(dns_mgr_ctx, dns_req); } #if MTA_USE_PTHREADS r = pthread_mutex_unlock(&dns_tsk->dnstsk_mutex); SM_ASSERT(r == 0); /* COMPLAIN if error */ #endif #else /* MTA_USE_DNSTSK_LIST */ empty = true; #endif /* MTA_USE_DNSTSK_LIST */ if (empty) { /* if task-specific list is empty, try "global" list */ empty = DNSIRQL_EMPTY(dns_mgr_ctx); MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_tsk_wr, empty=%d\n", empty)); if (!empty) { /* move element from incoming to wait list */ dns_req = DNSIRQL_FIRST(dns_mgr_ctx); DNSIRQL_REMOVE(dns_mgr_ctx, dns_req); dns_req->dnsreq_idx = dns_tsk->dnstsk_idx; (void) dns_req_ins_wrql(dns_mgr_ctx, dns_req); } } #if MTA_USE_PTHREADS r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex); SM_ASSERT(r == 0); /* COMPLAIN if error */ #endif if (empty) { #if MTA_USE_PTHREADS r = pthread_mutex_lock(&dns_tsk->dnstsk_mutex); SM_LOCK_OK(r); if (r != 0) { sm_log_write(dns_mgr_ctx->dnsmgr_lctx, LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER, SM_LOG_CRIT, 4, "sev=CRIT, func=dns_tsk_select, lock_tsk=%d" , r); } DNS_TSK_CLR_FLAG(dns_tsk, DNS_TSK_FL_WR_EN); r = pthread_mutex_unlock(&dns_tsk->dnstsk_mutex); SM_ASSERT(r == 0); #endif /* MTA_USE_PTHREADS */ /* Turn off WR, dns_req_add() will turn it on if req. */ return EVTHR_WAITQ|evthr_r_no(EVTHR_EV_WR); } MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_wr, req=%p\n", dns_req)); /* Extract query from dns_req and send it to DNS server */ do { /* Note: dnsreq_query must be '\0' terminated! */ r = res_mkquery(QUERY, (const char *) sm_cstr_data( dns_req->dnsreq_query), C_IN, dns_req->dnsreq_type, NULL, 0, NULL, sm_str_data(dns_tsk->dnstsk_wr), (int) sm_str_getsize(dns_tsk->dnstsk_wr)); if (r == -1) { size_t len; len = sm_str_getsize(dns_tsk->dnstsk_wr) * 2; ret = sm_str_space(dns_tsk->dnstsk_wr, len); if (sm_is_err(ret)) goto error; } else { HEADER *hp; hp = (HEADER *) sm_str_data(dns_tsk->dnstsk_wr); dns_req->dnsreq_id = hp->id; } } while (r == -1); SM_ASSERT(r >= 0); SM_STR_SETLEN(dns_tsk->dnstsk_wr, (uint)r); ret = dns_send(dns_tsk); MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_wr, send=%x\n", ret)); } while (sm_is_success(ret) && 0 == sm_write_wait(dns_tsk->dnstsk_fd, 0)); #if 0 /* ** Don't terminate the task just because dns_send() failed ** How to deal with this properly? (see below) */ if (sm_is_err(ret)) goto error; #endif /* 0 */ return EVTHR_WAITQ; error: /* ** XXX need to determine whether this is "real" error, ** i.e., one that actually will persist and hence it makes ** sense to terminate this task. */ return EVTHR_WAITQ; /* return ret; */ } #if MTA_LIBDNS_DEBUG sm_ret_T printreq(bht_entry_P e, void *ctx) { if (e != NULL) sm_io_fprintf(smioerr, "e=%p, ctx=%p\n", e, ctx); return 0; } #endif /* ** DNS_TSK_CLEANUP -- deal with requests that have not been answered. ** This should be run whenever a request times out. ** ** Parameters: ** tsk -- evthr task ** ** Return value: ** usual sm_error code */ static sm_ret_T dns_tsk_cleanup(sm_evthr_task_P tsk) { sm_ret_T ret; int r; dns_mgr_ctx_P dns_mgr_ctx; dns_req_P dns_req, dns_req_next; dns_res_T dns_res; time_T now; timeval_T sleept; uint delay; sm_str_P key; size_t keylen; dns_rql_T rql_hd; SM_IS_EVTHR_TSK(tsk); dns_mgr_ctx = (dns_mgr_ctx_P) tsk->evthr_t_actx; if (NULL == dns_mgr_ctx) return EVTHR_DEL; ret = evthr_timeval(tsk->evthr_t_ctx, &sleept); now = sleept.tv_sec; delay = 0; tsk->evthr_t_sleep.tv_usec = sleept.tv_usec; RQL_INIT(&rql_hd); dns_req_next = NULL; #if MTA_USE_PTHREADS r = pthread_mutex_lock(&dns_mgr_ctx->dnsmgr_mutex); SM_LOCK_OK(r); if (r != 0) { sm_log_write(dns_mgr_ctx->dnsmgr_lctx, LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER, SM_LOG_CRIT, 4, "sev=CRIT, func=dns_tsk_cleanup, lock=%d", r); ret = sm_error_perm(SM_EM_DNS, r); goto error; } #endif while (!DNSWRQL_EMPTY(dns_mgr_ctx)) { dns_req = DNSWRQL_FIRST(dns_mgr_ctx); if (dns_req == dns_req_next) { sm_log_write(dns_mgr_ctx->dnsmgr_lctx, LIBDNS_LCAT_RESOLVER, LIBDNS_LMOD_RESOLVER, SM_LOG_ERROR, 4, "sev=ERROR, func=dns_cleanup, status=loop, dns_req=%p, dns_req_next=%p\n", dns_req, dns_req_next); SM_ASSERT(dns_req != dns_req_next); break; } dns_req_next = dns_req; MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_cleanup, dns_req=%p, dns_req_next=%p\n", dns_req, dns_req_next)); MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_cleanup, now=%ld, endt=%ld\n", (long) now, (long) dns_req->dnsreq_endt)); /* Stop the loop if the timeout hasn't been reached yet */ if (now <= dns_req->dnsreq_endt) { delay = dns_req->dnsreq_endt - now; if (delay == 0) delay = MIN_TSK_CLEANUP_SLP; break; } if (dns_req->dnsreq_tries < dns_mgr_ctx->dnsmgr_retries) { #if DNS_STATISTICS dns_req->dnsreq_startt = sleept; #endif dns_req->dnsreq_endt = now + dns_mgr_ctx->dnsmgr_timeout; DNSWRQL_REMOVE(dns_mgr_ctx, dns_req); DNSREQ_CLR_INLIST(dns_req); DNSIRQL_INSERT_TAIL(dns_mgr_ctx, dns_req); DNSREQ_SET_INLIST(dns_req, DNSREQ_FL_IN_INC); ++dns_req->dnsreq_tries; /* how to avoid repeated activations? */ (void) dns_tsk_select(dns_mgr_ctx, now, true, NULL, THR_NO_LOCK|THR_LOCK_UNLOCK_S1); continue; } /* Create key from query and type... still ugly... */ ret = dns_crt_key(dns_req->dnsreq_query, dns_req->dnsreq_type, &key, &keylen); if (sm_is_err(ret)) goto errunl; MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_cleanup, dns_req=%p, key='%s', keylen=%d\n", dns_req, sm_str_data(key), keylen)); /* ** Remove dns_req from hash table. ** Notice: this removes all entries that match the key ** and moves them to our local list. ** ** Note: this creates a problem: should we requeue ** those requests? This is just a timeout, ** maybe later on it actually works. */ ret = dns_bht2reql(dns_mgr_ctx, key, keylen, &rql_hd); #if MTA_LIBDNS_DEBUG if (dns_req == DNSWRQL_FIRST(dns_mgr_ctx)) bht_walk(dns_mgr_ctx->dnsmgr_req_ht, printreq, key); #endif SM_ASSERT(dns_req != DNSWRQL_FIRST(dns_mgr_ctx)); sm_str_free(key); } MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_cleanup, empty=%d, delay=%d\n", DNSWRQL_EMPTY(dns_mgr_ctx), delay)); if (delay == 0 && !DNSWRQL_EMPTY(dns_mgr_ctx)) { dns_req = DNSWRQL_FIRST(dns_mgr_ctx); /* make sure the result will be > 0 [unsigned arithmetic] */ if (now < dns_req->dnsreq_endt) delay = dns_req->dnsreq_endt - now; if (delay == 0) delay = MIN_TSK_CLEANUP_SLP; MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_cleanup, where=not-empty, now=%ld, endt=%ld\n", (long) now, (long) dns_req->dnsreq_endt)); } MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_cleanup, where=end, delay=%d\n", delay)); if (delay == 0) delay = TSK_CLEANUP_SLP; #if MTA_USE_PTHREADS r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex); SM_ASSERT(r == 0); /* COMPLAIN if error */ #endif /* Go through list of "done" requests and invoke callbacks */ for (dns_req = RQL_FIRST(&rql_hd); dns_req != RQL_END(&rql_hd); dns_req = dns_req_next) { ushort u; dns_tsk_P dns_tsk; dns_req_next = RQL_NEXT(dns_req); MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_cleanup, dns_req=%p, fct=%p\n", dns_req, dns_req->dnsreq_fct)); if ((u = dns_req->dnsreq_idx) < dns_mgr_ctx->dnsmgr_ntsks && (dns_tsk = dns_mgr_ctx->dnsmgr_dnstsks[u]) != NULL && dns_tsk->dnstsk_maxtimeouts > 0 && (++dns_tsk->dnstsk_timeouts > dns_tsk->dnstsk_maxtimeouts)) { dns_mgr_ctx->dnsmgr_tskstatus[u] = DNSTSK_ST_DEAD; dns_mgr_ctx->dnsmgr_tskchg[u] = now; dns_tsk->dnstsk_timeouts = 0; } /* Unlink entry from list */ if (dns_req->dnsreq_fct != NULL) { /* Return a timeout to caller... fill in more data? */ sm_memzero(&dns_res, sizeof(dns_res)); dns_res.dnsres_query = dns_req->dnsreq_query; dns_res.dnsres_ret = DNSR_TIMEOUT; dns_res.dnsres_qtype = dns_req->dnsreq_type; DRESL_INIT(&dns_res); /* Ignore return value? */ dns_req->dnsreq_fct(&dns_res, dns_req->dnsreq_ctx); } RQL_REMOVE(&rql_hd, dns_req); ret = dns_req_free(dns_req, NULL); } tsk->evthr_t_sleep.tv_sec = sleept.tv_sec + delay; return EVTHR_SLPQ; errunl: #if MTA_USE_PTHREADS r = pthread_mutex_unlock(&dns_mgr_ctx->dnsmgr_mutex); SM_ASSERT(r == 0); /* COMPLAIN if error */ #endif error: /* Cleanup? */ tsk->evthr_t_sleep.tv_sec = sleept.tv_sec + delay; return EVTHR_SLPQ; /* XXX Always? */ } /* ** DNS_COMM_TSK -- DNS communication task: calls functions to perform ** reads and writes. ** This runs as task in the event thread system. ** ** Parameters: ** tsk -- evthr task ** ** Return value: ** usual sm_error code */ sm_ret_T dns_comm_tsk(sm_evthr_task_P tsk) { sm_ret_T ret; dns_tsk_P dns_tsk; SM_IS_EVTHR_TSK(tsk); dns_tsk = (dns_tsk_P) tsk->evthr_t_actx; MTA_LIBDNS_DBG_DPRINTF((smioerr, "sev=DBG, func=dns_comm, dns_tsk=%p, want=%x, got=%x\n", dns_tsk, tsk->evthr_t_rqevf, tsk->evthr_t_evocc)); if (NULL == dns_tsk) return EVTHR_DEL; if (is_valid_fd(tsk->evthr_t_fd)) { ret = EVTHR_WAITQ; if (evthr_got_wr(tsk)) ret = dns_tsk_wr(tsk); /* XXX check ret here? */ /* XXX This may turn off WR; change the order? */ if (evthr_got_rd(tsk)) ret = dns_tsk_rd(tsk); if (sm_is_err(ret)) { /* complain? do some cleanup? */ return ret; } return ret; } return EVTHR_DEL; }