/* * query.c * - overall query management (allocation, completion) * - per-query memory management * - query submission and cancellation (user-visible and internal) */ /* * This file is * Copyright (C) 1997-2000 Ian Jackson * * It is part of adns, which is * Copyright (C) 1997-2000 Ian Jackson * Copyright (C) 1999-2000 Tony Finch * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Id: query.c,v 1.2 2003/06/14 13:55:50 tr-ircd Exp $ */ #include "struct.h" #include "fileio.h" #include "internal.h" #include "ircsprintf.h" #include "memory.h" #include "s_user.h" static adns_query query_alloc(adns_state ads, const typeinfo * typei, adns_queryflags flags, struct timeval now) { /* Allocate a virgin query and return it. */ adns_query qu; qu = (adns_query) MyMalloc(sizeof(*qu)); qu->answer = (adns_answer *) MyMalloc(sizeof(*qu->answer)); qu->ads = ads; qu->state = query_tosend; qu->back = qu->next = qu->parent = 0; ADNS_LIST_INIT(qu->children); LINK_INIT(qu->siblings); ADNS_LIST_INIT(qu->allocations); qu->interim_allocd = 0; qu->preserved_allocd = 0; qu->final_allocspace = 0; qu->typei = typei; qu->query_dgram = 0; qu->query_dglen = 0; adns__vbuf_init(&qu->vb); qu->cname_dgram = 0; qu->cname_dglen = qu->cname_begin = 0; adns__vbuf_init(&qu->search_vb); qu->search_origlen = qu->search_pos = qu->search_doneabs = 0; qu->id = -2; /* will be overwritten with real id before we leave adns */ qu->flags = flags; qu->retries = 0; qu->udpnextserver = 0; qu->udpsent = 0; timerclear(&qu->timeout); qu->expires = now.tv_sec + MAXTTLBELIEVE; memset(&qu->ctx, 0, sizeof(qu->ctx)); qu->answer->status = adns_s_ok; qu->answer->cname = qu->answer->owner = 0; qu->answer->type = typei->type; qu->answer->expires = -1; qu->answer->nrrs = 0; qu->answer->rrs.untyped = 0; qu->answer->rrsz = typei->rrsz; return qu; } static void query_submit(adns_state ads, adns_query qu, const typeinfo * typei, vbuf * qumsg_vb, int id, adns_queryflags flags, struct timeval now) { /* Fills in the query message in for a previously-allocated query, * and submits it. Cannot fail. Takes over the memory for qumsg_vb. */ qu->vb = *qumsg_vb; adns__vbuf_init(qumsg_vb); qu->query_dgram = (byte *) MyMalloc(qu->vb.used); qu->id = id; qu->query_dglen = qu->vb.used; memcpy(qu->query_dgram, qu->vb.buf, qu->vb.used); adns__query_send(qu, now); } adns_status adns__internal_submit(adns_state ads, adns_query * query_r, const typeinfo * typei, vbuf * qumsg_vb, int id, adns_queryflags flags, struct timeval now, const qcontext * ctx) { adns_query qu; qu = query_alloc(ads, typei, flags, now); if (!qu) { adns__vbuf_free(qumsg_vb); return adns_s_nomemory; } *query_r = qu; memcpy(&qu->ctx, ctx, sizeof(qu->ctx)); query_submit(ads, qu, typei, qumsg_vb, id, flags, now); return adns_s_ok; } static void query_simple(adns_state ads, adns_query qu, const byte * owner, int ol, const typeinfo * typei, adns_queryflags flags, struct timeval now) { vbuf vb_new; int id; adns_status status; status = adns__mkquery(ads, &qu->vb, &id, (const char *) owner, ol, typei, flags); if (status) { if (status == adns_s_querydomaintoolong && (flags & adns_qf_search)) { adns__search_next(ads, qu, now); return; } else { adns__query_fail(qu, status); return; } } vb_new = qu->vb; adns__vbuf_init(&qu->vb); query_submit(ads, qu, typei, &vb_new, id, flags, now); } void adns__search_next(adns_state ads, adns_query qu, struct timeval now) { const char *nextentry; adns_status status; if (qu->search_doneabs < 0) { nextentry = 0; qu->search_doneabs = 1; } else { if (qu->search_pos >= ads->nsearchlist) { if (qu->search_doneabs) { status = adns_s_nxdomain; goto x_fail; } else { nextentry = 0; qu->search_doneabs = 1; } } else { nextentry = ads->searchlist[qu->search_pos++]; } } qu->search_vb.used = qu->search_origlen; if (nextentry) { if (!adns__vbuf_append(&qu->search_vb, (const byte *) ".", 1) || !adns__vbuf_appendstr(&qu->search_vb, nextentry)) { status = adns_s_nomemory; goto x_fail; } } MyFree(qu->query_dgram); qu->query_dgram = 0; qu->query_dglen = 0; query_simple(ads, qu, qu->search_vb.buf, qu->search_vb.used, qu->typei, qu->flags, now); return; x_fail: adns__query_fail(qu, status); } static int save_owner(adns_query qu, const char *owner, int ol) { /* Returns 1 if OK, otherwise there was no memory. */ adns_answer *ans; ans = qu->answer; assert(!ans->owner); ans->owner = adns__alloc_preserved(qu, ol + 1); if (!ans->owner) return 0; memcpy(ans->owner, owner, ol); ans->owner[ol] = 0; return 1; } int adns_submit(adns_state ads, const char *owner, adns_rrtype type, adns_queryflags flags, void *context, adns_query * query_r) { int r, ol, ndots; adns_status status; const typeinfo *typei; struct timeval now; adns_query qu; const char *p; adns__consistency(ads, 0, cc_entex); typei = adns__findtype(type); if (!typei) return ENOSYS; r = gettimeofday(&now, 0); if (r) goto x_errno; qu = query_alloc(ads, typei, flags, now); if (!qu) goto x_errno; qu->ctx.ext = context; qu->ctx.callback = 0; memset(&qu->ctx.info, 0, sizeof(qu->ctx.info)); *query_r = qu; ol = strlen(owner); if (!ol) { status = adns_s_querydomaininvalid; goto x_adnsfail; } if (ol > DNS_MAXDOMAIN + 1) { status = adns_s_querydomaintoolong; goto x_adnsfail; } if (ol >= 1 && owner[ol - 1] == '.' && (ol < 2 || owner[ol - 2] != '\\')) { flags &= ~adns_qf_search; qu->flags = flags; ol--; } if (flags & adns_qf_search) { r = adns__vbuf_append(&qu->search_vb, (const byte *) owner, ol); if (!r) { status = adns_s_nomemory; goto x_adnsfail; } for (ndots = 0, p = owner; (p = strchr(p, '.')); p++, ndots++); qu->search_doneabs = (ndots >= ads->searchndots) ? -1 : 0; qu->search_origlen = ol; adns__search_next(ads, qu, now); } else { if (flags & adns_qf_owner) { if (!save_owner(qu, owner, ol)) { status = adns_s_nomemory; goto x_adnsfail; } } query_simple(ads, qu, (const byte *) owner, ol, typei, flags, now); } adns__autosys(ads, now); adns__consistency(ads, qu, cc_entex); return 0; x_adnsfail: adns__query_fail(qu, status); adns__consistency(ads, qu, cc_entex); return 0; x_errno: r = errno; assert(r); adns__consistency(ads, 0, cc_entex); return r; } #ifdef IPV6 int adns_submit_reverse_ip6(adns_state ads, const struct sockaddr *addr, const char *zone, adns_rrtype type, adns_queryflags flags, void *context, adns_query * query_r) { char shortbuf[100]; char *buf, *buf_free; const unsigned char *cp; char *qp; int n, c, lreq, r; if (addr->sa_family != AF_INET6) return ENOSYS; cp = (const unsigned char *) &(((const struct sockaddr_in6 *) addr)->sin6_addr.s6_addr); lreq = 71 + strlen(zone) + 1; if (lreq > sizeof(shortbuf)) { buf = (char *) MyMalloc(strlen(zone) + 4 * 4 + 1); if (!buf) return errno; buf_free = buf; } else { buf = shortbuf; buf_free = 0; } qp = buf; for (n = 15; n >= 0; n--) { c = ircsprintf(qp, "%n.%n.", cp[n] & 0xf, (cp[n] >> 4) & 0xf); qp += c; } strcpy(qp, zone); r = adns_submit(ads, buf, type, flags, context, query_r); if (buf_free) MyFree(buf_free); return r; } #endif int adns_submit_reverse_any(adns_state ads, const struct sockaddr *addr, const char *zone, adns_rrtype type, adns_queryflags flags, void *context, adns_query * query_r) { const unsigned char *iaddr; char *buf, *buf_free; char shortbuf[100]; int r, lreq; flags &= ~adns_qf_search; if (addr->sa_family != AF_INET) return ENOSYS; iaddr = (const unsigned char *) &(((const struct sockaddr_in *) addr)->sin_addr); lreq = strlen(zone) + 4 * 4 + 1; if (lreq > sizeof(shortbuf)) { buf = (char *) MyMalloc(strlen(zone) + 4 * 4 + 1); #if 0 if (!buf) return errno; #endif buf_free = buf; } else { buf = shortbuf; buf_free = 0; } ircsprintf(buf, "%d.%d.%d.%d.%s", iaddr[3], iaddr[2], iaddr[1], iaddr[0], zone); r = adns_submit(ads, buf, type, flags, context, query_r); MyFree(buf_free); return r; } int adns_submit_reverse(adns_state ads, const struct sockaddr *addr, adns_rrtype type, adns_queryflags flags, void *context, adns_query * query_r) { if (type != adns_r_ptr && type != adns_r_ptr_raw && type != adns_r_ptr_ip6) return EINVAL; #ifdef IPV6 if (addr->sa_family == AF_INET6) return adns_submit_reverse_ip6(ads, addr, "ip6.int", type, flags, context, query_r); else #endif return adns_submit_reverse_any(ads, addr, "in-addr.arpa", type, flags, context, query_r); } #if 0 int adns_synchronous(adns_state ads, const char *owner, adns_rrtype type, adns_queryflags flags, adns_answer ** answer_r) { adns_query qu; int r; r = adns_submit(ads, owner, type, flags, 0, &qu); if (r) return r; r = adns_wait(ads, &qu, answer_r, 0); if (r) adns_cancel(qu); return r; } #endif static void *alloc_common(adns_query qu, size_t sz) { allocnode *an; if (!sz) return qu; /* Any old pointer will do */ assert(!qu->final_allocspace); an = (allocnode *) MyMalloc(MEM_ROUND(MEM_ROUND(sizeof(*an)) + sz)); #if 0 if (!an) return 0; #endif LIST_LINK_TAIL(qu->allocations, an); return (byte *) an + MEM_ROUND(sizeof(*an)); } void *adns__alloc_interim(adns_query qu, size_t sz) { void *rv; sz = MEM_ROUND(sz); rv = alloc_common(qu, sz); if (!rv) return 0; qu->interim_allocd += sz; return rv; } void *adns__alloc_preserved(adns_query qu, size_t sz) { void *rv; sz = MEM_ROUND(sz); rv = adns__alloc_interim(qu, sz); if (!rv) return 0; qu->preserved_allocd += sz; return rv; } void *adns__alloc_mine(adns_query qu, size_t sz) { return alloc_common(qu, MEM_ROUND(sz)); } void adns__transfer_interim(adns_query from, adns_query to, void *block, size_t sz) { allocnode *an; if (!block) return; an = (void *) ((byte *) block - MEM_ROUND(sizeof(*an))); assert(!to->final_allocspace); assert(!from->final_allocspace); LIST_UNLINK(from->allocations, an); LIST_LINK_TAIL(to->allocations, an); sz = MEM_ROUND(sz); from->interim_allocd -= sz; to->interim_allocd += sz; if (to->expires > from->expires) to->expires = from->expires; } void *adns__alloc_final(adns_query qu, size_t sz) { /* When we're in the _final stage, we _subtract_ from interim_alloc'd * each allocation, and use final_allocspace to point to the next free * bit. */ void *rp; sz = MEM_ROUND(sz); rp = qu->final_allocspace; assert(rp); qu->interim_allocd -= sz; assert(qu->interim_allocd >= 0); qu->final_allocspace = (byte *) rp + sz; return rp; } static void cancel_children(adns_query qu) { adns_query cqu, ncqu; for (cqu = qu->children.head; cqu; cqu = ncqu) { ncqu = cqu->siblings.next; adns_cancel(cqu); } } void adns__reset_preserved(adns_query qu) { assert(!qu->final_allocspace); cancel_children(qu); qu->answer->nrrs = 0; qu->answer->rrs.untyped = 0; qu->interim_allocd = qu->preserved_allocd; } static void free_query_allocs(adns_query qu) { allocnode *an, *ann; cancel_children(qu); for (an = qu->allocations.head; an; an = ann) { ann = an->next; MyFree(an); } ADNS_LIST_INIT(qu->allocations); adns__vbuf_free(&qu->vb); adns__vbuf_free(&qu->search_vb); MyFree(qu->query_dgram); qu->query_dgram = 0; } void adns_cancel(adns_query qu) { adns_state ads; assert(qu != NULL); ads = qu->ads; adns__consistency(ads, qu, cc_entex); if (qu->parent) LIST_UNLINK_PART(qu->parent->children, qu, siblings.); switch (qu->state) { case query_tosend: LIST_UNLINK(ads->udpw, qu); break; case query_tcpw: LIST_UNLINK(ads->tcpw, qu); break; case query_childw: LIST_UNLINK(ads->childw, qu); break; case query_done: LIST_UNLINK(ads->output, qu); break; default: break; #if 0 abort(); #endif } free_query_allocs(qu); MyFree(qu->answer); MyFree(qu); adns__consistency(ads, 0, cc_entex); } void adns__update_expires(adns_query qu, unsigned long ttl, struct timeval now) { time_t max; assert(ttl <= MAXTTLBELIEVE); max = now.tv_sec + ttl; if (qu->expires < max) return; qu->expires = max; } static void makefinal_query(adns_query qu) { adns_answer *ans; int rrn; ans = qu->answer; assert(qu->answer); if (qu->interim_allocd) { ans = (adns_answer *) MyRealloc(qu->answer, MEM_ROUND(MEM_ROUND(sizeof(*ans)) + qu->interim_allocd)); if (!ans) goto x_nomem; qu->answer = ans; } qu->final_allocspace = (byte *) ans + MEM_ROUND(sizeof(*ans)); adns__makefinal_str(qu, &ans->cname); adns__makefinal_str(qu, &ans->owner); if (ans->nrrs) { adns__makefinal_block(qu, &ans->rrs.untyped, ans->nrrs * ans->rrsz); for (rrn = 0; rrn < ans->nrrs; rrn++) qu->typei->makefinal(qu, ans->rrs.bytes + rrn * ans->rrsz); } free_query_allocs(qu); return; x_nomem: qu->preserved_allocd = 0; qu->answer->cname = 0; qu->answer->owner = 0; adns__reset_preserved(qu); /* (but we just threw away the preserved stuff) */ qu->answer->status = adns_s_nomemory; free_query_allocs(qu); } void adns__query_done(adns_query qu) { adns_answer *ans; adns_query parent; cancel_children(qu); qu->id = -1; ans = qu->answer; if (qu->flags & adns_qf_owner && qu->flags & adns_qf_search && ans->status != adns_s_nomemory) { if (!save_owner(qu, (const char *) qu->search_vb.buf, qu->search_vb.used)) { adns__query_fail(qu, adns_s_nomemory); return; } } if (ans->nrrs && qu->typei->diff_needswap) { if (!adns__vbuf_ensure(&qu->vb, qu->typei->rrsz)) { adns__query_fail(qu, adns_s_nomemory); return; } adns__isort(ans->rrs.bytes, ans->nrrs, ans->rrsz, qu->vb.buf, (int (*)(void *, const void *, const void *)) qu->typei-> diff_needswap, qu->ads); } ans->expires = qu->expires; parent = qu->parent; if (parent) { LIST_UNLINK_PART(parent->children, qu, siblings.); LIST_UNLINK(qu->ads->childw, parent); qu->ctx.callback(parent, qu); free_query_allocs(qu); MyFree(qu->answer); MyFree(qu); } else { makefinal_query(qu); LIST_LINK_TAIL(qu->ads->output, qu); qu->state = query_done; } } void adns__query_fail(adns_query qu, adns_status status) { adns__reset_preserved(qu); qu->answer->status = status; adns__query_done(qu); } void adns__makefinal_str(adns_query qu, char **strp) { int l; char *before, *after; before = *strp; if (!before) return; l = strlen(before) + 1; after = adns__alloc_final(qu, l); memcpy(after, before, l); *strp = after; } void adns__makefinal_block(adns_query qu, void **blpp, size_t sz) { void *before, *after; before = *blpp; if (!before) return; after = adns__alloc_final(qu, sz); memcpy(after, before, sz); *blpp = after; }