X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=blobdiff_plain;f=adns%2Fadns.c;h=d57c49bf1ba3e2efee7f11add33135a8e450e671;hb=79afa3a523e92a1d552d46729b1e1d04db97f72c;hp=c18ccad5a4698a5886a2f95c83e39d2278c37a25;hpb=c7fc51a4ad6bb6f01266ed5912b062ae98a653b8;p=chiark-tcl.git diff --git a/adns/adns.c b/adns/adns.c index c18ccad..d57c49b 100644 --- a/adns/adns.c +++ b/adns/adns.c @@ -3,9 +3,9 @@ /* * adns lookup TYPE DOMAIN [QUERY-OPTIONS] => [list RDATA] * if no or dontknow, throws an exception, with errorCode one of - * ADNS NO 300 nxdomain {No such domain} - * ADNS NO 301 nodata {No such data} - * ADNS FAILED ERROR-CODE ERROR-NAME ERROR-STRING + * ADNS permfail 300 nxdomain {No such domain} + * ADNS permfail 301 nodata {No such data} + * ADNS tempfail ERROR-CODE ERROR-NAME ERROR-STRING * where * ERROR-CODE is the numerical adns status value * ERROR-NAME is the symbolic adns status value (in lowercase) @@ -37,6 +37,8 @@ * -quotefail-cname * -cname-loose * -cname-forbid + * -reverse + * -reverse-any ZONE-A-LIKE * * adns new-resolver [RES-OPTIONS...] => RESOLVER * options: @@ -46,75 +48,53 @@ * -noenv|-debug|-logpid * -checkc-entex * -checkc-freq - * -reverse - * -reverse-any ZONE-A-LIKE * -config CONFIG-STRING * + * adns set-default-resolver RESOLVER + * cancels any outstanding queries from a previous anonymous + * default resolver + * * adns destroy-resolver RESOLVER + * cancels outstanding queries */ +#define _GNU_SOURCE + +#include + #include "tables.h" #include "hbytes.h" -IdDataTable adnstcl_resolvers= { "adns-res" }; -IdDataTable adnstcl_queries= { "adns-q" }; +/*---------- important types and forward declarations ----------*/ -typedef struct { - int ix; /* first! */ - Tcl_Interp *interp; fixme what about interpreter deletion - adns_state *ads; - int maxfd; - fd_set handling[3]; -} Resolver; +typedef struct Query Query; +typedef struct Resolver Resolver; +typedef struct OptionInfo OptionInfo; -typedef struct { - int ix; /* first! */ - adns_query *aqu; - Tcl_Obj *on_yes, *on_no, *on_fail, *xargs; -} Query; +static void asynch_sethandlers(Resolver *res); +static void asynch_cancelhandlers(Resolver *res); +static void asynch_perturbed(Resolver *res); -static adns_state *adnstcl_default; -static int default_auto; +static void asynch_query_dispose(Tcl_Interp *interp, Query *query); -#define RRTYPE_EXACTLY(t) { #t, adns_r_##t } -#define RRTYPE_RAW(t) { #t, adns_r_##t##_raw } -#define RRTYPE_PLUS(t) { #t "+", adns_r_##t } -#define RRTYPE_MINUS(t) { #t "-", adns_r_##t##_raw } +#define ASSOC_DEFAULTRES "adns-defaultresolver" -const AdnsTclRRTypeInfo adnstclrrtypeinfos[]= { - RRTYPE_EXACTLY(a), - RRTYPE_EXACTLY(cname), - RRTYPE_EXACTLY(hinfo), - RRTYPE_EXACTLY(addr), - - RRTYPE_RAW(ns), - RRTYPE_RAW(mx), - - RRTYPE_EXACTLY(soa), - RRTYPE_EXACTLY(ptr), - RRTYPE_EXACTLY(rp), - - RRTYPE_MINUS(soa), - RRTYPE_MINUS(ptr), - RRTYPE_MINUS(rp), - { 0 } -}; +/*---------- common resolver/query option processing ----------*/ typedef struct { /* this struct type is used to hold both resolver and query options */ /* common to resolver and query: */ - unsigned long *aflags; - unsigned long *sflags; + unsigned long aflags; + unsigned long sflags; /* resolver: */ FILE *errfile; Tcl_Obj *errcallback; const char *config_string; /* query: */ - adns_state *resolver; + Resolver *resolver; const char *reverseany; } OptionParse; -typedef struct OptionInfo OptionInfo; struct OptionInfo { const char *name; int (*fn)(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg, @@ -124,58 +104,22 @@ struct OptionInfo { }; enum { - oisf_makedefault= 0x0001, oisf_reverse= 0x0002 }; static int oiufn_f(const OptionInfo *oi, unsigned long *flags) { - flags &= ~oi->flags_remove; - flags |= oi->flsgs_add; + *flags &= ~oi->flags_remove; + *flags |= oi->flags_add; + return TCL_OK; } static int oifn_fa(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg, - OptionParse *op) { oiufn_f(oi,op->aflags); } + OptionParse *op) { return oiufn_f(oi,&op->aflags); } static int oifn_fs(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg, - OptionParse *op) { oiufn_f(oi,op->sflags); } - -static int oifn_errfile(Tcl_Interp *ip, const OptionInfo *oi, - Tcl_Obj *arg, OptionParse *op) { - int rc; - const char *str; - - rc= pat_string(ip,arg,&str); if (rc) return rc; - if (!strcmp(str,"stderr")) op.errfile= stderr; - else if (!strcmp(str,"stdout")) op.errfile= stdout; - else return staticerr(ip,"-errfile argument must be stderr or stdout",0); - - op.aflags &= ~adns_if_noerrprint; - op.errcallback= 0; - return TCL_OK; -} - -static int oifn_errcallback(Tcl_Interp *ip, const OptionInfo *oi, - Tcl_Obj *arg, OptionParse *op) { - op.errcallback= arg; - op.aflags &= ~adns_if_noeerpring; - op.errfile= 0; - return TCL_OK; -} - -static int oifn_config(Tcl_Interp *ip, const OptionInfo *oi, - Tcl_Obj *arg, OptionParse *op) { - return pat_string(ip,arg,&op.config_string); -} - -static int oifn_resolver(Tcl_Interp *ip, const OptionInfo *oi, - Tcl_Obj *arg, OptionParse *op) { - void *val_v; - rc= pat_iddata(ip,arg,&val_v,adnstcl_resolvers); - if (rc) return rc; - op.resolver= val_v; -} + OptionParse *op) { return oiufn_f(oi,&op->sflags); } static int oifn_reverse_any(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg, OptionParse *op) { - return pat_string(ip,arg,&op.reverseany); + return pat_string(ip,arg,&op->reverseany); } #define OIFA1(t,f,r) { "-" #f, oifn_fa, 0, adns_##t##_##f, r } @@ -183,44 +127,27 @@ static int oifn_reverse_any(Tcl_Interp *ip, const OptionInfo *oi, #define OIFS(f) { "-" #f, oifn_fs, 0, oisf_##f, 0 } #define OICA(o) { "-" #o, oifn_##o, 1 } -static const OptionInfo resolver_optioninfos[]= { - OIFA1(if,noenv, 0), - OIFA1(if,debug, adns_if_noerrprint), - OIFA1(if,logpid, adns_if_noerrprint), - OIFA1(if,noerrprint, adns_if_debug), - OIFA2(if,checkc,entex), - OIFA2(if,checkc,freq), - OIFS(makedefault), - OICA(errprint), - OICA(errcallback), - OICA(config), - { 0 } -}; - -static const OptionInfo query_optioninfos[]= { - OIFA1(if,search,0), - OIFA1(if,usevc,0), - OIFA2(if,quoteok,query), - OIFA2(if,quoteok,anshost), - OIFA2(if,quotefail,cname), - OIFA2(if,cname,loose), - OIFA2(if,cname,forbid), - OICA(resolver), - OIFS(reverse), - { "-reverse-any", oifn_reverse_any, 1 }, - { 0 } -}; +static void optparse_blank(OptionParse *op) { + memset(op,0,sizeof(*op)); + op->errfile= stderr; + op->errcallback= 0; + op->config_string= 0; +} static int parse_options(Tcl_Interp *ip, int objc, Tcl_Obj *const *objv, const OptionInfo opttable[], OptionParse *op) { const OptionInfo *oi; + const void *oi_v; Tcl_Obj *arg; - + int rc; + + objc--; objv++; for (;;) { if (!objc--) break; - - rc= pat_enum(ip,*objv++, &oi,opttable, sizeof(OptionInfo)); + rc= pat_enum(ip, *objv++, &oi_v, opttable, sizeof(OptionInfo), + "query or resolver option"); if (rc) return rc; + oi= oi_v; if (oi->takesarg) { if (!objc--) { @@ -237,124 +164,302 @@ static int parse_options(Tcl_Interp *ip, int objc, Tcl_Obj *const *objv, return TCL_OK; } -int do_adns_destroy_resolver(ClientData cd, Tcl_Interp *ip, void *res_v) { - adns_state *res= res_v; +/*---------- resolver management ----------*/ - if (res == adnstcl_default) { - adnstcl_default= 0; - default_auto= 0; - } - adns_finish(res); fixme what about outstanding qs +struct Resolver { + int ix; /* first! */ + Tcl_Interp *interp; + adns_state ads; + Tcl_TimerToken timertoken; + int maxfd; + fd_set handling[3]; + ScriptToInvoke errcallback; + Tcl_Obj *errstring_accum; +}; + +/* The default resolver is recorded using Tcl_SetAssocData with key + * ASSOC_DEFAULTRES to record the Resolver*. If it was explicitly + * created with `adns new-resolver' then ix will be >=0, and the + * resolver will be destroyed - leaving no default - when explicitly + * requested. If it was implicitly created (by starting a query when + * there is no default) then ix will be -1. */ + +static int oifn_errfile(Tcl_Interp *ip, const OptionInfo *oi, + Tcl_Obj *arg, OptionParse *op) { + int rc; + const char *str; + + rc= pat_string(ip,arg,&str); if (rc) return rc; + if (!strcmp(str,"stderr")) op->errfile= stderr; + else if (!strcmp(str,"stdout")) op->errfile= stdout; + else return staticerr(ip,"-errfile argument must be stderr or stdout",0); + + op->aflags &= ~adns_if_noerrprint; + op->errcallback= 0; return TCL_OK; } -static void asynch_check(Resolver *res); +static int oifn_errcallback(Tcl_Interp *ip, const OptionInfo *oi, + Tcl_Obj *arg, OptionParse *op) { + op->errcallback= arg; + op->aflags &= ~adns_if_noerrprint; + op->errfile= 0; + return TCL_OK; +} -static void asynch_timerhandler(void *res_v) { - Resolver *res= res_v; - res->timertoken= 0; - adns_processtimeouts(res->ads,0); - checkqueries_updatehandlers(res); +static int oifn_config(Tcl_Interp *ip, const OptionInfo *oi, + Tcl_Obj *arg, OptionParse *op) { + return pat_string(ip,arg,&op->config_string); } -static void asynch_filehandler(void *res_v, int mask) { - Resolver *res= res_v; - ec= adns_processany(res->ads); - if (ec) adns_globalsystemfailure(res->ads); - checkqueries_updatehandlers(res); +static const OptionInfo resolver_optioninfos[]= { + OIFA1(if,noenv, 0), + OIFA1(if,debug, adns_if_noerrprint), + OIFA1(if,logpid, adns_if_noerrprint), + OIFA1(if,noerrprint, adns_if_debug), + OIFA2(if,checkc,entex), + OIFA2(if,checkc,freq), + OICA(errfile), + OICA(errcallback), + OICA(config), + { 0 } +}; + +static void adnslogfn_flushmessage(Resolver *res) { + scriptinv_invoke(&res->errcallback, 1, &res->errstring_accum); + Tcl_SetObjLength(res->errstring_accum, 0); } -static void asynch_sethandlers(Resolver *res, int shutdown) { - fd_set want[3]; - int maxfd; - struct timeval tvbuf, *timeout; +static void adnslogfn_callback(adns_state ads, void *logfndata, + const char *fmt, va_list al) { + Resolver *res= logfndata; + int l, newline; + char *str; - for (i=0; i<3; i++) FD_ZERO(&want[i]); - maxfd= 0; - timeout= 0; + l= vasprintf(&str,fmt,al); + if (l<0) { + posixerr(res->interp,errno,"construct adns log callback string"); + Tcl_BackgroundError(res->interp); + } - if (!shutdown) - adns_beforeselect(res->ads,&maxfd,&want[0],&want[1],&want[2], - &timeout,&tv_buf,0); + if (l==0) { free(str); return; } + if ((newline= l>0 && str[l-1]=='\n')) l--; - for (fd= 0; fd < maxfd || fd < res->maxfd; fd++) - for (i=0; i<3; i++) - if (!!FD_ISSET(fd, &res->handling[i]) - != !!FD_ISSET(fd, &want[i])) { - int mask=0; - if (FD->ISSET(fd, &want[0])) mask |= TCL_READABLE; - if (FD->ISSET(fd, &want[1])) mask |= TCL_WRITABLE; - if (FD->ISSET(fd, &want[2])) mask |= TCL_EXCEPTION; - if (mask) Tcl_CreateFileHandler(fd,mask,filehandler,res); - else Tcl_DeleteFileHandler(fd); - } + if (!res->errstring_accum) { + res->errstring_accum= Tcl_NewStringObj(str,l); + Tcl_IncrRefCount(res->errstring_accum); + } else { + Tcl_AppendToObj(res->errstring_accum,str,l); + } + free(str); - Tcl_DeleteTimerHandler(res->timertoken); + if (newline) + adnslogfn_flushmessage(res); +} - if (timeout) { - int milliseconds; +static Resolver *default_resolver(Tcl_Interp *ip) { + return Tcl_GetAssocData(ip,ASSOC_DEFAULTRES,0); +} - if (timeout->tv_sec >= INT_MAX/1000 - 1) - milliseconds= INT_MAX; - else - milliseconds= timeout->tv_sec * 1000 + - (timeout->tv_usec + 999) / 1000; - - res->timertoken= Tcl_CreateTimerHandler(milliseconds,timerhandler,res); +static void destroy_resolver(Tcl_Interp *ip, Resolver *res) { + void *query_v; + int logstring_len; + char *rstr; + adns_query aqu; + + if (res == default_resolver(ip)) + Tcl_DeleteAssocData(ip,ASSOC_DEFAULTRES); + + if (res->errstring_accum) { + rstr= Tcl_GetStringFromObj(res->errstring_accum, &logstring_len); + assert(rstr); + if (logstring_len) + adnslogfn_flushmessage(res); } + + if (res->ads) { + /* although adns would throw these away for us, we need to + * destroy our own data too and only adns has a list of them */ + for (;;) { + adns_forallqueries_begin(res->ads); + aqu= adns_forallqueries_next(res->ads, &query_v); + if (!aqu) break; + asynch_query_dispose(ip, query_v); + } + adns_finish(res->ads); + res->ads= 0; + } + asynch_cancelhandlers(res); + scriptinv_cancel(&res->errcallback); + Tcl_EventuallyFree(res, Tcl_Free); +} + +static void destroy_resolver_idtabcb(Tcl_Interp *ip, void *resolver_v) { + destroy_resolver(ip,resolver_v); +} +static void destroy_resolver_defcb(ClientData resolver_v, Tcl_Interp *ip) { + destroy_resolver(ip,resolver_v); } +int do_adns_destroy_resolver(ClientData cd, Tcl_Interp *ip, void *res_v) { + tabledataid_disposing(ip,res_v,&adnstcl_resolvers); + destroy_resolver(ip,res_v); + return TCL_OK; +} + +static int create_resolver(Tcl_Interp *ip, const OptionParse *op, + Resolver **res_r) { + Resolver *res=0; + int rc, i, ec; + + res= TALLOC(sizeof(*res)); assert(res); + res->ix= -1; + res->interp= ip; + res->ads= 0; + res->timertoken= 0; + res->maxfd= 0; + for (i=0; i<3; i++) FD_ZERO(&res->handling[i]); + scriptinv_init(&res->errcallback); + res->errstring_accum= 0; + + if (op->errcallback) + scriptinv_set(&res->errcallback, ip, op->errcallback, 0); + + ec= adns_init_logfn(&res->ads, + op->aflags | adns_if_noautosys, + op->config_string, + op->errcallback ? adnslogfn_callback : 0, + op->errcallback ? (void*)res : (void*)op->errfile); + if (ec) { rc= posixerr(ip,ec,"create adns resolver"); goto x_rc; } + + *res_r= res; + return TCL_OK; + + x_rc: + if (res) { + if (res->ads) adns_finish(res->ads); + TFREE(res); + } + return rc; +} + int do_adns_new_resolver(ClientData cd, Tcl_Interp *ip, int objc, Tcl_Obj *const *objv, void **result) { OptionParse op; - adns_state *ads=0; Resolver *res=0; + int rc; - op.aflags= adns_if_noautosys; - op.sflags= 0; - op.errfile= 0; - op.errcallback= 0; - op.config_string= 0; + optparse_blank(&op); rc= parse_options(ip,objc,objv,resolver_optioninfos,&op); - if (rc) goto x_rc; - - res= TALLOC(sizeof(*res)); assert(res); - res->maxfd= 0; - for (i=0; i<3; i++) FD_ZERO(&res->handling); - res->interp= ip; + if (rc) return rc; if (op.aflags & adns_if_noerrprint) { op.errfile= 0; op.errcallback= 0; } - ec= adns_init_logfn(&ads, op.aflags, op.configstring, - op.errcallback ? adnslogfn_callback : 0, - op.errcallback ? op.errcallback : op.errfile); - if (ec) { rc= posixerr(ip,ec,"create adns resolver"); goto x_rc; } - res->ads= ads; + rc= create_resolver(ip, &op, &res); + if (rc) return rc; + + *result= res; + return TCL_OK; +} + +int do_adns_set_default_resolver(ClientData cd, Tcl_Interp *ip, void *res_v) { + Resolver *res= res_v; + Tcl_DeleteAssocData(ip,ASSOC_DEFAULTRES); + Tcl_SetAssocData(ip, ASSOC_DEFAULTRES, 0, res); + return TCL_OK; +} + +const IdDataSpec adnstcl_resolvers= { + "adns-res", "adns-resolvers-table", destroy_resolver_idtabcb +}; + +/*---------- query, query option and answers - common stuff ----------*/ + +#define RRTYPE_EXACTLY(t) { #t, adns_r_##t } +#define RRTYPE_RAW(t) { #t, adns_r_##t##_raw } +#define RRTYPE_PLUS(t) { #t "+", adns_r_##t } +#define RRTYPE_MINUS(t) { #t "-", adns_r_##t##_raw } - if (op.errcallback) - Tcl_IncrRefCount(op.errcallback); +const AdnsTclRRTypeInfo adnstclrrtypeinfos[]= { + RRTYPE_EXACTLY(a), + RRTYPE_EXACTLY(cname), + RRTYPE_EXACTLY(hinfo), + RRTYPE_EXACTLY(addr), - rc= update_handlers(&res); if (rc) goto x_rc; + RRTYPE_RAW(ns), + RRTYPE_RAW(mx), + + RRTYPE_EXACTLY(soa), + RRTYPE_EXACTLY(ptr), + RRTYPE_EXACTLY(rp), + + RRTYPE_MINUS(soa), + RRTYPE_MINUS(ptr), + RRTYPE_MINUS(rp), + { 0 } +}; + +static int oifn_resolver(Tcl_Interp *ip, const OptionInfo *oi, + Tcl_Obj *arg, OptionParse *op) { + void *val_v; + int rc; - *result= res; + rc= pat_iddata(ip,arg,&val_v,&adnstcl_resolvers); + if (rc) return rc; + op->resolver= val_v; return TCL_OK; } -static int query_submit(Tcl_Interp *ip, int objc, Tcl_Obj *const *objv, - const char *domain) { +static const OptionInfo query_optioninfos[]= { + OIFA1(qf,search,0), + OIFA1(qf,usevc,0), + OIFA2(qf,quoteok,query), + OIFA2(qf,quoteok,anshost), + OIFA2(qf,quotefail,cname), + OIFA2(qf,cname,loose), + OIFA2(qf,cname,forbid), + OICA(resolver), + OIFS(reverse), + { "-reverse-any", oifn_reverse_any, 1 }, + { 0 } +}; + +static int query_submit(Tcl_Interp *ip, + const AdnsTclRRTypeInfo *type, const char *domain, + int queryopts_objc, Tcl_Obj *const *queryopts_objv, + adns_query *aqu_r, void *context, Resolver **res_r) { struct sockaddr sa; static const int aftry[]= { AF_INET, AF_INET6 }; + OptionParse op; + OptionParse res_op; + int rc, r, ec; + adns_state ads; op.aflags= adns_qf_owner; op.sflags= 0; op.resolver= 0; - rc= parse_options(ip,objc,objv,query_optioninfos,&op); + op.reverseany= 0; + rc= parse_options(ip, queryopts_objc,queryopts_objv, query_optioninfos,&op); if (rc) return rc; + if (!op.resolver) { + op.resolver= default_resolver(ip); + if (!op.resolver) { + optparse_blank(&res_op); + rc= create_resolver(ip, &res_op, &op.resolver); + if (rc) return rc; + + Tcl_SetAssocData(ip, ASSOC_DEFAULTRES, + destroy_resolver_defcb, op.resolver); + } + } + + *res_r= op.resolver; + if (op.reverseany || (op.sflags & oisf_reverse)) { const int *af; for (af=aftry; af < af + sizeof(af)/sizeof(*af); af++) { @@ -363,25 +468,21 @@ static int query_submit(Tcl_Interp *ip, int objc, Tcl_Obj *const *objv, r= inet_pton(*af,domain,&sa); if (!r) goto af_found; } - return staticerr(ip,ec,"invalid address for adns reverse submit",""); + return staticerr(ip,"invalid address for adns reverse submit",""); + af_found:; } - if (!op.resolver) { - if (!adnstcl_default) { - ec= adns_init(&adnstcl_default,adns_if_noautosys,0); - if (ec) return posixerr(ip,ec,"create default adns resolver"); - } - default_auto= 1; - op.resolver= adnstcl_default; - } + ads= op.resolver->ads; if (op.reverseany) { - ec= adns_submit_reverse_any(op.resolver, &sa, op.reverseany, rrtype, - op.aflags, 0, &query); + ec= adns_submit_reverse_any(ads, &sa, op.reverseany, + type->number, op.aflags, context, aqu_r); } else if (op.sflags & oisf_reverse) { - ec= adns_submit_reverse(op.resolver, &sa, rrtype, op.aflags, 0, &query); + ec= adns_submit_reverse(ads, &sa, + type->number, op.aflags, context, aqu_r); } else { - ec= adns_submit(op.resolver, domain, rrtype, op.aflags, 0, &query); + ec= adns_submit(ads, domain, + type->number, op.aflags, context, aqu_r); } if (ec) return posixerr(ip,ec,"submit adns query"); @@ -389,117 +490,294 @@ static int query_submit(Tcl_Interp *ip, int objc, Tcl_Obj *const *objv, return TCL_OK; } -int do_adns_lookup(ClientData cd, Tcl_Interp *ip, - const AdnsTclRRTypeInfo *rrtype, - const char *domain, - int objc, Tcl_Obj *const *objv, - Tcl_Obj **result) { - OptionParse op; +#define RESULTSTATUS_LLEN 4 +#define RESULTLIST_LLEN 7 - rc= query_submit(ip,domain,objc,objv,&op, ); if (rc) return rc; - - ec= adns_wait(op.resolver,&query,&answer,0); - if (ec) return posixerr(ip,ec,"wait for adns lookup"); - - ...; +static void make_resultstatus(Tcl_Interp *ip, adns_status status, + Tcl_Obj *results[RESULTSTATUS_LLEN]) { + results[0]= ret_string(ip, adns_errtypeabbrev(status)); + results[1]= ret_int(ip, status); + results[2]= ret_string(ip, adns_errabbrev(status)); + results[3]= ret_string(ip, adns_strerror(status)); } -#define RESULTS_LLEN 7 -static void make_results(Tcl_Interp *ip, adns_answer *answer, - Tcl_Obj results[RESULTS_LLEN]) { - Tcl_Obj *rdata; +static Tcl_Obj *make_resultrdata(Tcl_Interp *ip, adns_answer *answer) { + Tcl_Obj **rdata, *rl; + int i, rrsz; + adns_status st; + char *datap, *rdatastring; - results[0]= ret_string(ip, adns_errtypeabbrev(answer->status)); - results[1]= ret_int(ip, answer->status); - results[2]= ret_string(ip, adns_errabbrev(answer->status)); - results[3]= ret_string(ip, adns_strstatus(answer->status)); - results[4]= ret_string(ip, answer->owner); - results[5]= ret_string(ip, answer->cname ? answer->cname : ""); - rdata= TALLOC(sizeof(*rdata) * answer->nrrs); for (i=0, datap=answer->rrs.untyped; - inrrs; i++, datap += rrsz) { + inrrs; + i++, datap += rrsz) { st= adns_rr_info(answer->type, 0,0, &rrsz, datap, &rdatastring); assert(!st); rdata[i]= ret_string(ip, rdatastring); free(rdatastring); } - results[6]= Tcl_NewListObj(RESULTS_LLEN,rdata); + rl= Tcl_NewListObj(answer->nrrs, rdata); TFREE(rdata); + return rl; } + +static void make_resultlist(Tcl_Interp *ip, adns_answer *answer, + Tcl_Obj *results[RESULTLIST_LLEN]) { + + make_resultstatus(ip, answer->status, results); + assert(RESULTSTATUS_LLEN==4); + results[4]= ret_string(ip, answer->owner); + results[5]= ret_string(ip, answer->cname ? answer->cname : ""); + results[6]= make_resultrdata(ip, answer); +} + +/*---------- synchronous query handling ----------*/ + +static int synch(Tcl_Interp *ip, const AdnsTclRRTypeInfo *rrtype, + const char *domain, + int objc, Tcl_Obj *const *objv, adns_answer **answer_r) { + adns_query aqu; + Resolver *res; + int rc, ec; -static void asynch_query_dispose(Query *query) { - scriptinv_cancel(&query->on_yes); - scriptinv_cancel(&query->on_no); - scriptinv_cancel(&query->on_fail); - if (query->xargs) Tcl_DecrRefCount(query->xargs); - if (query->aqu) adns_cancel(query->aqu); - TFREE(query); + rc= query_submit(ip,rrtype,domain,objc,objv,&aqu,0,&res); + if (rc) return rc; + + ec= adns_wait(res->ads,&aqu,answer_r,0); + assert(!ec); + + asynch_perturbed(res); + return TCL_OK; } -static void asynch_check(Resolver *res) { +int do_adns_lookup(ClientData cd, Tcl_Interp *ip, + const AdnsTclRRTypeInfo *rrtype, + const char *domain, + int objc, Tcl_Obj *const *objv, + Tcl_Obj **result) { + int rc; + adns_answer *answer; + + rc= synch(ip,rrtype,domain,objc,objv,&answer); if (rc) return rc; + + if (answer->status) { + Tcl_Obj *problem[RESULTSTATUS_LLEN]; + make_resultstatus(ip, answer->status, problem); + *result= Tcl_NewListObj(RESULTSTATUS_LLEN, problem); + } else { + *result= make_resultrdata(ip, answer); + } + free(answer); + return TCL_OK; +} + +int do_adns_synch(ClientData cd, Tcl_Interp *ip, + const AdnsTclRRTypeInfo *rrtype, + const char *domain, + int objc, Tcl_Obj *const *objv, + Tcl_Obj **result) { + int rc; + adns_answer *answer; + Tcl_Obj *results[RESULTLIST_LLEN]; + + rc= synch(ip,rrtype,domain,objc,objv,&answer); if (rc) return rc; + make_resultlist(ip,answer,results); + free(answer); + *result= Tcl_NewListObj(RESULTLIST_LLEN, results); + return TCL_OK; +} + +/*---------- asynchronous query handling ----------*/ + +struct Query { + int ix; /* first! */ + Resolver *res; + adns_query aqu; + ScriptToInvoke on_yes, on_no, on_fail; + Tcl_Obj *xargs; +}; + +static void asynch_check_now(Resolver *res); + +static void asynch_timerhandler(void *res_v) { + Resolver *res= res_v; + res->timertoken= 0; + adns_processtimeouts(res->ads,0); + asynch_check_now(res); +} + +static void asynch_filehandler(void *res_v, int mask) { + Resolver *res= res_v; + int ec; + + ec= adns_processany(res->ads); + if (ec) adns_globalsystemfailure(res->ads); + asynch_check_now(res); +} + +static void asynch_sethandlers_generic(Resolver *res, + int shutdown /*from _cancelhandlers*/, + int immediate /*from _perturbed*/) { + fd_set want[3]; + int maxfd; + struct timeval tv_buf, *timeout; + int i, fd; + + timeout= 0; + maxfd= 0; + for (i=0; i<3; i++) FD_ZERO(&want[i]); + + if (!shutdown) + adns_beforeselect(res->ads,&maxfd,&want[0],&want[1],&want[2], + &timeout,&tv_buf,0); + + for (fd= 0; fd < maxfd || fd < res->maxfd; fd++) + for (i=0; i<3; i++) + if (!!FD_ISSET(fd, &res->handling[i]) + != !!FD_ISSET(fd, &want[i])) { + int mask=0; + if (FD_ISSET(fd, &want[0])) mask |= TCL_READABLE; + if (FD_ISSET(fd, &want[1])) mask |= TCL_WRITABLE; + if (FD_ISSET(fd, &want[2])) mask |= TCL_EXCEPTION; + if (mask) { + Tcl_CreateFileHandler(fd,mask,asynch_filehandler,res); + FD_SET(fd, &res->handling[i]); + } else { + Tcl_DeleteFileHandler(fd); + FD_CLR(fd, &res->handling[i]); + } + } + res->maxfd= maxfd; + + Tcl_DeleteTimerHandler(res->timertoken); + + if (immediate) { + res->timertoken= Tcl_CreateTimerHandler(0,asynch_timerhandler,res); + } else if (timeout) { + int milliseconds; + + if (timeout->tv_sec >= INT_MAX/1000 - 1) + milliseconds= INT_MAX; + else + milliseconds= timeout->tv_sec * 1000 + + (timeout->tv_usec + 999) / 1000; + + res->timertoken= + Tcl_CreateTimerHandler(milliseconds,asynch_timerhandler,res); + } +} + +static void asynch_sethandlers(Resolver *res) { + asynch_sethandlers_generic(res,0,0); +} +static void asynch_cancelhandlers(Resolver *res) { + asynch_sethandlers_generic(res,1,0); +} +static void asynch_perturbed(Resolver *res) { + asynch_sethandlers_generic(res,0,1); +} + +static void asynch_check_now(Resolver *res) { Tcl_Interp *interp= res->interp; - adns_query *aqu; + adns_query aqu; adns_answer *answer; - Query *qu; + void *query_v; + Query *query; ScriptToInvoke *si; - Tcl_Obj results[RESULTS_LLEN]; + int ec; + Tcl_Obj *results[RESULTLIST_LLEN]; + Tcl_Preserve(res); + for (;;) { - ec= adns_check(res->ads, &aqu, &answer, &qu); + if (!res->ads) { /* oh, it has been destroyed! */ + Tcl_Release(res); + return; + } + + aqu= 0; + ec= adns_check(res->ads, &aqu, &answer, &query_v); if (ec==ESRCH || ec==EAGAIN) break; assert(!ec); + query= query_v; - qu->aqu= 0; + query->aqu= 0; + tabledataid_disposing(interp, query, &adnstcl_queries); - si= (!answer->status ? si= &query->on_yes + si= (!answer->status ? &query->on_yes : answer->status > adns_s_max_tempfail ? &query->on_no : &query->on_fail); - adnstcl_queries[qu->ix].a= 0; - make_results(ip, answer, results); - scriptinv_invoke(si, RESULTS_LLEN, results); - asynch_query_dispose(query); + make_resultlist(interp, answer, results); + free(answer); + scriptinv_invoke(si, RESULTLIST_LLEN, results); + asynch_query_dispose(interp, query); } - asynch_sethandlers(res,0); + asynch_sethandlers(res); + + Tcl_Release(res); } int do_adns_asynch(ClientData cd, Tcl_Interp *ip, - const AdnsTclRRTypeInfo *rrtype, const char *domain, Tcl_Obj *on_yes, Tcl_Obj *on_no, Tcl_Obj *on_fail, Tcl_Obj *xargs, + const AdnsTclRRTypeInfo *rrtype, const char *domain, int objc, Tcl_Obj *const *objv, void **result) { Query *query; - adns_query *aqu; + int rc; + Resolver *res= 0; query= TALLOC(sizeof(*query)); + query->ix= -1; query->aqu= 0; scriptinv_init(&query->on_yes); scriptinv_init(&query->on_no); scriptinv_init(&query->on_fail); query->xargs= 0; - rc= query_submit(ip,rrtype,domain, ...); if (rc) goto x_rc; - query->aqu= aqu; + rc= query_submit(ip,rrtype,domain,objc,objv,&query->aqu,query,&query->res); + if (rc) goto x_rc; + + res= query->res; - rc= scriptinv_set(&query->on_yes, ip,on_yes); if (rc) goto x_rc; - rc= scriptinv_set(&query->on_no, ip,on_no); if (rc) goto x_rc; - rc= scriptinv_set(&query->on_fail,ip,on_fail); if (rc) goto x_rc; + rc= scriptinv_set(&query->on_yes, ip,on_yes, xargs); if (rc) goto x_rc; + rc= scriptinv_set(&query->on_no, ip,on_no, xargs); if (rc) goto x_rc; + rc= scriptinv_set(&query->on_fail,ip,on_fail,xargs); if (rc) goto x_rc; query->xargs= xargs; Tcl_IncrRefCount(xargs); *result= query; - - return TCL_OK; + query= 0; /* do not dispose */ + rc= TCL_OK; x_rc: - asynch_cancel(query); + if (query) asynch_query_dispose(ip, query); + if (res) asynch_perturbed(res); return rc; } int do_adns_asynch_cancel(ClientData cd, Tcl_Interp *ip, void *query_v) { - adns_query *qu; - + Query *query= query_v; + Resolver *res= query->res; + asynch_query_dispose(ip, query); + asynch_perturbed(res); + return TCL_OK; } -int do_adns_synch(ClientData cd, Tcl_Interp *ip, const AdnsTclRRTypeInfo *rrtype, const char *domain, int objc, Tcl_Obj *const *objv, adns_answer **result); +static void asynch_query_dispose(Tcl_Interp *interp, Query *query) { + tabledataid_disposing(interp, query, &adnstcl_queries); + scriptinv_cancel(&query->on_yes); + scriptinv_cancel(&query->on_no); + scriptinv_cancel(&query->on_fail); + if (query->xargs) Tcl_DecrRefCount(query->xargs); + if (query->aqu) adns_cancel(query->aqu); + TFREE(query); +} + +static void destroy_query_idtabcb(Tcl_Interp *interp, void *query_v) { + asynch_query_dispose(interp, query_v); +} + +const IdDataSpec adnstcl_queries= { + "adns", "adns-query-table", destroy_query_idtabcb +};