pike.git
/
lib
/
modules
/
Protocols.pmod
/
DNS.pmod
version
»
Context lines:
10
20
40
80
file
none
3
pike.git/lib/modules/Protocols.pmod/DNS.pmod:1:
// Not yet finished -- Fredrik Hubinette
-
// RFC 1035
+
-
//!
module
Protocols
-
//
!
submodule
DNS
+
//
inherit
Stdio.UDP
: udp;
-
//!
$Id:
DNS.pmod,v
1.54
2000
/
12
/
08
12:15:17
grubba
Exp
$
+
//!
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 (EXPERIMENTAL)
+
T_MB=7,
+
+
//! Type - mail group member (EXPERIMENTAL)
+
T_MG=8,
+
+
//! Type - mail rename domain name (EXPERIMENTAL)
+
T_MR=9,
+
+
//! Type - null RR (EXPERIMENTAL)
+
T_NULL=10,
+
+
//! Type - well known service description
+
T_WKS=11,
+
+
//! Type - domain name pointer
+
T_PTR=12,
+
+
//! Type - host information
+
T_HINFO=13,
+
+
//! Type - mailbox or mail list information
+
T_MINFO=14,
+
+
//! Type - mail exchange
+
T_MX=15,
+
+
//! Type - text strings
+
T_TXT=16,
+
+
//! Type - IPv6 address record (RFC 1886)
+
T_AAAA=28,
+
+
//! Type - Location Record (RFC 1876)
+
T_LOC=29,
+
+
//! Type - Service location record (RFC 2782)
+
T_SRV=33,
+
+
//! Type - NAPTR (RFC 3403)
+
T_NAPTR=35,
+
+
//! Type - IPv6 address record (RFC 2874, incomplete support)
+
T_A6=38,
+
+
//! Type - SPF - Sender Policy Framework (RFC 4408)
+
T_SPF=99,
+
+
//! Type - ANY - A request for all records
+
T_ANY=255,
+
};
+
+
int safe_bind(Stdio.UDP udp, mixed ... args)
+
{
+
mixed err = catch {
+
udp->bind(@args);
+
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||({})), lambda(string t) {
-
return sprintf("%
1c%s
",
strlen(
t)
, t)
;
+
return sprintf("%
1H
", t);
})*"";
-
+
case T_SPF:
+
return Array.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:247:
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
+
}
+
}
-
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;
+
+
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_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)
//!
-
//!
class
client
-
//!
Synchronous
DNS
client
.
+
//!
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.Port port;
-
inherit
protocol
;
+
if(args[i]) {
+
port = Stdio.Port(args[i+1], accept, args[i])
;
+
} else {
+
port = Stdio.Port(args[i+1], accept);
+
}
-
static
private
int
is
_
ip
(
string
ip
)
+
port->set
_
id
(
port);
+
// port objects are stored for destruction when the server object is destroyed.
+
ports += ({ port }
)
;
+
}
+
}
+
+
protected void destroy()
{
-
return(replace(ip,
-
(
{
"0",
"1", "2", "3", "4", "5", "6", "7", "8", "9", "." }
)
,
-
(
{
"",
"",
"",
"",
"", "", "", "", "", "", "" }
)
) == "")
;
+
foreach
(
connections;
Connection
con;)
{
+
destruct
(
con
);
}
-
static
private
mapping etc_hosts
;
+
::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. */
+
catch {
foreach(RegGetKeyNames(HKEY_LOCAL_MACHINE,
-
"
SYSTEM\\CurrentControlSet\\Tcpip\\
"
+
"
SYSTEM\\CurrentControlSet\\Services\\Tcpip\\
"
"Parameters\\Interfaces"), string key) { catch { res += ({ RegGetValue(HKEY_LOCAL_MACHINE,
-
"
SYSTEM\\CurrentControlSet\\Tcpip\\
"
+
"
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__
*/
-
//
!
-
//!
method
void
create()
-
//
! method void create(void|string|array server, void|int|array domain)
-
//!
+
//
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:503:
}) * ({}); #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) {
+
// Mac OS X / Darwin (and possibly other systems) that use
+
// NetInfo may have these values in the database.
+
if (nameservers =
+
System->get_netinfo_property(".",
+
"/locations/resolver",
+
"nameserver")) {
+
nameservers = map(nameservers, `-, "\n");
+
} else {
+
nameservers = ({});
+
}
+
+
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); sscanf(line,"%*[\r \t]%s",line); line=reverse(line); sscanf(line,"%s%*[ \t]%s",line,rest); switch(line)
pike.git/lib/modules/Protocols.pmod/DNS.pmod:542:
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) { 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; }
-
//!
-
//! method array gethostbyname(string hostname)
-
//! method array gethostbyaddr(string hostip)
-
//! Querys the host name or ip from the default or given
-
//! DNS server. The result is a
mapping
with three elements,
-
//! <data
_
description type=array>
-
//! <elem value=hostname type=string>hostname</elem>
-
//! <elem value=ip type=array
(string
)>ip
number(
s
)</elem>
-
//! <elem value=ip
type
=array(string
)
>dns name(s)</elem>
-
//! </data_description>
-
//!
-
-
array gethostbyname(string s)
+
protected
mapping
low
_
gethostbyname
(string s
,
int
type)
{ mapping m; if(sizeof(domains) && s[-1] != '.' && sizeof(s/".") < 3) {
-
m = do_sync_query(mkquery(s, C_IN,
T_A
));
+
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,
T_A
));
+
m = do_sync_query(mkquery(s+"."+domain, C_IN,
type
));
if(m && m->an && sizeof(m->an)) break; }
-
+
return m;
} else {
-
m
=
do_sync_query(mkquery(s, C_IN,
T_A
));
+
return
do_sync_query(mkquery(s, C_IN,
type
));
}
-
-
if (!m) {
-
return ({ 0, ({}), ({}) });
+
}
-
+
//! @decl array gethostbyname(string hostname)
+
//! 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 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);
+
#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}); }
-
+
}
+
// 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.
+
//!
+
//! 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:696:
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)
+
//! Queries the primary mx for the host.
+
//! @returns
+
//! Returns the hostname of the primary mail exchanger.
//!
-
//! method string get_primary_mx(string hostname)
-
//! Querys the primary mx for the host.
-
//! 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) { m=do_sync_query(mkquery(host+"."+domain, C_IN, T_MX)); if(m && m->an && sizeof(m->an))
pike.git/lib/modules/Protocols.pmod/DNS.pmod:728:
} 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
-
+
// FIXME: Randomized source port!
+
//! Asynchronous DNS client.
class async_client { inherit client; inherit Stdio.UDP : udp; async_client next_client; class Request { string req; string domain; function callback; int retries; int timestamp; array args; }; mapping requests=([]);
-
static
private void remove(object(Request) r)
+
protected
private 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); destruct(r); } void retry(object(Request) r, void|int nsno) {
pike.git/lib/modules/Protocols.pmod/DNS.pmod:815:
} send(nameservers[nsno],53,r->req); call_out(retry,RETRY_DELAY,r,nsno+1); } void 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;
pike.git/lib/modules/Protocols.pmod/DNS.pmod:845:
/* 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->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 void 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))
pike.git/lib/modules/Protocols.pmod/DNS.pmod:901:
{ callback(domain, an[field], @args); return; } callback(domain,0,@args); return; } } }
+
//!
void host_to_ip(string host, function callback, mixed ... args) { if(sizeof(domains) && host[-1] != '.' && sizeof(host/".") < 3) { 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, generic_get, -1, 0, T_A, "a", host, callback, @args); } }
-
+
//!
void ip_to_host(string ip, function callback, mixed ... args) { 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) {
-
mapping m;
+
if(sizeof(domains) && host[-1] != '.' && sizeof(host/".") < 3) { 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, generic_get, -1, 1, T_MX, "mx", host, callback, @args); } }
-
+
//!
void get_mx(string host, function callback, mixed ... args) { 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); } };
-
+
+
+
async_client global_async_client;
+
+
#define GAC(X) \
+
void async_##X( string host, function callback, mixed ... args ) \
+
{ \
+
if( !global_async_client ) \
+
global_async_client = async_client(); \
+
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)