chiark / gitweb /
avoid invoking callback procedures other than from event handlers, by using a zero...
[chiark-tcl.git] / adns / adns.c
1 /*
2  */
3 /*
4  * adns lookup TYPE DOMAIN [QUERY-OPTIONS]                    => [list RDATA]
5  *    if no or dontknow, throws an exception, with errorCode one of
6  *         ADNS permfail 300 nxdomain {No such domain}
7  *         ADNS permfail 301 nodata {No such data}
8  *         ADNS tempfail ERROR-CODE ERROR-NAME ERROR-STRING
9  *    where
10  *         ERROR-CODE is the numerical adns status value
11  *         ERROR-NAME is the symbolic adns status value (in lowercase)
12  *         ERROR-STRING is the result of adns_strstatus
13  *
14  * adns synch TYPE DOMAIN [QUERY-OPTIONS]                     => RESULTS
15  *        RESULTS is [list ok|permfail|tempfail
16  *                         ERROR-CODE ERROR-NAME ERROR-STRING  \
17  *                         OWNER CNAME                         \
18  *                         [list RDATA ...]]
19  *        OWNER is the RR owner
20  *        CNAME is the empty string or the canonical name if we went
21  *                  via a CNAME
22  *
23  * adns asynch ON-YES ON-NO ON-DONTKNOW XARGS \
24  *             TYPE DOMAIN \
25  *             [QUERY-OPTIONS]                               => QUERY-ID
26  *        calls, later,
27  *           [concat ON-YES|ON-NO|ON-DONTKNOW XARGS RESULTS]
28  * adns asynch-cancel QUERY-ID
29  *
30  * QUERY-OPTIONS are zero or more of
31  *         -resolver RESOLVER  (see adns new-resolver)
32  *                 default is to use a default resolver
33  *         -search
34  *         -usevc
35  *         -quoteok-query
36  *         -quoteok-anshost
37  *         -quotefail-cname
38  *         -cname-loose
39  *         -cname-forbid
40  *
41  * adns new-resolver [RES-OPTIONS...]                         => RESOLVER
42  *        options:
43  *         -errfile stdout|stderr       (stderr is the default)
44  *         -noerrprint
45  *         -errcallback CALLBACK    results in  eval CALLBACK [list MESSAGE]
46  *         -noenv|-debug|-logpid
47  *         -checkc-entex
48  *         -checkc-freq
49  *         -reverse
50  *         -reverse-any ZONE-A-LIKE
51  *         -config CONFIG-STRING
52  *
53  * adns destroy-resolver RESOLVER
54  */
55
56 #include "tables.h"
57 #include "hbytes.h"
58
59 /*---------- important types and forward declarations ----------*/
60
61 typedef struct Query Query;
62 typedef struct Resolver Resolver;
63 typedef struct OptionInfo OptionInfo;
64
65 static void asynch_sethandlers(Resolver *res);
66 static void asynch_cancelhandlers(Resolver *res);
67 static void asynch_perturbed(Resolver *res);
68
69 static void asynch_query_dispose(Tcl_Interp *interp, Query *query);
70
71 /*---------- common resolver/query option processing ----------*/
72
73 typedef struct {
74   /* this struct type is used to hold both resolver and query options */
75   /* common to resolver and query: */
76   unsigned long aflags;
77   unsigned long sflags;
78   /* resolver: */
79   FILE *errfile;
80   Tcl_Obj *errcallback;
81   const char *config_string;
82   /* query: */
83   Resolver *resolver;
84   const char *reverseany;
85 } OptionParse;
86
87 struct OptionInfo {
88   const char *name;
89   int (*fn)(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg,
90             OptionParse *op);
91   int takesarg;
92   unsigned long flags_add, flags_remove;
93 };
94
95 enum {
96   oisf_makedefault= 0x0001,
97   oisf_reverse=     0x0002
98 };
99
100 static int oiufn_f(const OptionInfo *oi, unsigned long *flags) {
101   *flags &= ~oi->flags_remove;
102   *flags |= oi->flags_add;
103   return TCL_OK;
104 }
105 static int oifn_fa(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg,
106                    OptionParse *op) { return oiufn_f(oi,&op->aflags); }
107 static int oifn_fs(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg,
108                    OptionParse *op) { return oiufn_f(oi,&op->sflags); }
109
110 static int oifn_reverse_any(Tcl_Interp *ip, const OptionInfo *oi,
111                             Tcl_Obj *arg, OptionParse *op) {
112   return pat_string(ip,arg,&op->reverseany);
113 }
114
115 #define OIFA1(t,f,r) { "-" #f, oifn_fa, 0, adns_##t##_##f, r }
116 #define OIFA2(t,f,g) { "-" #f "-" #g, oifn_fa, 0, adns_##t##_##f##_##g, 0 }
117 #define OIFS(f) { "-" #f, oifn_fs, 0, oisf_##f, 0 }
118 #define OICA(o) { "-" #o, oifn_##o, 1 }
119
120 static int parse_options(Tcl_Interp *ip, int objc, Tcl_Obj *const *objv,
121                          const OptionInfo opttable[], OptionParse *op) {
122   const OptionInfo *oi;
123   const void *oi_v;
124   Tcl_Obj *arg;
125   int rc;
126
127   objc--; objv++;
128   for (;;) {
129     if (!objc--) break;
130     rc= pat_enum(ip, *objv++, &oi_v, opttable, sizeof(OptionInfo),
131                  "query or resolver option");
132     if (rc) return rc;
133     oi= oi_v;
134
135     if (oi->takesarg) {
136       if (!objc--) {
137         setstringresult(ip,"missing value for option");
138         return TCL_ERROR;
139       }
140       arg= *objv++;
141     } else {
142       arg= 0;
143     }
144     rc= oi->fn(ip,oi,arg,op);
145     if (rc) return rc;
146   }
147   return TCL_OK;
148 }
149
150 /*---------- resolver management ----------*/
151
152 struct Resolver {
153   int ix; /* first! */
154   Tcl_Interp *interp;
155   adns_state ads;
156   Tcl_TimerToken timertoken;
157   int maxfd;
158   fd_set handling[3];
159 };
160
161 static int oifn_errfile(Tcl_Interp *ip, const OptionInfo *oi,
162                         Tcl_Obj *arg, OptionParse *op) {
163   int rc;
164   const char *str;
165   
166   rc= pat_string(ip,arg,&str);  if (rc) return rc;
167   if (!strcmp(str,"stderr")) op->errfile= stderr;
168   else if (!strcmp(str,"stdout")) op->errfile= stdout;
169   else return staticerr(ip,"-errfile argument must be stderr or stdout",0);
170
171   op->aflags &= ~adns_if_noerrprint;
172   op->errcallback= 0;
173   return TCL_OK;
174 }
175
176 static int oifn_errcallback(Tcl_Interp *ip, const OptionInfo *oi,
177                             Tcl_Obj *arg, OptionParse *op) {
178   op->errcallback= arg;
179   op->aflags &= ~adns_if_noerrprint;
180   op->errfile= 0;
181   return TCL_OK;
182 }
183
184 static int oifn_config(Tcl_Interp *ip, const OptionInfo *oi,
185                        Tcl_Obj *arg, OptionParse *op) {
186   return pat_string(ip,arg,&op->config_string);
187 }
188
189 static const OptionInfo resolver_optioninfos[]= {
190   OIFA1(if,noenv, 0),
191   OIFA1(if,debug, adns_if_noerrprint),
192   OIFA1(if,logpid, adns_if_noerrprint),
193   OIFA1(if,noerrprint, adns_if_debug),
194   OIFA2(if,checkc,entex),
195   OIFA2(if,checkc,freq),
196   OIFS(makedefault),
197   OICA(errfile),
198   OICA(errcallback),
199   OICA(config),
200   { 0 }
201 };
202
203 static void adnslogfn_callback(adns_state ads, void *logfndata,
204                                const char *fmt, va_list al) {
205   abort(); /* fixme implement adns_logcallbackfn */
206 }
207
208 static void destroy_resolver(Tcl_Interp *ip, Resolver *res) {
209   void *query_v;
210   adns_query aqu;
211   
212   if (res->ads) {
213     /* although adns would throw these away for us, we need to
214      * destroy our own data too and only adns has a list of them */
215     for (;;) {
216       adns_forallqueries_begin(res->ads);
217       aqu= adns_forallqueries_next(res->ads, &query_v);
218       if (!aqu) break;
219       asynch_query_dispose(ip, query_v);
220     }
221     adns_finish(res->ads);
222   }
223   asynch_cancelhandlers(res);
224   TFREE(res);
225   /* fixme what about the default resolver */
226 }
227
228 static void destroy_resolver_idtabcb(Tcl_Interp *ip, void *resolver_v) {
229   destroy_resolver(ip,resolver_v);
230 }
231
232 int do_adns_destroy_resolver(ClientData cd, Tcl_Interp *ip, void *res_v) {
233   destroy_resolver(ip,res_v);
234   tabledataid_disposing(ip,res_v,&adnstcl_resolvers);
235   return TCL_OK;
236 }
237
238 int do_adns_new_resolver(ClientData cd, Tcl_Interp *ip,
239                          int objc, Tcl_Obj *const *objv,
240                          void **result) {
241   OptionParse op;
242   Resolver *res=0;
243   int rc, i, ec;
244
245   op.aflags= adns_if_noautosys;
246   op.sflags= 0;
247   op.errfile= 0;
248   op.errcallback= 0;
249   op.config_string= 0;
250   rc= parse_options(ip,objc,objv,resolver_optioninfos,&op);
251   if (rc) goto x_rc;
252
253   res= TALLOC(sizeof(*res)); assert(res);
254   res->ix= -1;
255   res->interp= ip;
256   res->ads= 0;
257   res->timertoken= 0;
258   res->maxfd= 0;
259   for (i=0; i<3; i++) FD_ZERO(&res->handling[i]);
260
261   if (op.aflags & adns_if_noerrprint) {
262     op.errfile= 0;
263     op.errcallback= 0;
264   }
265
266   ec= adns_init_logfn(&res->ads, op.aflags, op.config_string,
267                       op.errcallback ? adnslogfn_callback : 0,
268                       op.errcallback ? (void*)op.errcallback
269                       : (void*)op.errfile);
270   if (ec) { rc= posixerr(ip,ec,"create adns resolver"); goto x_rc; }
271
272   if (op.errcallback)
273     Tcl_IncrRefCount(op.errcallback);
274
275   *result= res;
276   return TCL_OK;
277
278  x_rc:
279   if (res) {
280     if (res->ads) adns_finish(res->ads);
281     TFREE(res);
282   }
283   return rc;
284 }
285
286 const IdDataSpec adnstcl_resolvers= {
287   "adns-res", "adns-resolvers-table", destroy_resolver_idtabcb
288 };
289
290 /*---------- query, query option and answers - common stuff ----------*/
291
292 #define RRTYPE_EXACTLY(t) { #t, adns_r_##t }
293 #define RRTYPE_RAW(t) { #t, adns_r_##t##_raw }
294 #define RRTYPE_PLUS(t) { #t "+", adns_r_##t }
295 #define RRTYPE_MINUS(t) { #t "-", adns_r_##t##_raw }
296
297 const AdnsTclRRTypeInfo adnstclrrtypeinfos[]= {
298   RRTYPE_EXACTLY(a),
299   RRTYPE_EXACTLY(cname),
300   RRTYPE_EXACTLY(hinfo),
301   RRTYPE_EXACTLY(addr),
302
303   RRTYPE_RAW(ns),
304   RRTYPE_RAW(mx),
305
306   RRTYPE_EXACTLY(soa),
307   RRTYPE_EXACTLY(ptr),
308   RRTYPE_EXACTLY(rp),
309
310   RRTYPE_MINUS(soa),
311   RRTYPE_MINUS(ptr),
312   RRTYPE_MINUS(rp),
313   { 0 }
314 };
315
316 static int oifn_resolver(Tcl_Interp *ip, const OptionInfo *oi,
317                          Tcl_Obj *arg, OptionParse *op) {
318   void *val_v;
319   int rc;
320   
321   rc= pat_iddata(ip,arg,&val_v,&adnstcl_resolvers);
322   if (rc) return rc;
323   op->resolver= val_v;
324   return TCL_OK;
325 }
326
327 static const OptionInfo query_optioninfos[]= {
328   OIFA1(qf,search,0),
329   OIFA1(qf,usevc,0),
330   OIFA2(qf,quoteok,query),
331   OIFA2(qf,quoteok,anshost),
332   OIFA2(qf,quotefail,cname),
333   OIFA2(qf,cname,loose),
334   OIFA2(qf,cname,forbid),
335   OICA(resolver),
336   OIFS(reverse),
337   { "-reverse-any", oifn_reverse_any, 1 },
338   { 0 }
339 };
340
341 static int query_submit(Tcl_Interp *ip,
342                         const AdnsTclRRTypeInfo *type, const char *domain,
343                         int queryopts_objc, Tcl_Obj *const *queryopts_objv,
344                         adns_query *aqu_r, void *context, OptionParse *op) {
345   struct sockaddr sa;
346   static const int aftry[]= { AF_INET, AF_INET6 };
347   int rc, r, ec;
348   adns_state ads;
349   
350   op->aflags= adns_qf_owner;
351   op->sflags= 0;
352   op->resolver= 0; /* fixme default */
353   op->reverseany= 0;
354   rc= parse_options(ip, queryopts_objc,queryopts_objv, query_optioninfos,op);
355   if (rc) return rc;
356
357   if (op->reverseany || (op->sflags & oisf_reverse)) {
358     const int *af;
359     for (af=aftry; af < af + sizeof(af)/sizeof(*af); af++) {
360       memset(&sa,0,sizeof(sa));
361       sa.sa_family= *af;
362       r= inet_pton(*af,domain,&sa);
363       if (!r) goto af_found;
364     }
365     return staticerr(ip,"invalid address for adns reverse submit","");
366   af_found:;
367   }
368
369   ads= op->resolver->ads;
370
371   if (op->reverseany) {
372     ec= adns_submit_reverse_any(ads, &sa, op->reverseany,
373                                 type->number, op->aflags, context, aqu_r);
374   } else if (op->sflags & oisf_reverse) {
375     ec= adns_submit_reverse(ads, &sa,
376                             type->number, op->aflags, context, aqu_r);
377   } else {
378     ec= adns_submit(ads, domain,
379                     type->number, op->aflags, context, aqu_r);
380   }
381   if (ec)
382     return posixerr(ip,ec,"submit adns query");
383
384   return TCL_OK;
385 }
386
387 #define RESULTSTATUS_LLEN 4
388 #define RESULTLIST_LLEN 7
389
390 static void make_resultstatus(Tcl_Interp *ip, adns_status status,
391                               Tcl_Obj *results[RESULTSTATUS_LLEN]) {
392   results[0]= ret_string(ip, adns_errtypeabbrev(status));
393   results[1]= ret_int(ip, status);
394   results[2]= ret_string(ip, adns_errabbrev(status));
395   results[3]= ret_string(ip, adns_strerror(status));
396 }
397
398 static Tcl_Obj *make_resultrdata(Tcl_Interp *ip, adns_answer *answer) {
399   Tcl_Obj **rdata, *rl;
400   int i, rrsz;
401   adns_status st;
402   char *datap, *rdatastring;
403   
404   rdata= TALLOC(sizeof(*rdata) * answer->nrrs);
405   for (i=0, datap=answer->rrs.untyped;
406        i<answer->nrrs;
407        i++, datap += rrsz) {
408     st= adns_rr_info(answer->type, 0,0, &rrsz, datap, &rdatastring);
409     assert(!st);
410     rdata[i]= ret_string(ip, rdatastring);
411     free(rdatastring);
412   }
413   rl= Tcl_NewListObj(answer->nrrs, rdata);
414   TFREE(rdata);
415   return rl;
416 }
417
418 static void make_resultlist(Tcl_Interp *ip, adns_answer *answer,
419                             Tcl_Obj *results[RESULTLIST_LLEN]) {
420
421   make_resultstatus(ip, answer->status, results);
422   assert(RESULTSTATUS_LLEN==4);
423   results[4]= ret_string(ip, answer->owner);
424   results[5]= ret_string(ip, answer->cname ? answer->cname : "");
425   results[6]= make_resultrdata(ip, answer);
426 }
427
428 /*---------- synchronous query handling ----------*/
429
430 static int synch(Tcl_Interp *ip, const AdnsTclRRTypeInfo *rrtype,
431                  const char *domain,
432                  int objc, Tcl_Obj *const *objv, adns_answer **answer_r) {
433   adns_query aqu;
434   OptionParse op;
435   Resolver *res;
436   int rc, ec;
437   
438   rc= query_submit(ip,rrtype,domain,objc,objv,&aqu,0,&op);
439   if (rc) return rc;
440
441   res= op.resolver;
442   ec= adns_wait(res->ads,&aqu,answer_r,0);
443   assert(!ec);
444
445   asynch_perturbed(res);
446   return TCL_OK;
447 }
448
449 int do_adns_lookup(ClientData cd, Tcl_Interp *ip,
450                    const AdnsTclRRTypeInfo *rrtype,
451                    const char *domain,
452                    int objc, Tcl_Obj *const *objv,
453                    Tcl_Obj **result) {
454   int rc;
455   adns_answer *answer;
456   
457   rc= synch(ip,rrtype,domain,objc,objv,&answer);  if (rc) return rc;
458
459   if (answer->status) {
460     Tcl_Obj *problem[RESULTSTATUS_LLEN];
461     make_resultstatus(ip, answer->status, problem);
462     *result= Tcl_NewListObj(RESULTSTATUS_LLEN, problem);
463   } else {
464     *result= make_resultrdata(ip, answer);
465   }
466   free(answer);
467   return TCL_OK;
468 }
469
470 int do_adns_synch(ClientData cd, Tcl_Interp *ip,
471                   const AdnsTclRRTypeInfo *rrtype,
472                   const char *domain,
473                   int objc, Tcl_Obj *const *objv,
474                   Tcl_Obj **result) {
475   int rc;
476   adns_answer *answer;
477   Tcl_Obj *results[RESULTLIST_LLEN];
478
479   rc= synch(ip,rrtype,domain,objc,objv,&answer);  if (rc) return rc;
480   make_resultlist(ip,answer,results);
481   free(answer);
482   *result= Tcl_NewListObj(RESULTLIST_LLEN, results);
483   return TCL_OK;
484 }
485
486 /*---------- asynchronous query handling ----------*/
487
488 struct Query {
489   int ix; /* first! */
490   Resolver *res;
491   adns_query aqu;
492   ScriptToInvoke on_yes, on_no, on_fail;
493   Tcl_Obj *xargs;
494 };
495
496 static void asynch_check_now(Resolver *res);
497
498 static void asynch_timerhandler(void *res_v) {
499   Resolver *res= res_v;
500   res->timertoken= 0;
501   adns_processtimeouts(res->ads,0);
502   asynch_check_now(res);
503 }
504
505 static void asynch_filehandler(void *res_v, int mask) {
506   Resolver *res= res_v;
507   int ec;
508   
509   ec= adns_processany(res->ads);
510   if (ec) adns_globalsystemfailure(res->ads);
511   asynch_check_now(res);
512 }
513
514 static void asynch_sethandlers_generic(Resolver *res,
515                                        int shutdown /*from _cancelhandlers*/,
516                                        int immediate /*from _perturbed*/) {
517   fd_set want[3];
518   int maxfd;
519   struct timeval tv_buf, *timeout;
520   int i, fd;
521
522   timeout= 0;
523   maxfd= 0;
524   for (i=0; i<3; i++) FD_ZERO(&want[i]);
525
526   if (!shutdown)
527     adns_beforeselect(res->ads,&maxfd,&want[0],&want[1],&want[2],
528                       &timeout,&tv_buf,0);
529
530   for (fd= 0; fd < maxfd || fd < res->maxfd; fd++)
531     for (i=0; i<3; i++)
532       if (!!FD_ISSET(fd, &res->handling[i])
533           != !!FD_ISSET(fd, &want[i])) {
534         int mask=0;
535         if (FD_ISSET(fd, &want[0])) mask |= TCL_READABLE;
536         if (FD_ISSET(fd, &want[1])) mask |= TCL_WRITABLE;
537         if (FD_ISSET(fd, &want[2])) mask |= TCL_EXCEPTION;
538         if (mask) Tcl_CreateFileHandler(fd,mask,asynch_filehandler,res);
539         else Tcl_DeleteFileHandler(fd);
540       }
541
542   Tcl_DeleteTimerHandler(res->timertoken);
543
544   if (immediate) {
545     res->timertoken= Tcl_CreateTimerHandler(0,asynch_timerhandler,res);
546   } else if (timeout) {
547     int milliseconds;
548
549     if (timeout->tv_sec >= INT_MAX/1000 - 1)
550       milliseconds= INT_MAX;
551     else
552       milliseconds= timeout->tv_sec * 1000 +
553         (timeout->tv_usec + 999) / 1000;
554     
555     res->timertoken=
556       Tcl_CreateTimerHandler(milliseconds,asynch_timerhandler,res);
557   }
558 }
559
560 static void asynch_sethandlers(Resolver *res) {
561   asynch_sethandlers_generic(res,0,0);
562 }
563 static void asynch_cancelhandlers(Resolver *res) {
564   asynch_sethandlers_generic(res,1,0);
565 }
566 static void asynch_perturbed(Resolver *res) {
567   asynch_sethandlers_generic(res,0,1);
568 }
569
570 static void asynch_check_now(Resolver *res) {
571   Tcl_Interp *interp= res->interp;
572   adns_query aqu;
573   adns_answer *answer;
574   void *query_v;
575   Query *query;
576   ScriptToInvoke *si;
577   int ec;
578   Tcl_Obj *results[RESULTLIST_LLEN];
579
580   for (;;) {
581     aqu= 0;
582     ec= adns_check(res->ads, &aqu, &answer, &query_v);
583     if (ec==ESRCH || ec==EAGAIN) break;
584     assert(!ec);
585     query= query_v;
586
587     query->aqu= 0;
588     tabledataid_disposing(interp, query, &adnstcl_queries);
589
590     si= (!answer->status ? &query->on_yes
591          : answer->status > adns_s_max_tempfail ? &query->on_no
592          : &query->on_fail);
593
594     make_resultlist(interp, answer, results);
595     free(answer);
596     scriptinv_invoke(si, RESULTLIST_LLEN, results);
597     asynch_query_dispose(interp, query);
598   }
599
600   asynch_sethandlers(res);
601 }
602     
603 int do_adns_asynch(ClientData cd, Tcl_Interp *ip,
604                    Tcl_Obj *on_yes, Tcl_Obj *on_no,
605                    Tcl_Obj *on_fail, Tcl_Obj *xargs,
606                    const AdnsTclRRTypeInfo *rrtype, const char *domain,
607                    int objc, Tcl_Obj *const *objv, void **result) {
608   Query *query;
609   int rc;
610   Resolver *res=0;
611   OptionParse op;
612   
613   query= TALLOC(sizeof(*query));
614   query->ix= -1;
615   query->aqu= 0;
616   scriptinv_init(&query->on_yes);
617   scriptinv_init(&query->on_no);
618   scriptinv_init(&query->on_fail);
619   query->xargs= 0;
620
621   rc= query_submit(ip,rrtype,domain,objc,objv,&query->aqu,query,&op);
622   if (rc) goto x_rc;
623
624   res= op.resolver;
625
626   rc= scriptinv_set(&query->on_yes, ip,on_yes, xargs);  if (rc) goto x_rc;
627   rc= scriptinv_set(&query->on_no,  ip,on_no,  xargs);  if (rc) goto x_rc;
628   rc= scriptinv_set(&query->on_fail,ip,on_fail,xargs);  if (rc) goto x_rc;
629   query->xargs= xargs;
630   Tcl_IncrRefCount(xargs);
631   *result= query;
632   query= 0; /* do not dispose */
633   rc= TCL_OK;
634
635  x_rc:
636   if (query) asynch_query_dispose(ip, query);
637   if (res) asynch_perturbed(res);
638   return rc;
639 }
640
641 int do_adns_asynch_cancel(ClientData cd, Tcl_Interp *ip, void *query_v) {
642   Query *query= query_v;
643   Resolver *res= query->res;
644   asynch_query_dispose(ip, query);
645   asynch_perturbed(res);
646   return TCL_OK;
647 }
648
649 static void asynch_query_dispose(Tcl_Interp *interp, Query *query) {
650   tabledataid_disposing(interp, query, &adnstcl_queries);
651   scriptinv_cancel(&query->on_yes);
652   scriptinv_cancel(&query->on_no);
653   scriptinv_cancel(&query->on_fail);
654   if (query->xargs) Tcl_DecrRefCount(query->xargs);
655   if (query->aqu) adns_cancel(query->aqu);
656   TFREE(query);
657 }
658
659 static void destroy_query_idtabcb(Tcl_Interp *interp, void *query_v) {
660   asynch_query_dispose(interp, query_v);
661 }
662
663 const IdDataSpec adnstcl_queries= {
664   "adns", "adns-query-table", destroy_query_idtabcb
665 };