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

version» Context lines:

pike.git/lib/modules/Protocols.pmod/DNS.pmod:1:   // Not yet finished -- Fredrik Hubinette - // RFC 1035 +     - //! $Id: DNS.pmod,v 1.60 2001/09/17 21:36:45 grubba Exp $ +  //inherit Stdio.UDP : udp;    -  + //! Support for the Domain Name System protocol. + //! + //! RFC 1034, RFC 1035 and RFC 2308 +  + protected void send_reply(mapping r, mapping q, mapping m, Stdio.UDP udp); +    #pike __REAL_VERSION__    - constant NOERROR=0; - constant FORMERR=1; - constant SERVFAIL=2; - constant NXDOMAIN=3; - constant NOTIMPL=4; - constant NXRRSET=8; + // documentation taken from RFC 2136    - constant QUERY=0; + //! No error condition. + final constant NOERROR=0;    - constant C_IN=1; - constant C_ANY=255; + //! The name server was unable to interpret the request due to a + //! format error. + final constant FORMERR=1;    - constant T_A=1; - constant T_NS=2; - constant T_MD=3; - constant T_MF=4; - constant T_CNAME=5; - constant T_SOA=6; - constant T_MB=7; - constant T_MG=8; - constant T_MR=9; - constant T_NULL=10; - constant T_PTR=12; - constant T_HINFO=13; - constant T_MINFO=14; - constant T_MX=15; - constant T_TXT=16; - constant T_AAAA=28; + //! 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, +  +  //! Class CSNET (Obsolete) +  C_CS=2, +  +  //! Class CHAOS +  C_CH=3, +  +  //! Class Hesiod +  C_HS=4, +  +  //! Class ANY +  C_ANY=255, + }; +  + //! Entry types + enum EntryType + { +  //! Type - host address +  T_A=1, +  +  //! Type - authoritative name server +  T_NS=2, +  +  //! Type - mail destination (Obsolete - use MX) +  T_MD=3, +  +  //! 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 (Obsolete) +  T_MB=7, +  +  //! Type - mail group member (Obsolete) +  T_MG=8, +  +  //! Type - mail rename domain name (Obsolete) +  T_MR=9, +  +  //! Type - null RR (Obsolete RFC 1035) +  T_NULL=10, +  +  //! Type - well known service description (Obsolete RFC 1123, RFC 1127) +  T_WKS=11, +  +  //! Type - domain name pointer +  T_PTR=12, +  +  //! Type - host information +  T_HINFO=13, +  +  //! Type - mailbox or mail list information (Obsolete) +  T_MINFO=14, +  +  //! Type - mail exchange +  T_MX=15, +  +  //! Type - text strings +  T_TXT=16, +  +  //! 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, +  //! RFC 1706) +  T_NSAP=22, +  +  //! Type - OSI NSAP Pointer (RFC 1348, 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) +  T_LOC=29, +  +  //! Type - Next (RFC 2065, 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 - ATM End System Address (af-saa-0069.000) +  T_ATMA=34, +  +  //! Type - NAPTR (RFC 3403) +  T_NAPTR=35, +  +  //! Type - Key eXchanger record (RFC 2230) +  T_KX=36, +  +  //! Type - Certificate Record (RFC 4398) +  T_CERT=37, +  +  //! Type - IPv6 address record (RFC 2874 Obsolete RFC 6563) +  T_A6=38, +  +  //! 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, + }; +  + 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 +  werror("Protocols.DNS: Binding of UDP port failed with errno %d: %s\n", +  errno(), strerror(errno())); +  master()->handle_error(err); +  return 0; + } +  + //! Low level DNS protocol   class protocol   {    string mklabel(string s)    { -  if(strlen(s)>63) -  throw(({"Too long component in domain name\n",backtrace()})); -  return sprintf("%c%s",strlen(s),s); +  if(sizeof(s)>63) +  error("Too long component in domain name.\n"); +  return sprintf("%1H",s);    }    -  static private string mkname(string|array(string) labels, int pos, +  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*".";    if(comp[n])    return sprintf("%2c", comp[n]|0xc000);    else {    if(pos<0x4000)    comp[n]=pos;    string l = mklabel(labels[0]); -  return l + mkname(labels[1..], pos+strlen(l), comp); +  return l + mkname(labels[1..], pos+sizeof(l), comp);    }    }    -  static private string mkrdata(mapping entry, int pos, mapping(string:int) c) +  protected string make_raw_addr6(string addr6)    { -  +  if(!addr6) return "\0"*16; +  return sprintf ("%@2c", Protocols.IPv6.parse_addr (addr6)); +  } +  +  protected private string mkrdata(mapping entry, int pos, mapping(string:int) c) +  {    switch(entry->type) {    case T_CNAME:    return mkname(entry->cname, pos, c);    case T_PTR:    return mkname(entry->ptr, pos, c);    case T_NS:    return mkname(entry->ns, pos, c);    case T_MD:    return mkname(entry->md, pos, c);    case T_MF:    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", strlen(entry->cpu||""), entry->cpu||"", -  strlen(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+strlen(rmailbx), 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: -  case T_AAAA: +     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+strlen(mname), 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("%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", strlen(t), t); +  return sprintf("%1H", t);    })*""; -  +  case T_SPF: +  return map(stringp(entry->spf)? ({entry->spf}):(entry->spf||({})), +  lambda(string t) { +  return sprintf("%1H", t); +  })*""; +  case T_LOC: +  // FIXME: Not implemented yet.    default:    return "";    }    }    -  static private string encode_entries(array(mapping) entries, int pos, +  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 += strlen(e)+2; +  pos += sizeof(e)+2;    string rd = entry->rdata || mkrdata(entry, pos, comp); -  res += e + sprintf("%2c", strlen(rd)) + rd; -  pos += strlen(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}));    array ar = q->ar && (arrayp(q->ar)? q->ar : ({q->ar}));    string r = sprintf("%2c%c%c%2c%2c%2c%2c", q->id,    ((q->rd)&1)|(((q->tc)&1)<<1)|(((q->aa)&1)<<2)|    (((q->opcode)&15)<<3)|(((q->qr)&1)<<7),    ((q->rcode)&15)|(((q->cd)&1)<<4)|(((q->ad)&1)<<5)|    (((q->ra)&1)<<7),    qd && sizeof(qd), an && sizeof(an),    ns && sizeof(ns), ar && sizeof(ar));    mapping(string:int) c = ([]);    if(qd)    foreach(qd, mapping _qd) -  r += mkname(_qd->name, strlen(r), c) + +  r += mkname(_qd->name, sizeof(r), c) +    sprintf("%2c%2c", _qd->type, _qd->cl);    if(an) -  r+=encode_entries(an, strlen(r), c); +  r+=encode_entries(an, sizeof(r), c);    if(ns) -  r+=encode_entries(ns, strlen(r), c); +  r+=encode_entries(ns, sizeof(r), c);    if(ar) -  r+=encode_entries(ar, strlen(r), c); +  r+=encode_entries(ar, sizeof(r), c);    return r;    }       string low_mkquery(int id,    string dname,    int cl,    int type)    {    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 +  //! +  //! @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]});    continue;       default:    if((~len)&0xc0) -  throw(({"Invalid message compression mode\n",backtrace()})); +  error("Invalid message compression mode.\n");    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]+1],"%2c",int ret); -  next[0]+=2; -  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]. +  //! +  //! @param next +  //! Array with a single element containing the start position in @[s] +  //! on entry and the continuation position on return. +  //! +  //! @returns +  //! Returns an array of mappings describing the decoded entires: +  //! @array +  //! @elem mapping 0.. +  //! Mapping describing a single entry: +  //! @mapping +  //! @member string "name" +  //! Name the entry concerns. +  //! @member EntryType "type" +  //! Type of entry. +  //! @member ResourceClass "cl" +  //! Resource class. Typically @[C_IN]. +  //! @member int "ttl" +  //! Time to live for the entry in seconds. +  //! @member int "len" +  //! Length in bytes of the encoded data section. +  //! @endmapping +  //! Depending on the type of entry the mapping may contain +  //! different additional fields: +  //! @int +  //! @value T_CNAME +  //! @mapping +  //! @member string "cname" +  //! @endmapping +  //! @value T_PTR +  //! @mapping +  //! @member string "ptr" +  //! @endmapping +  //! @value T_NS +  //! @mapping +  //! @member string "ns" +  //! @endmapping +  //! @value T_MX +  //! @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. +  //! @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 +  //! @mapping +  //! @member string "a" +  //! IPv4-address in dotted-decimal format. +  //! @endmapping +  //! @value T_AAAA +  //! @mapping +  //! @member string "aaaa" +  //! IPv6-address in colon-separated hexadecimal format. +  //! @endmapping +  //! @value T_LOC +  //! @mapping +  //! @member int "version" +  //! Version, currently only version @expr{0@} (zero) is +  //! supported. +  //! @member int "size" +  //! @member int "h_perc" +  //! @member int "v_perc" +  //! @member int "lat" +  //! @member int "long" +  //! @member int "alt" +  //! @endmapping +  //! @value T_SOA +  //! @mapping +  //! @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). +  //! @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=({}); -  for(int e=0;e<num && next[0]<strlen(s);e++) +  for(int e=0;e<num && next[0]<sizeof(s);e++)    {    mapping m=([]);    m->name=decode_domain(s,next);    sscanf(s[next[0]..next[0]+10],    "%2c%2c%4c%2c",    m->type,m->cl,m->ttl,m->len);       next[0]+=10;    int tmp=next[0];    switch(m->type)
pike.git/lib/modules/Protocols.pmod/DNS.pmod:244:    m->ns=decode_domain(s,next);    break;    case T_MX:    m->preference=decode_short(s,next);    m->mx=decode_domain(s,next);    break;    case T_HINFO:    m->cpu=decode_string(s,next);    m->os=decode_string(s,next);    break; +  case T_SRV: +  // RFC 2052 and RFC 2782. +  m->priority=decode_short(s,next); +  m->weight=decode_short(s,next); +  m->port=decode_short(s,next); +  m->target=decode_domain(s,next); +  array x=m->name/"."; +  +  if(x[0][0..0]=="_") +  m->service=x[0][1..]; +  else +  m->service=x[0]; +  +  if(sizeof(x) > 1) +  { +  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=(array(string))values(s[next[0]..next[0]+m->len-1])*"."; +  break;    case T_AAAA: -  m->a=sprintf("%{.%d%}",values(s[next[0]..next[0]+m->len-1]))[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; +  aByte = decode_byte(s,next); +  m->v_perc = pow((aByte>>4)&0xf , aByte&0xf)/100.0; +  m->lat = ((decode_int(s,next)-(2<<30))/3600000.0); +  m->long = ((decode_int(s,next)-(2<<30))/3600000.0); +  m->alt = ((decode_int(s,next)/100.0)-100000.0); +  } +  break;    case T_SOA:    m->mname=decode_domain(s,next);    m->rname=decode_domain(s,next);    m->serial=decode_int(s,next);    m->refresh=decode_int(s,next);    m->retry=decode_int(s,next);    m->expire=decode_int(s,next);    m->minimum=decode_int(s,next);    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: +  { +  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=strlen(s); +  m->length=sizeof(s);    -  array(string) tmp=({}); -  int e; -  +     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;    }   };    - class server + protected string ANY; +  + protected void create()   { -  +  // 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(); +  +  // 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); +  }; + } +  + //! Base class for @[server], @[tcp_server]. + class server_base + {    inherit protocol; -  inherit Stdio.UDP : udp; +     -  static void send_reply(mapping r, mapping q, mapping m) +  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;    }    -  static mapping reply_query(mapping q, mapping m) +  //! Reply to a query (stub). +  //! +  //! @param query +  //! Parsed query. +  //! +  //! @param 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;    }    -  static void handle_query(mapping q, mapping m) +  //! 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|object udp)    { -  mapping r = reply_query(q, m); -  send_reply(r, q, m); +  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);    } -  +  }    -  static void handle_response(mapping r, mapping m) +  //! 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|object udp)    {    // This is a stub intended to simplify servers which allow recursion    }    -  static private void rec_data(mapping m) +  //! Low-level DNS-data receiver. +  //! +  //! This function receives the raw DNS-data from the @[Stdio.UDP] socket +  //! 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(sprintf("DNS: Failed to read UDP packet.\n" -  "%s\n", describe_backtrace(err))); +  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); +  mkmapping(({"id"}), array_sscanf(m->data, "%2c")), m, udp);    }    else if(q->qr) -  handle_response(q, m); +  handle_response(q, m, udp);    else -  handle_query(q, m); +  handle_query(q, m, udp);    }    -  void create(int|void port) +  protected void send_reply(mapping r, mapping q, mapping m, +  Stdio.UDP|object con); +  +  protected void destroy()    { -  if(!port) -  port = 53; -  if(!udp::bind(port)) -  throw(({"DNS: failed to bind port "+port+".\n",backtrace()})); -  udp::set_read_callback(rec_data); +  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{"::"@} 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 = ({ 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 (!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(!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}); +  }    - #define RETRIES 12 - #define RETRY_DELAY 5 +  } + }    - //! Synchronous DNS client. - class client +  + //! 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   { -  +  inherit server_base;    -  inherit protocol; +  mapping(Connection:int(1..1)) connections = ([ ]);    -  static private int is_ip(string ip) +  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_program::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)    { -  return(replace(ip, -  ({ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "." }), -  ({ "", "", "", "", "", "", "", "", "", "", "" })) == ""); +  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;    -  static private mapping etc_hosts; +  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. + 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:422: 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 });    } - #endif +     -  static private string match_etc_hosts(string host) + #else /* !__NT__ */ +  +  protected private mapping(string:string) etc_hosts; +  +  protected private int is_ip(string ip)    { -  if (!etc_hosts) { +  if( has_value( ip, ":") ) +  return (replace(ip, "0123456789abcdefABCDEF:"/1, allocate(23,"")) == ""); +  return (replace(ip, "0123456789."/1, allocate(11,"")) == ""); +  } +  +  protected private string read_etc_file(string fname) +  {    array(string) paths; -  +  string res;   #ifdef __NT__    paths = get_tcpip_param("DataBasePath");   #else    paths = ({ "/etc", "/amitcp/db" });   #endif -  +  foreach(paths, string path) { +  string raw = Stdio.read_file(path + "/" + fname); +  if (raw && sizeof(raw = replace(raw, "\r", "\n"))) { +  if (res) { +  if (sizeof(res) && (res[-1] != '\n')) { +  // Avoid confusion... +  res += "\n"; +  } +  res += raw; +  } else { +  res = raw; +  } +  } +  } +  return res; +  }    -  +  protected private string match_etc_hosts(string host) +  { +  if (!etc_hosts) {    etc_hosts = ([ "localhost":"127.0.0.1" ]);    -  foreach(paths, string path) { -  string raw = Stdio.read_file(path + "/hosts"); +  string raw = read_etc_file("hosts");       if (raw && sizeof(raw)) {    foreach(raw/"\n"-({""}), string line) {    // Handle comments, and split the line on white-space    line = lower_case(replace((line/"#")[0], "\t", " "));    array arr = (line/" ") - ({ "" });       if (sizeof(arr) > 1) {    if (is_ip(arr[0])) {    foreach(arr[1..], string name) {    etc_hosts[name] = arr[0];    }    } else {    // Bad /etc/hosts entry ignored.    }    }    }    } else { -  // Couldn't read /etc/hosts. +  // Couldn't read or no /etc/hosts.    }    } -  +  return etc_hosts[lower_case(host)];    } -  return(etc_hosts[lower_case(host)]); -  } + #endif /* !__NT__ */    -  +  // FIXME: Read hosts entry in /etc/nswitch.conf? +     //! @decl void create()    //! @decl void create(void|string|array server, void|int|array domain)       array(string) nameservers = ({});    array domains = ({}); -  void create(void|string|array(string) server, void|int|array(string) domain) +  protected void create(void|string|array(string) server, +  void|int|array(string) domain)    {    if(!server)    {   #if __NT__    domains = get_tcpip_param("Domain", "") +    get_tcpip_param("DhcpDomain", "") +    map(get_tcpip_param("SearchList", ""),    lambda(string s) {    return replace(s, " ", ",")/",";    }) * ({});
pike.git/lib/modules/Protocols.pmod/DNS.pmod:498:    }) * ({});   #else    string domain;    string resolv_conf;    foreach(({"/etc/resolv.conf", "/amitcp/db/resolv.conf"}),    string resolv_loc)    if ((resolv_conf = Stdio.read_file(resolv_loc)))    break;       if (!resolv_conf) { -  if (system->get_netinfo_property) { +  if (System->get_netinfo_property) {    // Mac OS X / Darwin (and possibly other systems) that use    // NetInfo may have these values in the database.    if (nameservers = -  system->get_netinfo_property(".", +  System->get_netinfo_property(".",    "/locations/resolver",    "nameserver")) {    nameservers = map(nameservers, `-, "\n"); -  +  } else { +  nameservers = ({});    }    -  if (domains = system->get_netinfo_property(".", +  if (domains = System->get_netinfo_property(".",    "/locations/resolver",    "domain")) {    domains = map(domains, `-, "\n"); -  +  } else { +  domains = ({});    }    } else {    /* FIXME: Is this a good idea?    * Why not just try the fallback?    * /grubba 1999-04-14    *    * Now uses 127.0.0.1 as fallback.    * /grubba 2000-10-17    */    resolv_conf = "nameserver 127.0.0.1";    }   #if 0 -  throw(({ "Protocols.DNS.client(): No /etc/resolv.conf!\n", -  backtrace() })); +  error( "Protocols.DNS.client(): No /etc/resolv.conf!\n" );   #endif /* 0 */    }       if (resolv_conf)    foreach(resolv_conf/"\n", string line)    {    string rest;    sscanf(line,"%s#",line);    sscanf(line,"%*[\r \t]%s",line);    line=reverse(line);
pike.git/lib/modules/Protocols.pmod/DNS.pmod:555:    case "search":    rest = replace(rest, "\t", " ");    domains += ((rest/" ") - ({""}));    break;       case "nameserver":    if (!is_ip(rest)) {    // Not an IP-number!    string host = rest;    if (!(rest = match_etc_hosts(host))) { -  werror(sprintf("Protocols.DNS.client(): " -  "Can't resolv nameserver \"%s\"\n", host)); +  werror("Protocols.DNS.client(): " +  "Can't resolv nameserver \"%s\"\n", host);    break;    }    }    if (sizeof(rest)) {    nameservers += ({ rest });    }    break;    }    }       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[..sizeof(d)-2]; +  return d[..<1];    }    return d;    });    }    else    {    if(arrayp(server))    nameservers = server;    else    nameservers = ({ server });       if(arrayp(domain))    domains = domain;    else    if(stringp(domain))    domains = ({ domain });    }    }    -  +  //! 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.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(); -  udp->bind(0); +  // Attempt to randomize the source port. +  for (i = 0; i < RETRIES; i++) { +  if (!safe_bind(udp, 1024 + random(65536-1024), ANY)) continue; +  } +  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; -  int i; +     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]) && -  (search(nameservers, m->ip) != -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)) +  foreach(domains, string domain) +  { +  m = do_sync_query(mkquery(s+"."+domain, C_IN, type)); +  if(m && m->an && sizeof(m->an)) +  break; +  } +  return m; +  } else { +  return do_sync_query(mkquery(s, C_IN, type)); +  } +  } +     //! @decl array gethostbyname(string hostname) -  //! @decl array gethostbyaddr(string hostip) -  //! Querys the host name or ip from the default or given +  //! Queries the host name from the default or given    //! DNS server. The result is an array with three elements, -  +  //! +  //! @returns +  //! An array with the requested information about the specified +  //! host. +  //!    //! @array    //! @elem string hostname    //! Hostname.    //! @elem array(string) ip    //! IP number(s).    //! @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 m; -  if(sizeof(domains) && s[-1] != '.' && sizeof(s/".") < 3) { -  m = do_sync_query(mkquery(s, C_IN, T_A)); -  if(!m || !m->an || !sizeof(m->an)) -  foreach(domains, string domain) -  { -  m = do_sync_query(mkquery(s+"."+domain, C_IN, T_A)); -  if(m && m->an && sizeof(m->an)) -  break; -  } -  } else { -  m = do_sync_query(mkquery(s, C_IN, T_A)); -  } +  mapping a_records = low_gethostbyname(s, T_A); +  mapping aaaa_records = low_gethostbyname(s, T_AAAA);    -  if (!m) { -  return ({ 0, ({}), ({}) }); -  } + #if 0 +  werror("a_records: %O\n" +  "aaaa_records: %O\n", +  a_records, aaaa_records); + #endif /* 0 */       array(string) names=({});    array(string) ips=({}); -  foreach(m->an, mapping x) +  if (a_records) { +  foreach(a_records->an, mapping x)    {    if(x->name)    names+=({x->name});    if(x->a)    ips+=({x->a});    } -  +  } +  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. +  //! +  //! 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 +  //! Priority +  //! @elem int weight +  //! Weight in event of multiple records with same priority. +  //! @elem int port +  //! port number +  //! @elem string target +  //! target dns name +  //! @endarray +  //! +  array getsrvbyname(string service, string protocol, string|void name) +  { +  mapping m; +  if(!service) error("no service name specified."); +  if(!protocol) error("no service name specified."); +  +  if(sizeof(domains) && !name) { +  // we haven't provided a target domain to search on. +  // we probably shouldn't do a searchdomains search, +  // as it might return ambiguous results. +  m = do_sync_query( +  mkquery("_" + service +"._"+ protocol + "." + name, C_IN, T_SRV)); +  if(!m || !m->an || !sizeof(m->an)) +  foreach(domains, string domain) +  { +  m = do_sync_query( +  mkquery("_" + service +"._"+ protocol + "." + domain, C_IN, T_SRV)); +  if(m && m->an && sizeof(m->an)) +  break; +  } +  } 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); +  +  return res; +  } +     string arpa_from_ip(string ip)    { -  +  if(has_value(ip,':')) { +  string raw_ipv6 = make_raw_addr6(ip); +  return reverse(sprintf("%@02X", values(raw_ipv6)))/1*"."+".IP6.ARPA"; +  } else    return reverse(ip/".")*"."+".IN-ADDR.ARPA";    }       string ip_from_arpa(string arpa)    { -  return reverse(arpa/".")[2..]*"."; +  array(string) parts = reverse(arpa/"."); +  if(sizeof(parts)<2) return ""; +  if(lower_case(parts[1]) == "ip6") +  return map(parts[2..]/4, `*, "")*":"; +  else +  return parts[2..]*".";    }    -  +  //! @decl array gethostbyaddr(string hostip) +  //! Queries the host name or ip from the default or given +  //! DNS server. The result is an array with three elements, +  //! +  //! @returns +  //! The requested data about the specified host. +  //! +  //! @array +  //! @elem string hostip +  //! The host IP. +  //! @elem array(string) ip +  //! IP number(s). +  //! @elem array(string) aliases +  //! DNS name(s). +  //! @endarray +  //!    array gethostbyaddr(string s)    {    mapping m=do_sync_query(mkquery(arpa_from_ip(s), C_IN, T_PTR));    if (m) {    array(string) names=({});    array(string) ips=({});       foreach(m->an, mapping x)    {    if(x->ptr)
pike.git/lib/modules/Protocols.pmod/DNS.pmod:712:    ips+=({ip_from_arpa(x->name)});    }    }    return ({    sizeof(names)?names[0]:0,    ips,    names,    });    } else {    // Lookup failed. -  return({ 0, ({}), ({}) }); +  return ({ 0, ({}), ({}) });    }    }       //! @decl string get_primary_mx(string hostname) -  //! Querys the primary mx for the host. +  //! Queries the primary mx for the host.    //! @returns    //! Returns the hostname of the primary mail exchanger.    //!    string get_primary_mx(string host)    {    mapping m;    if(sizeof(domains) && host[-1] != '.' && sizeof(host/".") < 3) {    m=do_sync_query(mkquery(host, C_IN, T_MX));    if(!m || !m->an || !sizeof(m->an))    foreach(domains, string domain)
pike.git/lib/modules/Protocols.pmod/DNS.pmod:743:    } else {    m=do_sync_query(mkquery(host, C_IN, T_MX));    }    if (!m) {    return 0;    }    int minpref=29372974;    string ret;    foreach(m->an, mapping m2)    { -  if(m2->preference<minpref) +  if(m2->mx && m2->preference<minpref)    {    ret=m2->mx;    minpref=m2->preference;    }    }    return ret;    }    -  +  //!    array(string) get_mx(string host)    {    mapping m;    if(sizeof(domains) && host[-1] != '.' && sizeof(host/".") < 3) {    m = do_sync_query(mkquery(host, C_IN, T_MX));    if(!m || !m->an || !sizeof(m->an)) {    foreach(domains, string domain)    {    m = do_sync_query(mkquery(host+"."+domain, C_IN, T_MX));    if(m && m->an && sizeof(m->an))    break;    }    }    } else {    m = do_sync_query(mkquery(host, C_IN, T_MX));    }    if (!m) {    return 0;    } -  array a = m->an; +  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 -  - 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_object()); +  }    };       mapping requests=([]);    -  static 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); +  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);    return;    } else {    nsno = 0;    }    }       send(nameservers[nsno],53,r->req);    call_out(retry,RETRY_DELAY,r,nsno+1);    }    -  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 ? 5 : 256;e>=0;e--) +  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(); +  object r = Request(domain, req, callback, args);    requests[lid]=r;    udp::send(nameservers[0],53,r->req);    call_out(retry,RETRY_DELAY,r,1); -  return; +  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);    }    -  static private void rec_data(mapping m) +  protected private void rec_data(mapping m)    {    mixed err;    if (err = catch { -  if(m->port != 53 || search(nameservers, m->ip) == -1) return; +  if(m->port != 53 || !has_value(nameservers, m->ip)) return;    sscanf(m->data,"%2c",int id);    object r=requests[id]; -  if(!r) return; +  if(!r) { +  // Invalid request id. Spoofed answer? +  // FIXME: Consider black- or greylisting the answer. +  return; +  }    m_delete(requests,id);    r->callback(r->domain,decode_res(m->data),@r->args);    destruct(r);    }) { -  werror(sprintf("DNS: Failed to read UDP packet. Connection refused?\n" -  "%s\n", -  describe_backtrace(err))); +  werror("DNS: Failed to read UDP packet. Connection refused?\n%s\n", +  describe_backtrace(err));    }    }    -  static 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)    { -  mapping m; +     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);    }    -  void create(void|string|array(string) server, void|string|array(string) domain) +  //! Close the client. +  //! +  //! @note +  //! All active requests are aborted. +  void close()    { -  if(!udp::bind(0)) -  throw(({"DNS: failed to bind a port.\n",backtrace()})); +  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) +  { +  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) \ + async_client.Request async_##X( string host, function callback, mixed ... args ) \ + { \ +  if( !global_async_client ) \ +  global_async_client = async_client(); \ +  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 + //! @decl void async_host_to_ip(string host, function cb, mixed ... cba) +  + //! @ignore + GAC(get_mx_all); + //! @endignore + //! @decl void async_get_mx_all(string host, function cb, mixed ... cba) +  + //! @ignore + GAC(get_mx); + //! @endignore + //! @decl void async_get_mx(string host, function cb, mixed ... cba) +  +  + client global_client; +  + #define GC(X) \ + mixed X( string host ) \ + { \ +  if( !global_client ) \ +  global_client = client(); \ +  return global_client->X(host); \ + } +  + //! @ignore + GC(gethostbyname); + //! @endignore + //! @decl array gethostbyname(string host) +  + //! @ignore + GC(gethostbyaddr); + //! @endignore + //! @decl array gethostbyaddr(string host) +  + //! @ignore + GC(get_mx); + //! @endignore + //! @decl string get_mx(string host) +  + //! @ignore + GC(get_primary_mx); + //! @endignore + //! @decl string get_primary_mx(string host)