pike.git / lib / modules / Protocols.pmod / DNS.pmod

version» Context lines:

pike.git/lib/modules/Protocols.pmod/DNS.pmod:1: - // $Id$ +    // Not yet finished -- Fredrik Hubinette       //inherit Stdio.UDP : udp; - //! RFC 1034, RFC 1035 and RFC 2308 +  + //! Support for the Domain Name System protocol. + //! + //! Implements @rfc{1034@}, @rfc{1035@} and @rfc{2308@}. +    protected void send_reply(mapping r, mapping q, mapping m, Stdio.UDP udp);      #pike __REAL_VERSION__    -  + // documentation taken from RFC 2136 +  + //! No error condition.   final constant NOERROR=0; -  +  + //! The name server was unable to interpret the request due to a + //! format error.   final constant FORMERR=1; -  +  + //! The name server encountered an internal failure while processing + //! this request, for example an operating system error or a + //! forwarding timeout.   final constant SERVFAIL=2; -  +  + //! Some name that ought to exist, does not exist.   final constant NXDOMAIN=3; -  +  + //! The name server does not support the specified Opcode.   final constant NOTIMPL=4; -  +  + //! The name server refuses to perform the specified operation for + //! policy or security reasons.   final constant REFUSED=5; -  +  + //! Some RRset that ought to exist, does not exist.   final constant NXRRSET=8;      final constant QUERY=0;      //! Resource classes   enum ResourceClass   {    //! Class Internet    C_IN=1,   
pike.git/lib/modules/Protocols.pmod/DNS.pmod:50:       //! Type - mail forwarder (Obsolete - use MX)    T_MF=4,       //! Type - canonical name for an alias    T_CNAME=5,       //! Type - start of a zone of authority    T_SOA=6,    -  //! Type - mailbox domain name (EXPERIMENTAL) +  //! Type - mailbox domain name (Obsolete)    T_MB=7,    -  //! Type - mail group member (EXPERIMENTAL) +  //! Type - mail group member (Obsolete)    T_MG=8,    -  //! Type - mail rename domain name (EXPERIMENTAL) +  //! Type - mail rename domain name (Obsolete)    T_MR=9,    -  //! Type - null RR (EXPERIMENTAL) +  //! Type - null RR (Obsolete @rfc{1035@})    T_NULL=10,    -  //! Type - well known service description +  //! Type - well known service description (Obsolete @rfc{1123@} and +  //! @rfc{1127@})    T_WKS=11,       //! Type - domain name pointer    T_PTR=12,       //! Type - host information    T_HINFO=13,    -  //! Type - mailbox or mail list information +  //! Type - mailbox or mail list information (Obsolete)    T_MINFO=14,       //! Type - mail exchange    T_MX=15,       //! Type - text strings    T_TXT=16,    -  //! Type - IPv6 address record (RFC 1886, deprecated) +  //! Type - Responsible Person +  T_RP=17, +  +  //! Type - AFC database record (@rfc{1183@}) +  T_AFSDB=18, +  +  //! Type - X25 PSDN address (@rfc{1183@}) +  T_X25=19, +  +  //! Type - ISDN address (@rfc{1183@}) +  T_ISDN=20, +  +  //! Type - Route Through (@rfc{1183@}) +  T_RT=21, +  +  //! Type - OSI Network Service Access Protocol (@rfc{1348@}, +  //! @rfc{1637@} and @rfc{1706@}) +  T_NSAP=22, +  +  //! Type - OSI NSAP Pointer (@rfc{1348@} and Obsolete @rfc{1637@}) +  T_NSAP_PTR=23, +  +  //! Type - Signature (@rfc{2535@}) +  T_SIG=24, +  +  //! Type - Key record (@rfc{2535@} and @rfc{2930@}) +  T_KEY=25, +  +  //! Type - Pointer to X.400 mapping information (@rfc{1664@}) +  T_PX=26, +  +  //! Type - Global Position (@rfc{1712@} Obsolete use LOC). +  T_GPOS=27, +  +  //! Type - IPv6 address record (@rfc{1886@})    T_AAAA=28,    -  //! Type - Location Record (RFC 1876) +  //! Type - Location Record (@rfc{1876@})    T_LOC=29,    -  //! Type - Service location record (RFC 2782) +  //! Type - Next (@rfc{2065@} and Obsolete @rfc{3755@}) +  T_NXT=30, +  +  //! Type - Nimrod Endpoint IDentifier (draft) +  T_EID=31, +  +  //! Type - Nimrod Locator (draft) +  T_NIMLOC=32, +  +  //! Type - Service location record (@rfc{2782@})    T_SRV=33,    -  //! Type - NAPTR (RFC 3403) +  //! Type - ATM End System Address (af-saa-0069.000) +  T_ATMA=34, +  +  //! Type - NAPTR (@rfc{3403@})    T_NAPTR=35,    -  //! Type - IPv6 address record (RFC 2874, incomplete support) +  //! Type - Key eXchanger record (@rfc{2230@}) +  T_KX=36, +  +  //! Type - Certificate Record (@rfc{4398@}) +  T_CERT=37, +  +  //! Type - IPv6 address record (@rfc{2874@} and Obsolete @rfc{6563@})    T_A6=38,    -  //! Type - SPF - Sender Policy Framework (RFC 4408) +  //! Type - Delegation Name (@rfc{2672@}) +  T_DNAME=39, +  +  //! Type - Kitchen Sink (draft) +  T_SINK=40, +  +  //! Type - Option (@rfc{2671@}) +  T_OPT=41, +  +  //! Type - Address Prefix List (@rfc{3123@}) +  T_APL=42, +  +  //! Type - Delegation Signer (@rfc{4034@}) +  T_DS=43, +  +  //! Type - SSH Public Key Fingerprint (@rfc{4255@}) +  T_SSHFP=44, +  +  //! Type - IPsec Key (@rfc{4025@}) +  T_IPSECKEY=45, +  +  //! Type - DNSSEC signature (@rfc{4034@}) +  T_RRSIG=46, +  +  //! Type - Next-Secure record (@rfc{4034@}) +  T_NSEC=47, +  +  //! Type - DNS Key record (@rfc{4034@}) +  T_DNSKEY=48, +  +  //! Type - DHCP identifier (@rfc{4701@}) +  T_DHCID=49, +  +  //! Type - NSEC record version 3 (@rfc{5155@}) +  T_NSEC3=50, +  +  //! Type - NSEC3 parameters (@rfc{5155@}) +  T_NSEC3PARAM=51, +  +  //! Type - TLSA certificate association (@rfc{6698@}) +  T_TLSA=52, +  +  //! Type - Host Identity Protocol (@rfc{5205@}) +  T_HIP=55, +  +  //! Type - SPF - Sender Policy Framework (@rfc{4408@})    T_SPF=99, -  +  +  // UserDB via DNS? +  T_UINFO=100, +  T_UID=101, +  T_GID=102, +  T_UNSPEC=103, +  +  //! Type - Secret key record (@rfc{2930@}) +  T_TKEY=249, +  +  //! Type - Transaction Signature (@rfc{2845@}) +  T_TSIG=250, +  +  //! Type - Incremental Zone Transfer (@rfc{1996@}) +  T_IXFR=251, +  +  //! Type - Authoritative Zone Transfer (@rfc{1035@}) +  T_AXFR=252, +  +  //! Type - Mail Box (MB, MG or MR) (Obsolete - use MX) +  T_MAILB=253, +  +  //! Type - Mail Agent (both MD and MF) (Obsolete - use MX) +  T_MAILA=254, +  +  //! Type - ANY - A request for all records +  T_ANY=255, +  +  //! Type - Certificate Authority Authorization (@rfc{6844@}) +  T_CAA=257, +  +  //! Type - DNSSEC Trust Authorities (draft) +  T_TA=32768, +  +  //! Type - DNSSEC Lookaside Validation Record (@rfc{4431@}) +  T_DLV=32769,   };    -  + //! Flag bits used in @[T_DNSKEY] RRs. + enum DNSKEY_Flags { +  F_ZONEKEY = 0x0100, //! Zone Key. +  F_SECUREENTRYPOINT = 0x0001, //! Secure Entry Point. + }; +  + //! DNSSEC Protocol types. + //! + //! @note + //! @rfc{4034@} obsoleted all but @[DNSSEC_DNSSEC]. + enum DNSSEC_Protocol { +  DNSSEC_TLS = 1, //! Reserved for use by TLS. +  DNSSEC_EMAIL = 2, //! Reserved for use by SMTP et al. +  DNSSEC_DNSSEC = 3, //! Key for use by DNSSEC. @rfc{4034:2.1.2@}. +  DNSSEC_IPSEC = 4, //! Reserved for use by IPSEC. +  DNSSEC_ALL = 255, //! Any use. Discouraged. + }; +  + //! DNSSEC Algorithm types. + enum DNSSES_Algorithm { +  DNSSEC_RSAMD5 = 1, //! RSA/MD5 @rfc{2537@}. +  DNSSEC_DH = 2, //! Diffie-Hellman @rfc{2539@}. +  DNSSEC_DSA = 3, //! DSA/SHA1 @rfc{2536@}. +  DNSSEC_ECC = 4, +  DNSSEC_RSASHA1 = 5, //! RSA/SHA1 @rfc{3110@}. +  +  DNSSEC_INDIRECT = 252, +  DNSSEC_PRIVATEDNS = 253, //! Private algorithm DNS-based @rfc{4035:A.1.1@}. +  DNSSEC_PRIVATEOID = 254, //! Private algorithm OID-based @rfc{4035:A.1.1@}. + }; +  + //! DNSSEC Digest types. + enum DNSSEC_Digests { +  DNSSEC_SHA1 = 1, //! SHA1 digest @rfc{4035:A.2@}. + }; +  + int safe_bind(Stdio.UDP udp, string|int port, string|void device) + { +  mixed err = catch { +  udp->bind(port, device, 1); +  return 1; +  }; + #if constant(System.EADDRINUSE) +  if (errno() == System.EADDRINUSE) return 0; + #endif + #if constant(System.WSAEACCES) +  if (errno() == System.WSAEACCES) return 0; + #endif +  werror("Protocols.DNS: Binding of UDP port failed with errno %d: %m.\n", +  errno()); +  master()->handle_error(err); +  return 0; + } +    //! Low level DNS protocol   class protocol   {    string mklabel(string s)    {    if(sizeof(s)>63)    error("Too long component in domain name.\n"); -  return sprintf("%c%s",sizeof(s),s); +  return sprintf("%1H",s);    }       protected private string mkname(string|array(string) labels, int pos,    mapping(string:int) comp)    {    if(stringp(labels))    labels = labels/"."-({""});    if(!labels || !sizeof(labels))    return "\0";    string n = labels*".";
pike.git/lib/modules/Protocols.pmod/DNS.pmod:155:    return mkname(entry->mf, pos, c);    case T_MB:    return mkname(entry->mb, pos, c);    case T_MG:    return mkname(entry->mg, pos, c);    case T_MR:    return mkname(entry->mr, pos, c);    case T_MX:    return sprintf("%2c", entry->preference)+mkname(entry->mx, pos+2, c);    case T_HINFO: -  return sprintf("%1c%s%1c%s", sizeof(entry->cpu||""), entry->cpu||"", -  sizeof(entry->os||""), entry->os||""); +  return sprintf("%1H%1H", entry->cpu||"", entry->os||"");    case T_MINFO:    string rmailbx = mkname(entry->rmailbx, pos, c);    return rmailbx + mkname(entry->emailbx, pos+sizeof(rmailbx), c);    case T_SRV:    return sprintf("%2c%2c%2c", entry->priority, entry->weight, entry->port) +    mkname(entry->target||"", pos+6, c);    case T_A:    return sprintf("%@1c", (array(int))((entry->a||"0.0.0.0")/".")[0..3]);    case T_AAAA:    return make_raw_addr6(entry->aaaa); -  +  case T_A6: +  if( stringp( entry->a6 ) || !entry->a6 ) +  return "\0"+make_raw_addr6(entry->a6); +  return sprintf( "%c%s%s", +  entry->a6->prefixlen, +  make_raw_addr6(entry->a6->address)[entry->a6->prefixlen/8..], +  entry->a6->prefixname||"");    case T_SOA:    string mname = mkname(entry->mname, pos, c);    return mname + mkname(entry->rname, pos+sizeof(mname), c) +    sprintf("%4c%4c%4c%4c%4c", entry->serial, entry->refresh,    entry->retry, entry->expire, entry->minimum);    case T_NAPTR:    string rnaptr = sprintf("%2c%2c", entry->order, entry->preference); -  rnaptr += sprintf("%1c%s%1c%s%1c%s%s", -  sizeof(entry->flags || ""), entry->flags || "", -  sizeof(entry->service || ""), entry->service || "", -  sizeof(entry->regexp || ""), entry->regexp || "", +  rnaptr += sprintf("%1H%1H%1H%s", +  entry->flags || "", +  entry->service || "", +  entry->regexp || "",    mkname(entry->replacement, pos, c));    return rnaptr;       case T_TXT: -  return Array.map(stringp(entry->txt)? ({entry->txt}):(entry->txt||({})), +  return map(stringp(entry->txt)? ({entry->txt}):(entry->txt||({})),    lambda(string t) { -  return sprintf("%1c%s", sizeof(t), t); +  return sprintf("%1H", t);    })*"";    case T_SPF: -  return Array.map(stringp(entry->spf)? ({entry->spf}):(entry->spf||({})), +  return map(stringp(entry->spf)? ({entry->spf}):(entry->spf||({})),    lambda(string t) { -  return sprintf("%1c%s", sizeof(t), t); +  return sprintf("%1H", t);    })*"";    case T_LOC:    // FIXME: Not implemented yet.    default:    return "";    }    }       protected private string encode_entries(array(mapping) entries, int pos,    mapping(string:int) comp)    {    string res="";    foreach(entries, mapping entry) {    string e = mkname(entry->name, pos, comp)+    sprintf("%2c%2c%4c", entry->type, entry->cl, entry->ttl);    pos += sizeof(e)+2;    string rd = entry->rdata || mkrdata(entry, pos, comp); -  res += e + sprintf("%2c", sizeof(rd)) + rd; +  res += e + sprintf("%2H", rd);    pos += sizeof(rd);    }    return res;    }       string low_low_mkquery(mapping q)    {    array qd = q->qd && (arrayp(q->qd)? q->qd : ({q->qd}));    array an = q->an && (arrayp(q->an)? q->an : ({q->an}));    array ns = q->ns && (arrayp(q->ns)? q->ns : ({q->ns}));
pike.git/lib/modules/Protocols.pmod/DNS.pmod:255:    return low_low_mkquery((["id":id, "rd":1,    "qd":(["name":dname, "cl":cl, "type":type])]));    }      //! create a DNS query PDU   //!   //! @param dnameorquery   //! @param cl   //! record class such as Protocols.DNS.C_IN   //! @param type - //! query type such Protocols.DNS.T_A +  //! query type such Protocols.DNS.T_A    //!    //! @returns    //! data suitable for use with    //! @[Protocols.DNS.client.do_sync_query]    //!    //! @example    //! // generate a query PDU for a address lookup on the hostname pike.lysator.liu.se    //! string q=Protocols.DNS.protocol()->mkquery("pike.lysator.liu.se", Protocols.DNS.C_IN, Protocols.DNS.T_A);    string mkquery(string|mapping dnameorquery, int|void cl, int|void type)    {    if(mappingp(dnameorquery))    return low_low_mkquery(dnameorquery);    else    return low_mkquery(random(65536),dnameorquery,cl,type);    }       string decode_domain(string msg, array(int) n)    { -  array(string) domains=({}); -  +     int pos=n[0];    int next=-1;    array(string) ret=({}); -  +  int labels = 0;    while(pos < sizeof(msg))    { -  +  labels++; +  if (labels > 255) +  error("Bad domain name. Too many labels.\n");    switch(int len=msg[pos])    {    case 0:    if(next==-1) next=pos+1;    n[0]=next;    return ret*".";       case 1..63:    pos+=len+1;    ret+=({msg[pos-len..pos-1]});
pike.git/lib/modules/Protocols.pmod/DNS.pmod:306:    if(next==-1) next=pos+2;    pos=((len&63)<<8) + msg[pos+1];    continue;    }    break;    }    }       string decode_string(string s, array(int) next)    { -  int len=s[next[0]]; +  int pos = next[0]; +  int len=s[pos];    next[0]+=len+1; -  return s[next[0]-len..next[0]-1]; +  return s[pos+1..pos+len];    }       int decode_byte(string s, array(int) next)    {    return s[next[0]++];    }       int decode_short(string s, array(int) next)    { -  sscanf(s[next[0]..next[0]+1],"%2c",int ret); -  next[0]+=2; -  return ret; +  return s[next[0]++]<<8 | s[next[0]++];    }       int decode_int(string s, array(int) next)    { -  sscanf(s[next[0]..next[0]+3],"%4c",int ret); -  next[0]+=4; -  return ret; +  int pos = next[0]; +  next[0] += 4; +  return s[pos++]<<24 | s[pos++]<<16 | s[pos++]<<8 | s[pos++];    }       //! Decode a set of entries from an answer.    //!    //! @param s    //! Encoded entries.    //!    //! @param num    //! Number of entires in @[s].    //!
pike.git/lib/modules/Protocols.pmod/DNS.pmod:385:    //! @mapping    //! @member int "preference"    //! @member string "mx"    //! @endmapping    //! @value T_HINFO    //! @mapping    //! @member string "cpu"    //! @member string "os"    //! @endmapping    //! @value T_SRV -  //! RFC 2052 and RFC 2782. +  //! @rfc{2052@} and @rfc{2782@}.    //! @mapping    //! @member int "priority"    //! @member int "weight"    //! @member int "port"    //! @member string "target"    //! @member string "service"    //! @member string "proto"    //! @member string "name"    //! @endmapping    //! @value T_A
pike.git/lib/modules/Protocols.pmod/DNS.pmod:429:    //! @member string "mname"    //! @member string "rname"    //! @member int "serial"    //! @member int "refresh"    //! @member int "retry"    //! @member int "expire"    //!    //! @member int "minimum"    //! Note: For historical reasons this entry is named    //! @expr{"minimum"@}, but it contains the TTL for -  //! negative answers (RFC 2308). +  //! negative answers (@rfc{2308@}).    //! @endmapping    //! @value T_NAPTR    //! @mapping    //! @member int "order"    //! @member int "preference"    //! @member string "flags"    //! @member string "service"    //! @member string "regexp"    //! @member string "replacement"    //! @endmapping    //! @value T_TXT    //! @mapping    //! @member string "txt" -  +  //! Note: For historical reasons, when receiving decoded +  //! DNS entries from a client, this will be the first string +  //! in the TXT record only. +  //! @member string "txta" +  //! When receiving decoded DNS data from a client, txta is +  //! the array of all strings in the record. When sending +  //! multiple strings in a TXT record in a server, please +  //! supply an array as "txt" containing the strings, txta +  //! will be ignored.    //! @endmapping    //! @value T_SPF    //! @mapping    //! @member string "spf"    //! @endmapping    //! @endint    //! @endarray    array decode_entries(string s,int num, array(int) next)    {    array(string) ret=({});
pike.git/lib/modules/Protocols.pmod/DNS.pmod:506:    {    if(x[1][0..0]=="_")    m->proto=x[1][1..];    else    m->proto=x[1];    }       m->name=x[2..]*".";    break;    case T_A: -  m->a=sprintf("%{.%d%}",values(s[next[0]..next[0]+m->len-1]))[1..]; +  m->a=(array(string))values(s[next[0]..next[0]+m->len-1])*".";    break;    case T_AAAA: -  m->aaaa=sprintf("%{:%02X%02X%}", -  values(s[next[0]..next[0]+m->len-1])/2)[1..]; +  m->aaaa=sprintf("%{%02X%}", +  (values(s[next[0]..next[0]+m->len-1])/2)[*])*":";    break;    case T_LOC:    m->version = decode_byte(s,next);    if (m->version == 0)    {    int aByte;    aByte = decode_byte(s,next);    m->size = pow((aByte>>4)&0xf , aByte&0xf)/100.0;    aByte = decode_byte(s,next);    m->h_perc = pow((aByte>>4)&0xf , aByte&0xf)/100.0;
pike.git/lib/modules/Protocols.pmod/DNS.pmod:546:    break;    case T_NAPTR:    m->order = decode_short (s, next);    m->preference = decode_short (s, next);    m->flags = decode_string (s, next);    m->service = decode_string (s, next);    m->regexp = decode_string (s, next);    m->replacement = decode_domain (s, next);    break;    case T_TXT: -  m->txt = decode_string(s, next); +  { +  int tlen; +  +  m->txta = ({ }); +  while (tlen < m->len) { +  m->txta += ({ decode_string(s, next) }); +  tlen += sizeof(m->txta[-1]) + 1; +  } +  m->txt = m->txta[0]; +  }    break;    case T_SPF:    m->spf = decode_string(s, next);    break;    }       next[0]=tmp+m->len;    ret+=({m});    }    return ret;    }       mapping decode_res(string s)    {    mapping m=([]); -  sscanf(s,"%2c%c%c%2c%2c%2c%2c", +  if (sscanf(s,"%2c%c%c%2c%2c%2c%2c",    m->id,    m->c1,    m->c2,    m->qdcount,    m->ancount,    m->nscount, -  m->arcount); +  m->arcount) != 7) +  error("Bad DNS request, failed to parse header\n");    m->rd=m->c1&1;    m->tc=(m->c1>>1)&1;    m->aa=(m->c1>>2)&1;    m->opcode=(m->c1>>3)&15;    m->qr=(m->c1>>7)&1;       m->rcode=m->c2&15;    m->cd=(m->c2>>4)&1;    m->ad=(m->c2>>5)&1;    m->ra=(m->c2>>7)&1;       m->length=sizeof(s);    -  array(string) tmp=({}); -  +     array(int) next=({12});    m->qd = allocate(m->qdcount);    for(int i=0; i<m->qdcount; i++) { -  +  if (next[0] > sizeof(s)) +  error("Bad DNS request, not enough data\n");    m->qd[i]=(["name":decode_domain(s,next)]);    sscanf(s[next[0]..next[0]+3],"%2c%2c",m->qd[i]->type, m->qd[i]->cl);    next[0]+=4;    }    m->an=decode_entries(s,m->ancount,next);    m->ns=decode_entries(s,m->nscount,next);    m->ar=decode_entries(s,m->arcount,next);    return m;    }   };    - //! Base class for implementing a Domain Name Service (DNS) server. - //! - //! This class is typically used by inheriting it, - //! and overloading @[reply_query()] and @[handle_response()]. - class server + protected string ANY; +  + protected void create()   { -  //! -  inherit protocol; +  // Check if IPv6 support is available, and that mapped IPv4 is enabled. +  catch { +  // Note: Attempt to bind on the IPv6 loopback (::1) +  // rather than on IPv6 any (::), to make sure some +  // IPv6 support is actually configured. This is needed +  // since eg Solaris happily binds on :: even +  // if no IPv6 interfaces are configured. +  // Try IPv6 any (::) too for paranoia reasons. +  // For even more paranoia, try sending some data +  // from :: to the drain services on 127.0.0.1 and ::1. +  // +  // If the tests fail, we regard the IPv6 support as broken, +  // and use just IPv4. +  Stdio.UDP udp = Stdio.UDP(); +  if (udp->bind(0, "::1") && udp->bind(0, "::") && +  (udp->send("127.0.0.1", 9, "/dev/null") == 9) && +  (udp->send("::1", 9, "/dev/null") == 9)) { +  // Note: The above tests are apparently not sufficient, since +  // WIN32 happily pretends to send stuff to ::ffff:0:0/96... +  Stdio.UDP udp2 = Stdio.UDP(); +  if (udp2->bind(0, "127.0.0.1")) { +  // IPv4 is present. +  array(string) a = udp2->query_address()/" "; +  int port = (int)a[1]; +  string key = Crypto.Random.random_string(16); +  udp2->set_nonblocking();    -  //inherit Stdio.UDP : udp; +  // We shouldn't get any lost packets, since we're on the loop-back, +  // but for paranoia reasons we perform a couple of retries. +  retry: +  for (int i = 0; i < 16; i++) { +  if (!udp->send("127.0.0.1", port, key)) continue; +  // udp2->wait() throws errors on WIN32. +  catch(udp2->wait(1)); +  mapping res; +  while ((res = udp2->read())) { +  if (res->data == key) { +  // Mapped IPv4 seems to work. +  ANY = "::"; +  break retry; +  } +  } +  } +  destruct(udp2); +  } +  } +  destruct(udp); +  }; + }    -  array(Stdio.UDP) ports = ({}); + //! Base class for @[server], @[tcp_server]. + class server_base + { +  inherit protocol;    -  protected void send_reply(mapping r, mapping q, mapping m, Stdio.UDP udp) +  array(Stdio.UDP|object) ports = ({ }); +  +  protected string low_send_reply(mapping r, mapping q, mapping m)    { -  // FIXME: Needs to handle truncation somehow. +     if(!r)    r = (["rcode":4]);    r->id = q->id;    r->qr = 1;    r->opcode = q->opcode;    r->rd = q->rd;    r->qd = r->qd || q->qd;    string s = mkquery(r); -  udp->send(m->ip, m->port, s); +  return s;    }       //! Reply to a query (stub).    //!    //! @param query    //! Parsed query.    //!    //! @param udp_data -  //! Raw UDP data. +  //! Raw UDP data. If the server operates in TCP mode (@[tcp_server]), +  //! it will contain an additional tcp_con entry. In that case, +  //! @expr{udp_data->tcp_con->con@} will contain the TCP connection the +  //! request was received on as @[Stdio.File] object.    //!    //! @param cb    //! Callback you can call with the result instead of returning it.    //! In that case, return @expr{0@} (zero).    //!    //!    //! Overload this function to implement the proper lookup.    //!    //! @note    //! To indicate the default failure @[cb] must be called with an    //! argument of @expr{0@} (zero), and @expr{0@} (zero) be returned.    //!    //! @returns    //! Returns @expr{0@} (zero) when the @[cb] callback will be used,    //! or a result mapping if not:    //! @mapping    //! @member int "rcode" -  +  //! 0 (or omit) for success, otherwise one of the Protocols.DNS.* constants    //! @member array(mapping(string:string|int))|void "an" -  +  //! Answer section:    //! @array    //! @elem mapping(string:string|int) entry    //! @mapping    //! @member string|array(string) "name"    //! @member int "type"    //! @member int "cl"    //! @endmapping    //! @endarray    //! @member array|void "qd" -  +  //! Question section, same format as @[an]; omit to return the original question    //! @member array|void "ns" -  +  //! Authority section (usually NS records), same format as @[an]    //! @member array|void "ar" -  +  //! Additional section, same format as @[an] +  //! @member int "aa" +  //! Set to 1 to include the Authoritative Answer bit in the response +  //! @member int "tc" +  //! Set to 1 to include the TrunCated bit in the response +  //! @member int "rd" +  //! Set to 1 to include the Recursion Desired bit in the response +  //! @member int "ra" +  //! Set to 1 to include the Recursion Available bit in the response +  //! @member int "cd" +  //! Set to 1 to include the Checking Disabled bit in the response +  //! @member int "ad" +  //! Set to 1 to include the Authenticated Data bit in the response    //! @endmapping    protected mapping reply_query(mapping query, mapping udp_data,    function(mapping:void) cb)    {    // Override this function.    //    // Return mapping may contain:    // aa, ra, {ad, cd,} rcode, an, ns, ar       return 0;    }       //! Handle a query.    //!    //! This function calls @[reply_query()],    //! and dispatches the result to @[send_reply()]. -  protected void handle_query(mapping q, mapping m, Stdio.UDP udp) +  protected void handle_query(mapping q, mapping m, Stdio.UDP|object udp)    {    int(0..1) result_available = 0;    void _cb(mapping r) {    if(result_available)    error("DNS: you can either use the callback OR return the reply, not both.\n");    result_available = 1;    send_reply(r, q, m, udp);    };    mapping r = reply_query(q, m, _cb);    if(r && result_available)    error("DNS: you can either use the callback OR return the reply, not both.\n");    if(r) {    result_available = 1;    send_reply(r, q, m, udp);    }    }       //! Handle a query response (stub).    //!    //! Overload this function to handle responses to possible recursive queries. -  protected void handle_response(mapping r, mapping m, Stdio.UDP udp) +  protected void handle_response(mapping r, mapping m, Stdio.UDP|object udp)    {    // This is a stub intended to simplify servers which allow recursion    }       //! Low-level DNS-data receiver.    //!    //! This function receives the raw DNS-data from the @[Stdio.UDP] socket -  //! @[udp], decodes it, and dispatches the decoded DNS request to -  //! @[handle_query()] and @[handle_response()]. -  protected private void rec_data(mapping m, Stdio.UDP udp) +  //! or TCP connection object @[udp], decodes it, and dispatches the decoded +  //! DNS request to @[handle_query()] and @[handle_response()]. +  protected void rec_data(mapping m, Stdio.UDP|object udp)    {    mixed err;    mapping q;    if (err = catch {    q=decode_res(m->data);    }) { -  werror("DNS: Failed to read UDP packet.\n%s\n", +  werror("DNS: Failed to read %s packet.\n%s\n", +  udp->tcp_connection ? "TCP" : "UDP",    describe_backtrace(err));    if(m && m->data && sizeof(m->data)>=2)    send_reply((["rcode":1]),    mkmapping(({"id"}), array_sscanf(m->data, "%2c")), m, udp);    }    else if(q->qr)    handle_response(q, m, udp);    else    handle_query(q, m, udp);    }    -  +  protected void send_reply(mapping r, mapping q, mapping m, +  Stdio.UDP|object con); +  +  protected void destroy() +  { +  if(sizeof(ports)) +  { +  foreach(ports;; object port) +  destruct(port); +  } +  } + } +  + //! Base class for implementing a Domain Name Service (DNS) server operating + //! over UDP. + //! + //! This class is typically used by inheriting it, + //! and overloading @[reply_query()] and @[handle_response()]. + //! + //! @seealso + //! @[dual_server] + class server + { +  //! +  inherit server_base; +  +  //inherit Stdio.UDP : udp; +  +  protected void send_reply(mapping r, mapping q, mapping m, Stdio.UDP udp) { +  udp->send(m->ip, m->port, low_send_reply(r, q, m)); +  } +     //! @decl void create()    //! @decl void create(int port)    //! @decl void create(string ip)    //! @decl void create(string ip, int port)    //! @decl void create(string ip, int port, string|int ... more)    //!    //! Open one or more new DNS server ports.    //!    //! @param ip -  //! The IP to bind to. Defaults to @expr{0@} (ie ANY). +  //! The IP to bind to. Defaults to @expr{"::"@} or @expr{0@} (ie ANY) +  //! depending on whether IPv6 support is present or not.    //!    //! @param port    //! The port number to bind to. Defaults to @expr{53@}.    //!    //! @param more    //! Optional further DNS server ports to open.    //! Must be a set of @[ip], @[port] argument pairs.    protected void create(int|string|void arg1, string|int ... args)    {    if(!arg1 && !sizeof(args))    arg1 = 53;    if(!sizeof(args))    {    if(stringp(arg1))    args = ({ arg1, 53 });    else -  args = ({ 0, arg1 }); +  args = ({ ANY, arg1 });    }    else    args = ({ arg1 }) + args;    if(sizeof(args)&1)    error("DNS: if you specify more than one argument, the number of "    "arguments needs to be even (server(ip1, port1, ip2, port2, "    "...)).\n");    for(int i;i<sizeof(args);i+=2) {    Stdio.UDP udp = Stdio.UDP();    if(args[i]) { -  if (!udp->bind(args[i+1],args[i])) +  if (!safe_bind(udp, args[i+1], args[i]))    error("DNS: failed to bind host:port %s:%d.\n", args[i],args[i+1]);    } else { -  if(!udp->bind(args[i+1])) +  if(!safe_bind(udp, args[i+1]))    error("DNS: failed to bind port %d.\n", args[i+1]);    }    udp->set_read_callback(rec_data, udp);    // port objects are stored for destruction when the server object is destroyed.    ports += ({udp});    }       } -  + }    -  protected void destory() +  + //! Base class for implementing a Domain Name Service (DNS) server operating + //! over TCP. + //! + //! This class is typically used by inheriting it, + //! and overloading @[reply_query()] and @[handle_response()]. + class tcp_server   { -  if(sizeof(ports)) +  inherit server_base; +  +  mapping(Connection:int(1..1)) connections = ([ ]); +  +  protected class Connection { +  constant tcp_connection = 1; +  +  protected int(0..1) write_ready; +  protected string read_buffer = "", out_buffer = ""; +  protected array c_id; +  Stdio.File con; +  +  protected void create(Stdio.File con) { +  this::con = con; +  con->set_nonblocking(rcb, wcb, ccb); +  c_id = call_out(destruct, 120, this); +  } +  +  protected void ccb(mixed id) { +  destruct(con); +  m_delete(connections, this); +  } +  +  protected void wcb(mixed id) { +  if (sizeof(out_buffer)) { +  int written = con->write(out_buffer); +  out_buffer = out_buffer[written..]; +  } else +  write_ready = 1; +  } +  +  protected void rcb(mixed id, string data) { +  int len; +  +  read_buffer += data; +  if (sscanf(read_buffer, "%2c", len)) { +  if (sizeof(read_buffer) > len - 2) { +  string data = read_buffer[2..len+1]; +  string ip, port; +  mapping m; +  +  read_buffer = read_buffer[len+2..]; +  +  remove_call_out(c_id); +  c_id = call_out(destruct, 120, this); +  +  [ip, port] = con->query_address() / " "; +  m = ([ "data" : data, +  "ip" : ip, +  "port" : (int)port, +  "tcp_con" : this ]); +  +  +  rec_data(m, this); +  } +  } +  } +  +  void send(string s) { +  if (sizeof(s) > 65535) +  error("DNS: Cannot send packets > 65535 bytes (%d here).\n", sizeof(s)); +  out_buffer += sprintf("%2H", s); +  +  if (write_ready) { +  int written = con->write(out_buffer); +  out_buffer = out_buffer[written..]; +  write_ready = 0; +  } +  +  remove_call_out(c_id); +  c_id = call_out(destruct, 120, this); +  } +  +  void destroy() { +  if (con) con->close(); +  destruct(con); +  m_delete(connections, this); +  } +  } +  +  protected int accept(Stdio.Port port) { +  connections[Connection(port->accept())] = 1; +  } +  +  protected void send_reply(mapping r, mapping q, mapping m, Connection con) { +  con->send(low_send_reply(r, q, m)); +  } +  +  //! @decl void create() +  //! @decl void create(int port) +  //! @decl void create(string ip) +  //! @decl void create(string ip, int port) +  //! @decl void create(string ip, int port, string|int ... more) +  //! +  //! Open one or more new DNS server ports. +  //! +  //! @param ip +  //! The IP to bind to. Defaults to @expr{"::"@} or @expr{0@} (ie ANY) +  //! depending on whether IPv6 support is present or not. +  //! +  //! @param port +  //! The port number to bind to. Defaults to @expr{53@}. +  //! +  //! @param more +  //! Optional further DNS server ports to open. +  //! Must be a set of @[ip], @[port] argument pairs. +  protected void create(int|string|void arg1, string|int ... args)    { -  foreach(ports;; Stdio.UDP port) -  destruct(port); +  if(!arg1 && !sizeof(args)) +  arg1 = 53; +  if(!sizeof(args)) +  { +  if(stringp(arg1)) +  args = ({ arg1, 53 }); +  else +  args = ({ ANY, arg1 });    } -  +  else +  args = ({ arg1 }) + args; +  if(sizeof(args)&1) +  error("DNS: if you specify more than one argument, the number of " +  "arguments needs to be even (server(ip1, port1, ip2, port2, " +  "...)).\n"); +  for(int i;i<sizeof(args);i+=2) { +  Stdio.Port port; +  +  if(args[i]) { +  port = Stdio.Port(args[i+1], accept, args[i]); +  } else { +  port = Stdio.Port(args[i+1], accept);    }    -  +  port->set_id(port); +  // Port objects are stored for destruction when the server +  // object is destroyed. +  ports += ({ port });    } -  +  }    -  +  protected void destroy() +  { +  foreach (connections; Connection con;) { +  destruct(con); +  }    -  +  ::destroy(); +  } + } +  + //! This is both a @[server] and @[tcp_server]. + class dual_server { +  inherit server : UDP; +  inherit tcp_server : TCP; +  +  protected void send_reply(mapping r, mapping q, mapping m, +  Connection|Stdio.UDP con) { +  string rpl = low_send_reply(r, q, m); +  +  if (!con->tcp_connection) { +  if (sizeof(rpl) > 512) { +  rpl = sprintf("%s%8c", rpl[..3], 0); // Truncate after header and +  // send empty response +  // ("dnscache strategy") +  rpl[2] |= 2; // Set TC bit +  } +  con->send(m->ip, m->port, rpl); +  } else +  con->send(rpl); +  } +  +  protected void create(int|string|void arg1, string|int ... args) +  { +  ::create(arg1, @args); +  } +  +  protected void destroy() +  { +  ::destroy(); +  } + } +  +    #define RETRIES 12   #define RETRY_DELAY 5    - //! Synchronous DNS client. + //! Synchronous DNS client.   class client   {    inherit protocol;      #ifdef __NT__    array(string) get_tcpip_param(string val, void|string fallbackvalue)    {    array(string) res = ({});    foreach(({    "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters", -  +  "SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters",    "SYSTEM\\CurrentControlSet\\Services\\VxD\\MSTCP"    }),string key)    {    catch {    res += ({ RegGetValue(HKEY_LOCAL_MACHINE, key, val) });    };    }      #if constant(RegGetKeyNames)    /* Catch if RegGetKeyNames() doesn't find the directory. */
pike.git/lib/modules/Protocols.pmod/DNS.pmod:825: Inside #if defined(__NT__) and #if constant(RegGetKeyNames)
   foreach(RegGetKeyNames(HKEY_LOCAL_MACHINE,    "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\"    "Parameters\\Interfaces"), string key)    {    catch {    res += ({ RegGetValue(HKEY_LOCAL_MACHINE,    "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\"    "Parameters\\Interfaces\\" + key, val) });    };    } +  foreach(RegGetKeyNames(HKEY_LOCAL_MACHINE, +  "SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\" +  "Parameters\\Interfaces"), string key) +  { +  catch { +  res += ({ RegGetValue(HKEY_LOCAL_MACHINE, +  "SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\" +  "Parameters\\Interfaces\\" + key, val) });    }; -  +  } +  };   #endif    res -= ({ UNDEFINED });    return sizeof(res) ? res : ({ fallbackvalue });    }      #else /* !__NT__ */       protected private mapping(string:string) etc_hosts;       protected private int is_ip(string ip)
pike.git/lib/modules/Protocols.pmod/DNS.pmod:1014:       if(domain)    domains = ({ domain }) + domains;   #endif    nameservers -= ({ "" });    if (!sizeof(nameservers)) {    /* Try localhost... */    nameservers = ({ "127.0.0.1" });    }    domains -= ({ "" }); -  domains = Array.map(domains, lambda(string d) { +  domains = map(domains, lambda(string d) {    if (d[-1] == '.') {    return d[..<1];    }    return d;    });    }    else    {    if(arrayp(server))    nameservers = server;
pike.git/lib/modules/Protocols.pmod/DNS.pmod:1036:    nameservers = ({ server });       if(arrayp(domain))    domains = domain;    else    if(stringp(domain))    domains = ({ domain });    }    }    - //! Perform a syncronous DNS query. +  //! Perform a synchronous DNS query.    //!    //! @param s    //! Result of @[Protocols.DNS.protocol.mkquery]    //! @returns - //! mapping containing query result or 0 on failure/timeout +  //! mapping containing query result or 0 on failure/timeout    //!    //! @example    //! @code    //! // Perform a hostname lookup, results stored in r->an    //! object d=Protocols.DNS.client();    //! mapping r=d->do_sync_query(d->mkquery("pike.lysator.liu.se", C_IN, T_A));    //! @endcode    mapping do_sync_query(string s)    {    int i;    object udp = Stdio.UDP();    // Attempt to randomize the source port.    for (i = 0; i < RETRIES; i++) { -  if (!catch { udp->bind(1024 + random(65536-1024)); }) continue; +  if (!safe_bind(udp, 1024 + random(65536-1024), ANY)) continue;    } -  if (i >= RETRIES) udp->bind(0); +  if (i >= RETRIES) safe_bind(udp, 0, ANY) || udp->bind(0);   #if 0    werror("Protocols.DNS.client()->do_sync_query(%O)\n"    "UDP Address: %s\n"    "%s\n", s, udp->query_address(), describe_backtrace(backtrace()));   #endif /* 0 */    mapping m;    for (i=0; i < RETRIES; i++) {    udp->send(nameservers[i % sizeof(nameservers)], 53, s);       // upd->wait() can throw an error sometimes. -  catch -  { -  while (udp->wait(RETRY_DELAY)) -  { +  // +  // One common case is on WIN32 where select complains +  // about it not being a socket. +  catch { +  udp->wait(RETRY_DELAY); +  }; +  // Make sure that udp->read() doesn't block. +  udp->set_nonblocking();    // udp->read() can throw an error on connection refused.    catch {    m = udp->read(); -  if ((m->port == 53) && +  if (m && (m->port == 53) &&    (m->data[0..1] == s[0..1]) &&    has_value(nameservers, m->ip)) {    // Success.    return decode_res(m->data);    }    }; -  +  // Restore blocking state for udp->send() on retry. +  udp->set_blocking();    } -  }; -  } +     // Failure.    return 0;    }       protected mapping low_gethostbyname(string s, int type)    {    mapping m;    if(sizeof(domains) && s[-1] != '.' && sizeof(s/".") < 3) {    mapping m = do_sync_query(mkquery(s, C_IN, type));    if(!m || !m->an || !sizeof(m->an))
pike.git/lib/modules/Protocols.pmod/DNS.pmod:1130:    //! @elem array(string) aliases    //! DNS name(s).    //! @endarray    //!    //! @note    //! Prior to Pike 7.7 this function only returned IPv4 addresses.    //!    array gethostbyname(string s)    {    mapping a_records = low_gethostbyname(s, T_A); -  mapping a6_records = low_gethostbyname(s, T_A6); +     mapping aaaa_records = low_gethostbyname(s, T_AAAA);      #if 0    werror("a_records: %O\n" -  "a6_records: %O\n" +     "aaaa_records: %O\n", -  a_records, a6_records, aaaa_records); +  a_records, aaaa_records);   #endif /* 0 */       array(string) names=({});    array(string) ips=({});    if (a_records) {    foreach(a_records->an, mapping x)    {    if(x->name)    names+=({x->name});    if(x->a)    ips+=({x->a});    }    } -  // Prefer a6 to aaaa. -  if (a6_records) { -  foreach(a6_records->an, mapping x) -  { -  if(x->name) -  names+=({x->name}); -  if(x->a6) -  ips+=({x->a6}); -  } -  } +     if (aaaa_records) {    foreach(aaaa_records->an, mapping x)    {    if(x->name)    names+=({x->name});    if(x->aaaa)    ips+=({x->aaaa});    }    }       return ({    sizeof(names)?names[0]:0,    ips,    names,    });    }    -  //! Queries the service record (RFC 2782) from the default or given -  //! DNS server. The result is an array of arrays with the -  //! following six elements for each record. The array is -  //! sorted according to the priority of each record. +  //! Queries the service record (@rfc{2782@}) from the default or +  //! given DNS server. The result is an array of arrays with the +  //! following six elements for each record. The array is sorted +  //! according to the priority of each record.    //!    //! Each element of the array returned represents a service    //! record. Each service record contains the following:    //!    //! @returns    //! An array with the requested information about the specified    //! service.    //!    //! @array    //! @elem int priority
pike.git/lib/modules/Protocols.pmod/DNS.pmod:1231:    } else {    m = do_sync_query(    mkquery("_" + service +"._"+ protocol + "." + name, C_IN, T_SRV));    }       if (!m) { // no entries.    return ({});    }       array res=({}); -  +     foreach(m->an, mapping x)    {    res+=({({x->priority, x->weight, x->port, x->target})});    }       // now we sort the array by priority as a convenience    array y=({});    foreach(res, array t)    y+=({t[0]});    sort(y, res);
pike.git/lib/modules/Protocols.pmod/DNS.pmod:1370:    }    if (!m) {    return 0;    }    array a = filter(m->an, `[], "mx");    array(string) b = column( a, "mx");    sort( column( a, "preference"), b);       return b;    } - } +     - #define REMOVE_DELAY 120 - #define GIVE_UP_DELAY (RETRIES * RETRY_DELAY + REMOVE_DELAY)*2 -  - // FIXME: Randomized source port! +     //! - class async_client +  class Request(string domain, string req, +  function(string,mapping,mixed...:void) callback, +  array(mixed) args)    { -  inherit client; -  inherit Stdio.UDP : udp; -  async_client next_client; +  int retries; +  int timestamp = time();    -  class Request +  //! Cancel the current request. +  void cancel()    { -  string req; -  string domain; -  function callback; -  int retries; -  int timestamp; -  array args; +  remove(this); +  } +  mixed retry_co;    };       mapping requests=([]);    -  protected private void remove(object(Request) r) +  protected void remove(object(Request) r)    {    if(!r) return;    sscanf(r->req,"%2c",int id);    m_delete(requests,id); -  r->callback(r->domain,0,@r->args); +  if (r->retry_co) remove_call_out(r->retry_co); +  r->retry_co = UNDEFINED; +  r->callback && r->callback(r->domain,0,@r->args);    destruct(r);    } -  + }    -  + #define REMOVE_DELAY 120 + #define GIVE_UP_DELAY (RETRIES * RETRY_DELAY + REMOVE_DELAY)*2 +  + // FIXME: Randomized source port! + //! Asynchronous DNS client. + class async_client + { +  inherit client; +  inherit Stdio.UDP : udp; +  async_client next_client; +     void retry(object(Request) r, void|int nsno)    {    if(!r) return;    if (nsno >= sizeof(nameservers)) {    if(r->retries++ > RETRIES)    { -  call_out(remove,REMOVE_DELAY,r); +  r->retry_co = call_out(remove, REMOVE_DELAY, r);    return;    } else {    nsno = 0;    }    }    -  send(nameservers[nsno],53,r->req); -  call_out(retry,RETRY_DELAY,r,nsno+1); +  r->retry_co = call_out(retry, RETRY_DELAY, r, nsno+1); +  udp::send(nameservers[nsno], 53, r->req);    }    -  void do_query(string domain, int cl, int type, +  //! Enqueue a new raw DNS request. +  //! +  //! @returns +  //! Returns a @[Request] object. +  //! +  //! @note +  //! Pike versions prior to 8.0 did not return the @[Request] object. +  Request do_query(string domain, int cl, int type,    function(string,mapping,mixed...:void) callback,    mixed ... args)    {    for(int e=next_client ? 100 : 256;e>=0;e--)    {    int lid = random(65536);    if(!catch { requests[lid]++; })    {    string req=low_mkquery(lid,domain,cl,type);    -  object r=Request(); -  r->req=req; -  r->domain=domain; -  r->callback=callback; -  r->args=args; -  r->timestamp=time(); -  requests[lid]=r; -  udp::send(nameservers[0],53,r->req); -  call_out(retry,RETRY_DELAY,r,1); -  return; +  object r = Request(domain, req, callback, args); +  r->retry_co = call_out(retry, RETRY_DELAY, r, 1); +  requests[lid] = r; +  udp::send(nameservers[0], 53, req); +  return r;    }    }       /* We failed miserably to find a request id to use,    * so we create a second UDP port to be able to have more    * requests 'in the air'. /Hubbe    */    if(!next_client) -  next_client=async_client(nameservers,domains); +  next_client=this_program(nameservers,domains);    -  next_client->do_query(domain, cl, type, callback, @args); +  return next_client->do_query(domain, cl, type, callback, @args);    }       protected private void rec_data(mapping m)    {    mixed err;    if (err = catch {    if(m->port != 53 || !has_value(nameservers, m->ip)) return;    sscanf(m->data,"%2c",int id);    object r=requests[id];    if(!r) {
pike.git/lib/modules/Protocols.pmod/DNS.pmod:1476:    }    m_delete(requests,id);    r->callback(r->domain,decode_res(m->data),@r->args);    destruct(r);    }) {    werror("DNS: Failed to read UDP packet. Connection refused?\n%s\n",    describe_backtrace(err));    }    }    -  protected private void generic_get(string d, +  protected private Request generic_get(string d,    mapping answer,    int multi,    int all,    int type,    string field,    string domain,    function callback,    mixed ... args)    {    if(!answer || !answer->an || !sizeof(answer->an))    {    if(multi == -1 || multi >= sizeof(domains)) {    // Either a request without multi (ip, or FQDN) or we have tried all    // domains.    callback(domain,0,@args);    } else {    // Multiple domain request. Try the next one... -  do_query(domain+"."+domains[multi], C_IN, type, +  return do_query(domain+"."+domains[multi], C_IN, type,    generic_get, ++multi, all, type, field, domain,    callback, @args);    }    } else {    if (all) {    callback(domain, answer->an, @args);    } else {    foreach(answer->an, array an)    if(an[field])    {    callback(domain, an[field], @args); -  return; +  return UNDEFINED;    }    callback(domain,0,@args); -  return; +     }    } -  +  return UNDEFINED;    }    -  void host_to_ip(string host, function callback, mixed ... args) +  //! +  Request host_to_ip(string host, function callback, mixed ... args)    {    if(sizeof(domains) && host[-1] != '.' && sizeof(host/".") < 3) { -  do_query(host, C_IN, T_A, +  return do_query(host, C_IN, T_A,    generic_get, 0, 0, T_A, "a", host, callback, @args );    } else { -  do_query(host, C_IN, T_A, +  return do_query(host, C_IN, T_A,    generic_get, -1, 0, T_A, "a",    host, callback, @args);    }    }    -  void ip_to_host(string ip, function callback, mixed ... args) +  //! +  Request ip_to_host(string ip, function callback, mixed ... args)    { -  do_query(arpa_from_ip(ip), C_IN, T_PTR, +  return do_query(arpa_from_ip(ip), C_IN, T_PTR,    generic_get, -1, 0, T_PTR, "ptr",    ip, callback,    @args);    }    -  void get_mx_all(string host, function callback, mixed ... args) +  //! +  Request get_mx_all(string host, function callback, mixed ... args)    {    if(sizeof(domains) && host[-1] != '.' && sizeof(host/".") < 3) { -  do_query(host, C_IN, T_MX, +  return do_query(host, C_IN, T_MX,    generic_get, 0, 1, T_MX, "mx", host, callback, @args);    } else { -  do_query(host, C_IN, T_MX, +  return do_query(host, C_IN, T_MX,    generic_get, -1, 1, T_MX, "mx", host, callback, @args);    }    }    -  void get_mx(string host, function callback, mixed ... args) +  //! +  Request get_mx(string host, function callback, mixed ... args)    { -  get_mx_all(host, +  return get_mx_all(host,    lambda(string domain, array(mapping) mx,    function callback, mixed ... args) {    array a;    if (mx) {    a = column(mx, "mx");    sort(column(mx, "preference"), a);    }    callback(a, @args);    }, callback, @args);    }    -  +  //! Close the client. +  //! +  //! @note +  //! All active requests are aborted.    void close()    { -  +  foreach(requests; ; Request r) { +  remove(r); +  }    udp::close();    udp::set_read_callback(0);    }       //!    protected void create(void|string|array(string) server,    void|string|array(string) domain)    { -  if(!udp::bind(0)) +  int i; +  // Attempt to randomize the source port. +  for (i = 0; i < RETRIES; i++) { +  if (safe_bind(udp::this, 1024 + random(65536-1024), ANY)) break; +  } +  if((i >= RETRIES) && +  !safe_bind(udp::this, 0, ANY) && +  !safe_bind(udp::this, 0))    error( "DNS: failed to bind a port.\n" );   #if 0    werror("Protocols.DNS.async_client(%O, %O)\n"    "UDP Address: %s\n"    "%s\n", server, domain, udp::query_address(),    describe_backtrace(backtrace()));   #endif /* 0 */       udp::set_read_callback(rec_data);    ::create(server,domain);    }   };       -  + // FIXME: Reuse sockets where possible? + //! Synchronous DNS client using TCP + //! Can handle larger responses than @[client] can. + class tcp_client + { +  inherit client; +  +  //! Perform a synchronous DNS query. +  //! +  //! @param s +  //! Result of @[Protocols.DNS.protocol.mkquery] +  //! @returns +  //! mapping containing query result or 0 on failure/timeout +  //! +  //! @example +  //! @code +  //! // Perform a hostname lookup, results stored in r->an +  //! object d=Protocols.DNS.tcp_client(); +  //! mapping r=d->do_sync_query(d->mkquery("pike.lysator.liu.se", C_IN, T_A)); +  //! @endcode +  mapping do_sync_query(string s) +  { +  for (int i=0; i < RETRIES; i++) { +  object tcp = Stdio.File(); +  if (tcp->connect(nameservers[i % sizeof(nameservers)], 53)) +  { +  tcp->write("%2H",s); +  sscanf(tcp->read(2),"%2c",int len); +  return decode_res(tcp->read(len)); +  } +  } +  // Failure. +  return 0; +  } + } +  + // FIXME: Reuse sockets? Acknowledge RETRIES? + //! Asynchronous DNS client using TCP + class async_tcp_client + { +  inherit async_client; +  +  //! +  class Request +  { +  inherit ::this_program; +  +  protected Stdio.File sock; +  protected string writebuf="",readbuf=""; +  +  protected void create(string domain, string req, +  function(string,mapping,mixed...:void) callback, +  array(mixed) args) +  { +  ::create(domain, req, callback, args); +  sock=Stdio.File(); +  sock->async_connect(nameservers[0], 53, connectedcb); +  } +  +  protected void close() +  { +  sock && sock->close(); +  sock = UNDEFINED; +  } +  +  protected void connectedcb(int ok) +  { +  if (!ok) {callback(domain, 0, @args); return;} +  sock->set_nonblocking(readcb, writecb, closecb); +  writebuf=sprintf("%2H",req); +  writecb(); +  } +  +  protected void readcb(mixed id,string data) +  { +  readbuf+=data; +  if (sscanf(readbuf,"%2H",string ret)) +  { +  if (callback) callback(domain, decode_res(ret), @args); +  callback=0; +  close(); +  } +  } +  +  protected void writecb() +  { +  if (writebuf!="") writebuf=writebuf[sock->write(writebuf)..]; +  } +  +  protected void closecb() +  { +  cancel(); +  } +  +  void cancel() +  { +  close(); +  ::cancel(); +  } +  } +  +  //! +  Request do_query(string domain, int cl, int type, +  function(string,mapping,mixed...:void) callback, +  mixed ... args) +  { +  string req=low_mkquery(random(65536),domain,cl,type); +  return Request(domain, req, callback, args); +  } + } +  + //! Both a @[client] and a @[tcp_client]. + class dual_client + { +  inherit client : UDP; +  inherit tcp_client : TCP; +  +  //! +  mapping do_sync_query(string s) +  { +  mapping ret = UDP::do_sync_query(s); +  if (!ret->tc) return ret; +  return TCP::do_sync_query(s); +  } +  +  void create(mixed ... args) {::create(@args);} + } +  + //! Both an @[async_client] and an @[async_tcp_client]. + class async_dual_client + { +  inherit async_client : UDP; +  inherit async_tcp_client : TCP; +  +  void check_truncation(string domain, mapping result, int cl, int type, +  function(string,mapping,mixed...:void) callback, +  mixed ... args) +  { +  if (!result || !result->tc) callback(domain,result,@args); +  else TCP::do_query(domain,cl,type,callback,@args); +  } +  +  //! +  Request do_query(string domain, int cl, int type, +  function(string,mapping,mixed...:void) callback, +  mixed ... args) +  { +  return UDP::do_query(domain,cl,type,check_truncation, +  cl,type,callback,@args); +  } +  +  void create(mixed ... args) {::create(@args);} + } +  +    async_client global_async_client;      #define GAC(X) \ - void async_##X( string host, function callback, mixed ... args ) \ + async_client.Request async_##X( string host, function callback, mixed ... args ) \   { \    if( !global_async_client ) \    global_async_client = async_client(); \ -  global_async_client->X(host,callback,@args); \ +  return global_async_client->X(host,callback,@args); \   }      //! @ignore   GAC(ip_to_host);   //! @endignore   //! @decl void async_ip_to_host(string ip, function cb, mixed ... cba)      //! @ignore   GAC(host_to_ip);   //! @endignore