chiark / gitweb /
adns compiles and does not work
[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 Resolver Resolver;
62 typedef struct OptionInfo OptionInfo;
63 static void asynch_check(Resolver *res);
64 static void asynch_sethandlers(Resolver *res, int shutdown);
65
66 /*---------- common resolver/query option processing ----------*/
67
68 typedef struct {
69   /* this struct type is used to hold both resolver and query options */
70   /* common to resolver and query: */
71   unsigned long aflags;
72   unsigned long sflags;
73   /* resolver: */
74   FILE *errfile;
75   Tcl_Obj *errcallback;
76   const char *config_string;
77   /* query: */
78   Resolver *resolver;
79   const char *reverseany;
80 } OptionParse;
81
82 struct OptionInfo {
83   const char *name;
84   int (*fn)(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg,
85             OptionParse *op);
86   int takesarg;
87   unsigned long flags_add, flags_remove;
88 };
89
90 enum {
91   oisf_makedefault= 0x0001,
92   oisf_reverse=     0x0002
93 };
94
95 static int oiufn_f(const OptionInfo *oi, unsigned long *flags) {
96   *flags &= ~oi->flags_remove;
97   *flags |= oi->flags_add;
98   return TCL_OK;
99 }
100 static int oifn_fa(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg,
101                    OptionParse *op) { return oiufn_f(oi,&op->aflags); }
102 static int oifn_fs(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg,
103                    OptionParse *op) { return oiufn_f(oi,&op->sflags); }
104
105 static int oifn_reverse_any(Tcl_Interp *ip, const OptionInfo *oi,
106                             Tcl_Obj *arg, OptionParse *op) {
107   return pat_string(ip,arg,&op->reverseany);
108 }
109
110 #define OIFA1(t,f,r) { "-" #f, oifn_fa, 0, adns_##t##_##f, r }
111 #define OIFA2(t,f,g) { "-" #f "-" #g, oifn_fa, 0, adns_##t##_##f##_##g, 0 }
112 #define OIFS(f) { "-" #f, oifn_fs, 0, oisf_##f, 0 }
113 #define OICA(o) { "-" #o, oifn_##o, 1 }
114
115 static int parse_options(Tcl_Interp *ip, int objc, Tcl_Obj *const *objv,
116                          const OptionInfo opttable[], OptionParse *op) {
117   const OptionInfo *oi;
118   const void *oi_v;
119   Tcl_Obj *arg;
120   int rc;
121
122   objc--; objv++;
123   for (;;) {
124     if (!objc--) break;
125     rc= pat_enum(ip, *objv++, &oi_v, opttable, sizeof(OptionInfo),
126                  "query or resolver option");
127     if (rc) return rc;
128     oi= oi_v;
129
130     if (oi->takesarg) {
131       if (!objc--) {
132         setstringresult(ip,"missing value for option");
133         return TCL_ERROR;
134       }
135       arg= *objv++;
136     } else {
137       arg= 0;
138     }
139     rc= oi->fn(ip,oi,arg,op);
140     if (rc) return rc;
141   }
142   return TCL_OK;
143 }
144
145 /*---------- resolver management ----------*/
146
147 IdDataTable adnstcl_resolvers= { "adns-res" };
148
149 struct Resolver {
150   int ix; /* first! */
151   Tcl_Interp *interp;
152   adns_state ads;
153   Tcl_TimerToken timertoken;
154   int maxfd;
155   fd_set handling[3];
156 };
157
158 static int oifn_errfile(Tcl_Interp *ip, const OptionInfo *oi,
159                         Tcl_Obj *arg, OptionParse *op) {
160   int rc;
161   const char *str;
162   
163   rc= pat_string(ip,arg,&str);  if (rc) return rc;
164   if (!strcmp(str,"stderr")) op->errfile= stderr;
165   else if (!strcmp(str,"stdout")) op->errfile= stdout;
166   else return staticerr(ip,"-errfile argument must be stderr or stdout",0);
167
168   op->aflags &= ~adns_if_noerrprint;
169   op->errcallback= 0;
170   return TCL_OK;
171 }
172
173 static int oifn_errcallback(Tcl_Interp *ip, const OptionInfo *oi,
174                             Tcl_Obj *arg, OptionParse *op) {
175   op->errcallback= arg;
176   op->aflags &= ~adns_if_noerrprint;
177   op->errfile= 0;
178   return TCL_OK;
179 }
180
181 static int oifn_config(Tcl_Interp *ip, const OptionInfo *oi,
182                        Tcl_Obj *arg, OptionParse *op) {
183   return pat_string(ip,arg,&op->config_string);
184 }
185
186 static const OptionInfo resolver_optioninfos[]= {
187   OIFA1(if,noenv, 0),
188   OIFA1(if,debug, adns_if_noerrprint),
189   OIFA1(if,logpid, adns_if_noerrprint),
190   OIFA1(if,noerrprint, adns_if_debug),
191   OIFA2(if,checkc,entex),
192   OIFA2(if,checkc,freq),
193   OIFS(makedefault),
194   OICA(errfile),
195   OICA(errcallback),
196   OICA(config),
197   { 0 }
198 };
199
200 static void adnslogfn_callback(adns_state ads, void *logfndata,
201                                const char *fmt, va_list al) {
202   abort(); /* fixme implement adns_logcallbackfn */
203 }
204
205 int do_adns_destroy_resolver(ClientData cd, Tcl_Interp *ip, void *res_v) {
206   Resolver *res= res_v;
207   tabledataid_disposing(res,&adnstcl_resolvers);
208   if (res->ads) adns_finish(res->ads);
209   asynch_sethandlers(res,1);
210   TFREE(res);
211   /* fixme what about outstanding queries */
212   /* fixme what about the default resolver */
213   /* fixme what if the tcl interpreter gets deleted */
214   return TCL_OK;
215 }
216
217 int do_adns_new_resolver(ClientData cd, Tcl_Interp *ip,
218                          int objc, Tcl_Obj *const *objv,
219                          void **result) {
220   OptionParse op;
221   Resolver *res=0;
222   int rc, i, ec;
223
224   op.aflags= adns_if_noautosys;
225   op.sflags= 0;
226   op.errfile= 0;
227   op.errcallback= 0;
228   op.config_string= 0;
229   rc= parse_options(ip,objc,objv,resolver_optioninfos,&op);
230   if (rc) goto x_rc;
231
232   res= TALLOC(sizeof(*res)); assert(res);
233   res->ix= -1;
234   res->interp= ip;
235   res->ads= 0;
236   res->timertoken= 0;
237   res->maxfd= 0;
238   for (i=0; i<3; i++) FD_ZERO(&res->handling[i]);
239
240   if (op.aflags & adns_if_noerrprint) {
241     op.errfile= 0;
242     op.errcallback= 0;
243   }
244
245   ec= adns_init_logfn(&res->ads, op.aflags, op.config_string,
246                       op.errcallback ? adnslogfn_callback : 0,
247                       op.errcallback ? (void*)op.errcallback
248                       : (void*)op.errfile);
249   if (ec) { rc= posixerr(ip,ec,"create adns resolver"); goto x_rc; }
250
251   if (op.errcallback)
252     Tcl_IncrRefCount(op.errcallback);
253
254   *result= res;
255   return TCL_OK;
256
257  x_rc:
258   if (res) {
259     if (res->ads) adns_finish(res->ads);
260     TFREE(res);
261   }
262   return rc;
263 }
264
265 /*---------- query, query option and answers - common stuff ----------*/
266
267 #define RRTYPE_EXACTLY(t) { #t, adns_r_##t }
268 #define RRTYPE_RAW(t) { #t, adns_r_##t##_raw }
269 #define RRTYPE_PLUS(t) { #t "+", adns_r_##t }
270 #define RRTYPE_MINUS(t) { #t "-", adns_r_##t##_raw }
271
272 const AdnsTclRRTypeInfo adnstclrrtypeinfos[]= {
273   RRTYPE_EXACTLY(a),
274   RRTYPE_EXACTLY(cname),
275   RRTYPE_EXACTLY(hinfo),
276   RRTYPE_EXACTLY(addr),
277
278   RRTYPE_RAW(ns),
279   RRTYPE_RAW(mx),
280
281   RRTYPE_EXACTLY(soa),
282   RRTYPE_EXACTLY(ptr),
283   RRTYPE_EXACTLY(rp),
284
285   RRTYPE_MINUS(soa),
286   RRTYPE_MINUS(ptr),
287   RRTYPE_MINUS(rp),
288   { 0 }
289 };
290
291 static int oifn_resolver(Tcl_Interp *ip, const OptionInfo *oi,
292                          Tcl_Obj *arg, OptionParse *op) {
293   void *val_v;
294   int rc;
295   
296   rc= pat_iddata(ip,arg,&val_v,&adnstcl_resolvers);
297   if (rc) return rc;
298   op->resolver= val_v;
299   return TCL_OK;
300 }
301
302 static const OptionInfo query_optioninfos[]= {
303   OIFA1(qf,search,0),
304   OIFA1(qf,usevc,0),
305   OIFA2(qf,quoteok,query),
306   OIFA2(qf,quoteok,anshost),
307   OIFA2(qf,quotefail,cname),
308   OIFA2(qf,cname,loose),
309   OIFA2(qf,cname,forbid),
310   OICA(resolver),
311   OIFS(reverse),
312   { "-reverse-any", oifn_reverse_any, 1 },
313   { 0 }
314 };
315
316 static int query_submit(Tcl_Interp *ip,
317                         const AdnsTclRRTypeInfo *type, const char *domain,
318                         int queryopts_objc, Tcl_Obj *const *queryopts_objv,
319                         adns_query *aqu_r, OptionParse *op) {
320   struct sockaddr sa;
321   static const int aftry[]= { AF_INET, AF_INET6 };
322   int rc, r, ec;
323   adns_state ads;
324   
325   op->aflags= adns_qf_owner;
326   op->sflags= 0;
327   op->resolver= 0;
328   rc= parse_options(ip, queryopts_objc,queryopts_objv, query_optioninfos,op);
329   if (rc) return rc;
330
331   if (op->reverseany || (op->sflags & oisf_reverse)) {
332     const int *af;
333     for (af=aftry; af < af + sizeof(af)/sizeof(*af); af++) {
334       memset(&sa,0,sizeof(sa));
335       sa.sa_family= *af;
336       r= inet_pton(*af,domain,&sa);
337       if (!r) goto af_found;
338     }
339     return staticerr(ip,"invalid address for adns reverse submit","");
340   af_found:;
341   }
342
343   ads= op->resolver->ads;
344
345   if (op->reverseany) {
346     ec= adns_submit_reverse_any(ads, &sa, op->reverseany,
347                                 type->number, op->aflags, 0, aqu_r);
348   } else if (op->sflags & oisf_reverse) {
349     ec= adns_submit_reverse(ads, &sa,
350                             type->number, op->aflags, 0, aqu_r);
351   } else {
352     ec= adns_submit(ads, domain,
353                     type->number, op->aflags, 0, aqu_r);
354   }
355   if (ec)
356     return posixerr(ip,ec,"submit adns query");
357
358   return TCL_OK;
359 }
360
361 #define RESULTSTATUS_LLEN 4
362 #define RESULTLIST_LLEN 7
363
364 static void make_resultstatus(Tcl_Interp *ip, adns_status status,
365                               Tcl_Obj *results[RESULTSTATUS_LLEN]) {
366   results[0]= ret_string(ip, adns_errtypeabbrev(status));
367   results[1]= ret_int(ip, status);
368   results[2]= ret_string(ip, adns_errabbrev(status));
369   results[3]= ret_string(ip, adns_strerror(status));
370 }
371
372 static Tcl_Obj *make_resultrdata(Tcl_Interp *ip, adns_answer *answer) {
373   Tcl_Obj **rdata;
374   int i, rrsz;
375   adns_status st;
376   char *datap, *rdatastring;
377   
378   rdata= TALLOC(sizeof(*rdata) * answer->nrrs);
379   for (i=0, datap=answer->rrs.untyped;
380        i<answer->nrrs;
381        i++, datap += rrsz) {
382     st= adns_rr_info(answer->type, 0,0, &rrsz, datap, &rdatastring);
383     assert(!st);
384     rdata[i]= ret_string(ip, rdatastring);
385     free(rdatastring);
386   }
387   TFREE(rdata);
388   return Tcl_NewListObj(answer->nrrs, rdata);
389 }
390
391 static void make_resultlist(Tcl_Interp *ip, adns_answer *answer,
392                             Tcl_Obj *results[RESULTLIST_LLEN]) {
393
394   make_resultstatus(ip, answer->status, results);
395   assert(RESULTSTATUS_LLEN==4);
396   results[4]= ret_string(ip, answer->owner);
397   results[5]= ret_string(ip, answer->cname ? answer->cname : "");
398   results[6]= make_resultrdata(ip, answer);
399 }
400
401 /*---------- synchronous query handling ----------*/
402
403 static int synch(Tcl_Interp *ip, const AdnsTclRRTypeInfo *rrtype,
404                  const char *domain,
405                  int objc, Tcl_Obj *const *objv, adns_answer **answer_r) {
406   adns_query aqu;
407   OptionParse op;
408   Resolver *res;
409   int rc, ec;
410   
411   rc= query_submit(ip,rrtype,domain,objc,objv,&aqu,&op);
412   if (rc) return rc;
413
414   res= op.resolver;
415   ec= adns_wait(res->ads,&aqu,answer_r,0);
416   assert(!ec);
417
418   asynch_check(res);
419   return TCL_OK;
420 }
421
422 int do_adns_lookup(ClientData cd, Tcl_Interp *ip,
423                    const AdnsTclRRTypeInfo *rrtype,
424                    const char *domain,
425                    int objc, Tcl_Obj *const *objv,
426                    Tcl_Obj **result) {
427   int rc;
428   adns_answer *answer;
429   
430   rc= synch(ip,rrtype,domain,objc,objv,&answer);  if (rc) return rc;
431
432   if (answer->status) {
433     Tcl_Obj *problem[RESULTSTATUS_LLEN];
434     make_resultstatus(ip, answer->status, problem);
435     *result= Tcl_NewListObj(RESULTSTATUS_LLEN, problem);
436   } else {
437     *result= make_resultrdata(ip, answer);
438   }
439   free(answer);
440   return TCL_OK;
441 }
442
443 int do_adns_synch(ClientData cd, Tcl_Interp *ip,
444                   const AdnsTclRRTypeInfo *rrtype,
445                   const char *domain,
446                   int objc, Tcl_Obj *const *objv,
447                   Tcl_Obj **result) {
448   int rc;
449   adns_answer *answer;
450   Tcl_Obj *results[RESULTLIST_LLEN];
451
452   rc= synch(ip,rrtype,domain,objc,objv,&answer);  if (rc) return rc;
453   make_resultlist(ip,answer,results);
454   free(answer);
455   *result= Tcl_NewListObj(RESULTLIST_LLEN, results);
456   return TCL_OK;
457 }
458
459 /*---------- asynchronous query handling ----------*/
460
461 typedef struct {
462   int ix; /* first! */
463   Resolver *res;
464   adns_query aqu;
465   ScriptToInvoke on_yes, on_no, on_fail;
466   Tcl_Obj *xargs;
467 } Query;
468
469 IdDataTable adnstcl_queries= { "adns" };
470
471 static void asynch_timerhandler(void *res_v) {
472   Resolver *res= res_v;
473   res->timertoken= 0;
474   adns_processtimeouts(res->ads,0);
475   asynch_check(res);
476 }
477
478 static void asynch_filehandler(void *res_v, int mask) {
479   Resolver *res= res_v;
480   int ec;
481   
482   ec= adns_processany(res->ads);
483   if (ec) adns_globalsystemfailure(res->ads);
484   asynch_check(res);
485 }
486
487 static void asynch_sethandlers(Resolver *res, int shutdown) {
488   fd_set want[3];
489   int maxfd;
490   struct timeval tv_buf, *timeout;
491   int i, fd;
492
493   timeout= 0;
494   maxfd= 0;
495   for (i=0; i<3; i++) FD_ZERO(&want[i]);
496
497   if (!shutdown)
498     adns_beforeselect(res->ads,&maxfd,&want[0],&want[1],&want[2],
499                       &timeout,&tv_buf,0);
500
501   for (fd= 0; fd < maxfd || fd < res->maxfd; fd++)
502     for (i=0; i<3; i++)
503       if (!!FD_ISSET(fd, &res->handling[i])
504           != !!FD_ISSET(fd, &want[i])) {
505         int mask=0;
506         if (FD_ISSET(fd, &want[0])) mask |= TCL_READABLE;
507         if (FD_ISSET(fd, &want[1])) mask |= TCL_WRITABLE;
508         if (FD_ISSET(fd, &want[2])) mask |= TCL_EXCEPTION;
509         if (mask) Tcl_CreateFileHandler(fd,mask,asynch_filehandler,res);
510         else Tcl_DeleteFileHandler(fd);
511       }
512
513   Tcl_DeleteTimerHandler(res->timertoken);
514
515   if (timeout) {
516     int milliseconds;
517
518     if (timeout->tv_sec >= INT_MAX/1000 - 1)
519       milliseconds= INT_MAX;
520     else
521       milliseconds= timeout->tv_sec * 1000 +
522         (timeout->tv_usec + 999) / 1000;
523     
524     res->timertoken=
525       Tcl_CreateTimerHandler(milliseconds,asynch_timerhandler,res);
526   }
527 }
528
529 static void asynch_query_dispose(Query *query) {
530   tabledataid_disposing(query, &adnstcl_queries);
531   scriptinv_cancel(&query->on_yes);
532   scriptinv_cancel(&query->on_no);
533   scriptinv_cancel(&query->on_fail);
534   if (query->xargs) Tcl_DecrRefCount(query->xargs);
535   if (query->aqu) adns_cancel(query->aqu);
536   TFREE(query);
537 }
538
539 static void asynch_check(Resolver *res) {
540   Tcl_Interp *interp= res->interp;
541   adns_query aqu;
542   adns_answer *answer;
543   void *query_v;
544   Query *query;
545   ScriptToInvoke *si;
546   int ec;
547   Tcl_Obj *results[RESULTLIST_LLEN];
548
549   for (;;) {
550     ec= adns_check(res->ads, &aqu, &answer, &query_v);
551     if (ec==ESRCH || ec==EAGAIN) break;
552     assert(!ec);
553     query= query_v;
554
555     query->aqu= 0;
556     tabledataid_disposing(query, &adnstcl_queries);
557
558     si= (!answer->status ? si= &query->on_yes
559          : answer->status > adns_s_max_tempfail ? &query->on_no
560          : &query->on_fail);
561
562     make_resultlist(interp, answer, results);
563     free(answer);
564     scriptinv_invoke(si, RESULTLIST_LLEN, results);
565     asynch_query_dispose(query);
566   }
567
568   asynch_sethandlers(res,0);
569 }
570     
571 int do_adns_asynch(ClientData cd, Tcl_Interp *ip,
572                    Tcl_Obj *on_yes, Tcl_Obj *on_no,
573                    Tcl_Obj *on_fail, Tcl_Obj *xargs,
574                    const AdnsTclRRTypeInfo *rrtype, const char *domain,
575                    int objc, Tcl_Obj *const *objv, void **result) {
576   Query *query;
577   int rc;
578   Resolver *res=0;
579   OptionParse op;
580   
581   query= TALLOC(sizeof(*query));
582   query->ix= -1;
583   query->aqu= 0;
584   scriptinv_init(&query->on_yes);
585   scriptinv_init(&query->on_no);
586   scriptinv_init(&query->on_fail);
587   query->xargs= 0;
588
589   rc= query_submit(ip,rrtype,domain,objc,objv,&query->aqu,&op);
590   if (rc) goto x_rc;
591
592   res= op.resolver;
593
594   rc= scriptinv_set(&query->on_yes, ip,on_yes);   if (rc) goto x_rc;
595   rc= scriptinv_set(&query->on_no,  ip,on_no);    if (rc) goto x_rc;
596   rc= scriptinv_set(&query->on_fail,ip,on_fail);  if (rc) goto x_rc;
597   query->xargs= xargs;
598   Tcl_IncrRefCount(xargs);
599   *result= query;
600
601   return TCL_OK;
602
603  x_rc:
604   if (query) asynch_query_dispose(query);
605   if (res) asynch_sethandlers(res,0);
606   return rc;
607 }
608
609 int do_adns_asynch_cancel(ClientData cd, Tcl_Interp *ip, void *query_v) {
610   Query *query= query_v;
611   Resolver *res= query->res;
612   
613   asynch_query_dispose(query);
614   asynch_sethandlers(res,0);
615   return TCL_OK;
616 }