chiark / gitweb /
adns wip
[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 NO 300 nxdomain {No such domain}
7  *         ADNS NO 301 nodata {No such data}
8  *         ADNS FAILED 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 IdDataTable adnstcl_resolvers= { "adns-res" };
60 IdDataTable adnstcl_queries= { "adns-q" };
61
62 typedef struct {
63   int ix; /* first! */
64   Tcl_Interp *interp; fixme what about interpreter deletion
65   adns_state *ads;
66   int maxfd;
67   fd_set handling[3];
68 } Resolver;
69
70 typedef struct {
71   int ix; /* first! */
72   adns_query *aqu;
73   Tcl_Obj *on_yes, *on_no, *on_fail, *xargs;
74 } Query;
75
76 static adns_state *adnstcl_default;
77 static int default_auto;
78
79 #define RRTYPE_EXACTLY(t) { #t, adns_r_##t }
80 #define RRTYPE_RAW(t) { #t, adns_r_##t##_raw }
81 #define RRTYPE_PLUS(t) { #t "+", adns_r_##t }
82 #define RRTYPE_MINUS(t) { #t "-", adns_r_##t##_raw }
83
84 const AdnsTclRRTypeInfo adnstclrrtypeinfos[]= {
85   RRTYPE_EXACTLY(a),
86   RRTYPE_EXACTLY(cname),
87   RRTYPE_EXACTLY(hinfo),
88   RRTYPE_EXACTLY(addr),
89
90   RRTYPE_RAW(ns),
91   RRTYPE_RAW(mx),
92
93   RRTYPE_EXACTLY(soa),
94   RRTYPE_EXACTLY(ptr),
95   RRTYPE_EXACTLY(rp),
96
97   RRTYPE_MINUS(soa),
98   RRTYPE_MINUS(ptr),
99   RRTYPE_MINUS(rp),
100   { 0 }
101 };
102
103 typedef struct {
104   /* this struct type is used to hold both resolver and query options */
105   /* common to resolver and query: */
106   unsigned long *aflags;
107   unsigned long *sflags;
108   /* resolver: */
109   FILE *errfile;
110   Tcl_Obj *errcallback;
111   const char *config_string;
112   /* query: */
113   adns_state *resolver;
114   const char *reverseany;
115 } OptionParse;
116
117 typedef struct OptionInfo OptionInfo;
118 struct OptionInfo {
119   const char *name;
120   int (*fn)(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg,
121             OptionParse *op);
122   int takesarg;
123   unsigned long flags_add, flags_remove;
124 };
125
126 enum {
127   oisf_makedefault= 0x0001,
128   oisf_reverse=     0x0002
129 };
130
131 static int oiufn_f(const OptionInfo *oi, unsigned long *flags) {
132   flags &= ~oi->flags_remove;
133   flags |= oi->flsgs_add;
134 }
135 static int oifn_fa(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg,
136                    OptionParse *op) { oiufn_f(oi,op->aflags); }
137 static int oifn_fs(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg,
138                    OptionParse *op) { oiufn_f(oi,op->sflags); }
139
140 static int oifn_errfile(Tcl_Interp *ip, const OptionInfo *oi,
141                         Tcl_Obj *arg, OptionParse *op) {
142   int rc;
143   const char *str;
144   
145   rc= pat_string(ip,arg,&str);  if (rc) return rc;
146   if (!strcmp(str,"stderr")) op.errfile= stderr;
147   else if (!strcmp(str,"stdout")) op.errfile= stdout;
148   else return staticerr(ip,"-errfile argument must be stderr or stdout",0);
149
150   op.aflags &= ~adns_if_noerrprint;
151   op.errcallback= 0;
152   return TCL_OK;
153 }
154
155 static int oifn_errcallback(Tcl_Interp *ip, const OptionInfo *oi,
156                             Tcl_Obj *arg, OptionParse *op) {
157   op.errcallback= arg;
158   op.aflags &= ~adns_if_noeerpring;
159   op.errfile= 0;
160   return TCL_OK;
161 }
162
163 static int oifn_config(Tcl_Interp *ip, const OptionInfo *oi,
164                        Tcl_Obj *arg, OptionParse *op) {
165   return pat_string(ip,arg,&op.config_string);
166 }
167
168 static int oifn_resolver(Tcl_Interp *ip, const OptionInfo *oi,
169                          Tcl_Obj *arg, OptionParse *op) {
170   void *val_v;
171   rc= pat_iddata(ip,arg,&val_v,adnstcl_resolvers);
172   if (rc) return rc;
173   op.resolver= val_v;
174 }
175
176 static int oifn_reverse_any(Tcl_Interp *ip, const OptionInfo *oi,
177                             Tcl_Obj *arg, OptionParse *op) {
178   return pat_string(ip,arg,&op.reverseany);
179 }
180
181 #define OIFA1(t,f,r) { "-" #f, oifn_fa, 0, adns_##t##_##f, r }
182 #define OIFA2(t,f,g) { "-" #f "-" #g, oifn_fa, 0, adns_##t##_##f##_##g, 0 }
183 #define OIFS(f) { "-" #f, oifn_fs, 0, oisf_##f, 0 }
184 #define OICA(o) { "-" #o, oifn_##o, 1 }
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(errprint),
195   OICA(errcallback),
196   OICA(config),
197   { 0 }
198 };
199
200 static const OptionInfo query_optioninfos[]= {
201   OIFA1(if,search,0),
202   OIFA1(if,usevc,0),
203   OIFA2(if,quoteok,query),
204   OIFA2(if,quoteok,anshost),
205   OIFA2(if,quotefail,cname),
206   OIFA2(if,cname,loose),
207   OIFA2(if,cname,forbid),
208   OICA(resolver),
209   OIFS(reverse),
210   { "-reverse-any", oifn_reverse_any, 1 },
211   { 0 }
212 };
213
214 static int parse_options(Tcl_Interp *ip, int objc, Tcl_Obj *const *objv,
215                          const OptionInfo opttable[], OptionParse *op) {
216   const OptionInfo *oi;
217   Tcl_Obj *arg;
218   
219   for (;;) {
220     if (!objc--) break;
221
222     rc= pat_enum(ip,*objv++, &oi,opttable, sizeof(OptionInfo));
223     if (rc) return rc;
224
225     if (oi->takesarg) {
226       if (!objc--) {
227         setstringresult(ip,"missing value for option");
228         return TCL_ERROR;
229       }
230       arg= *objv++;
231     } else {
232       arg= 0;
233     }
234     rc= oi->fn(ip,oi,arg,op);
235     if (rc) return rc;
236   }
237   return TCL_OK;
238 }
239
240 int do_adns_destroy_resolver(ClientData cd, Tcl_Interp *ip, void *res_v) {
241   adns_state *res= res_v;
242
243   if (res == adnstcl_default) {
244     adnstcl_default= 0;
245     default_auto= 0;
246   }
247   adns_finish(res); fixme what about outstanding qs
248   return TCL_OK;
249 }
250
251 static void asynch_check(Resolver *res);
252
253 static void asynch_timerhandler(void *res_v) {
254   Resolver *res= res_v;
255   res->timertoken= 0;
256   adns_processtimeouts(res->ads,0);
257   checkqueries_updatehandlers(res);
258 }
259
260 static void asynch_filehandler(void *res_v, int mask) {
261   Resolver *res= res_v;
262   ec= adns_processany(res->ads);
263   if (ec) adns_globalsystemfailure(res->ads);
264   checkqueries_updatehandlers(res);
265 }
266
267 static void asynch_sethandlers(Resolver *res, int shutdown) {
268   fd_set want[3];
269   int maxfd;
270   struct timeval tvbuf, *timeout;
271
272   for (i=0; i<3; i++) FD_ZERO(&want[i]);
273   maxfd= 0;
274   timeout= 0;
275
276   if (!shutdown)
277     adns_beforeselect(res->ads,&maxfd,&want[0],&want[1],&want[2],
278                       &timeout,&tv_buf,0);
279
280   for (fd= 0; fd < maxfd || fd < res->maxfd; fd++)
281     for (i=0; i<3; i++)
282       if (!!FD_ISSET(fd, &res->handling[i])
283           != !!FD_ISSET(fd, &want[i])) {
284         int mask=0;
285         if (FD->ISSET(fd, &want[0])) mask |= TCL_READABLE;
286         if (FD->ISSET(fd, &want[1])) mask |= TCL_WRITABLE;
287         if (FD->ISSET(fd, &want[2])) mask |= TCL_EXCEPTION;
288         if (mask) Tcl_CreateFileHandler(fd,mask,filehandler,res);
289         else Tcl_DeleteFileHandler(fd);
290       }
291
292   Tcl_DeleteTimerHandler(res->timertoken);
293
294   if (timeout) {
295     int milliseconds;
296
297     if (timeout->tv_sec >= INT_MAX/1000 - 1)
298       milliseconds= INT_MAX;
299     else
300       milliseconds= timeout->tv_sec * 1000 +
301         (timeout->tv_usec + 999) / 1000;
302     
303     res->timertoken= Tcl_CreateTimerHandler(milliseconds,timerhandler,res);
304   }
305 }
306
307 int do_adns_new_resolver(ClientData cd, Tcl_Interp *ip,
308                          int objc, Tcl_Obj *const *objv,
309                          void **result) {
310   OptionParse op;
311   adns_state *ads=0;
312   Resolver *res=0;
313
314   op.aflags= adns_if_noautosys;
315   op.sflags= 0;
316   op.errfile= 0;
317   op.errcallback= 0;
318   op.config_string= 0;
319   rc= parse_options(ip,objc,objv,resolver_optioninfos,&op);
320   if (rc) goto x_rc;
321
322   res= TALLOC(sizeof(*res)); assert(res);
323   res->maxfd= 0;
324   for (i=0; i<3; i++) FD_ZERO(&res->handling);
325   res->interp= ip;
326
327   if (op.aflags & adns_if_noerrprint) {
328     op.errfile= 0;
329     op.errcallback= 0;
330   }
331
332   ec= adns_init_logfn(&ads, op.aflags, op.configstring,
333                       op.errcallback ? adnslogfn_callback : 0,
334                       op.errcallback ? op.errcallback : op.errfile);
335   if (ec) { rc= posixerr(ip,ec,"create adns resolver"); goto x_rc; }
336   res->ads= ads;
337
338   if (op.errcallback)
339     Tcl_IncrRefCount(op.errcallback);
340
341   rc= update_handlers(&res);  if (rc) goto x_rc;
342   
343   *result= res;
344   return TCL_OK;
345 }
346
347 static int query_submit(Tcl_Interp *ip, int objc, Tcl_Obj *const *objv,
348                         const char *domain) {
349   struct sockaddr sa;
350   static const int aftry[]= { AF_INET, AF_INET6 };
351   
352   op.aflags= adns_qf_owner;
353   op.sflags= 0;
354   op.resolver= 0;
355   rc= parse_options(ip,objc,objv,query_optioninfos,&op);
356   if (rc) return rc;
357
358   if (op.reverseany || (op.sflags & oisf_reverse)) {
359     const int *af;
360     for (af=aftry; af < af + sizeof(af)/sizeof(*af); af++) {
361       memset(&sa,0,sizeof(sa));
362       sa.sa_family= *af;
363       r= inet_pton(*af,domain,&sa);
364       if (!r) goto af_found;
365     }
366     return staticerr(ip,ec,"invalid address for adns reverse submit","");
367   }
368
369   if (!op.resolver) {
370     if (!adnstcl_default) {
371       ec= adns_init(&adnstcl_default,adns_if_noautosys,0);
372       if (ec) return posixerr(ip,ec,"create default adns resolver");
373     }
374     default_auto= 1;
375     op.resolver= adnstcl_default;
376   }
377
378   if (op.reverseany) {
379     ec= adns_submit_reverse_any(op.resolver, &sa, op.reverseany, rrtype,
380                                 op.aflags, 0, &query);
381   } else if (op.sflags & oisf_reverse) {
382     ec= adns_submit_reverse(op.resolver, &sa, rrtype, op.aflags, 0, &query);
383   } else {
384     ec= adns_submit(op.resolver, domain, rrtype, op.aflags, 0, &query);
385   }
386   if (ec)
387     return posixerr(ip,ec,"submit adns query");
388
389   return TCL_OK;
390 }
391
392 int do_adns_lookup(ClientData cd, Tcl_Interp *ip,
393                    const AdnsTclRRTypeInfo *rrtype,
394                    const char *domain,
395                    int objc, Tcl_Obj *const *objv,
396                    Tcl_Obj **result) {
397   OptionParse op;
398
399   rc= query_submit(ip,domain,objc,objv,&op,  );  if (rc) return rc;
400
401   ec= adns_wait(op.resolver,&query,&answer,0);
402   if (ec) return posixerr(ip,ec,"wait for adns lookup");
403
404   ...;
405 }
406
407 #define RESULTS_LLEN 7
408 static void make_results(Tcl_Interp *ip, adns_answer *answer,
409                          Tcl_Obj results[RESULTS_LLEN]) {
410   Tcl_Obj *rdata;
411   
412   results[0]= ret_string(ip, adns_errtypeabbrev(answer->status));
413   results[1]= ret_int(ip, answer->status);
414   results[2]= ret_string(ip, adns_errabbrev(answer->status));
415   results[3]= ret_string(ip, adns_strstatus(answer->status));
416   results[4]= ret_string(ip, answer->owner);
417   results[5]= ret_string(ip, answer->cname ? answer->cname : "");
418
419   rdata= TALLOC(sizeof(*rdata) * answer->nrrs);
420   for (i=0, datap=answer->rrs.untyped;
421        i<answer->nrrs; i++, datap += rrsz) {
422     st= adns_rr_info(answer->type, 0,0, &rrsz, datap, &rdatastring);
423     assert(!st);
424     rdata[i]= ret_string(ip, rdatastring);
425     free(rdatastring);
426   }
427   results[6]= Tcl_NewListObj(RESULTS_LLEN,rdata);
428   TFREE(rdata);
429 }
430   
431 static void asynch_query_dispose(Query *query) {
432   scriptinv_cancel(&query->on_yes);
433   scriptinv_cancel(&query->on_no);
434   scriptinv_cancel(&query->on_fail);
435   if (query->xargs) Tcl_DecrRefCount(query->xargs);
436   if (query->aqu) adns_cancel(query->aqu);
437   TFREE(query);
438 }
439
440 static void asynch_check(Resolver *res) {
441   Tcl_Interp *interp= res->interp;
442   adns_query *aqu;
443   adns_answer *answer;
444   Query *qu;
445   ScriptToInvoke *si;
446   Tcl_Obj results[RESULTS_LLEN];
447
448   for (;;) {
449     ec= adns_check(res->ads, &aqu, &answer, &qu);
450     if (ec==ESRCH || ec==EAGAIN) break;
451     assert(!ec);
452
453     qu->aqu= 0;
454
455     si= (!answer->status ? si= &query->on_yes
456          : answer->status > adns_s_max_tempfail ? &query->on_no
457          : &query->on_fail);
458
459     adnstcl_queries[qu->ix].a= 0;
460     make_results(ip, answer, results);
461     scriptinv_invoke(si, RESULTS_LLEN, results);
462     asynch_query_dispose(query);
463   }
464
465   asynch_sethandlers(res,0);
466 }
467     
468 int do_adns_asynch(ClientData cd, Tcl_Interp *ip,
469                    const AdnsTclRRTypeInfo *rrtype, const char *domain,
470                    Tcl_Obj *on_yes, Tcl_Obj *on_no,
471                    Tcl_Obj *on_fail, Tcl_Obj *xargs,
472                    int objc, Tcl_Obj *const *objv, void **result) {
473   Query *query;
474   adns_query *aqu;
475   
476   query= TALLOC(sizeof(*query));
477   query->aqu= 0;
478   scriptinv_init(&query->on_yes);
479   scriptinv_init(&query->on_no);
480   scriptinv_init(&query->on_fail);
481   query->xargs= 0;
482
483   rc= query_submit(ip,rrtype,domain, ...);  if (rc) goto x_rc;
484   query->aqu= aqu;
485
486   rc= scriptinv_set(&query->on_yes, ip,on_yes);   if (rc) goto x_rc;
487   rc= scriptinv_set(&query->on_no,  ip,on_no);    if (rc) goto x_rc;
488   rc= scriptinv_set(&query->on_fail,ip,on_fail);  if (rc) goto x_rc;
489   query->xargs= xargs;
490   Tcl_IncrRefCount(xargs);
491   *result= query;
492
493   return TCL_OK;
494
495  x_rc:
496   asynch_cancel(query);
497   return rc;
498 }
499
500 int do_adns_asynch_cancel(ClientData cd, Tcl_Interp *ip, void *query_v) {
501   adns_query *qu;
502   
503 }
504
505 int do_adns_synch(ClientData cd, Tcl_Interp *ip, const AdnsTclRRTypeInfo *rrtype, const char *domain, int objc, Tcl_Obj *const *objv, adns_answer **result);