* fixed NT services not working. app must now call lcoreinit() at some point before...
[lcore.git] / dnscore.pas
index bb4fab49b6d000491fa1e6534607dc3f1b4d589c..4cb52e2461231bab9f3a34a547e0ee9290f0f44d 100755 (executable)
 }\r
 unit dnscore;\r
 \r
-\r
-\r
 {$ifdef fpc}{$mode delphi}{$endif}\r
 \r
-\r
-\r
-\r
+{$include lcoreconfig.inc}\r
 \r
 interface\r
 \r
-uses binipstuff,classes,pgtypes;\r
+uses binipstuff,classes,pgtypes,lcorernd;\r
 \r
 var usewindns : boolean = {$ifdef win32}true{$else}false{$endif};\r
-//hint to users of this unit that they should use windows dns instead.\r
-//May be disabled by applications if desired. (e.g. if setting a custom\r
-//dnsserverlist).\r
+{hint to users of this unit that they should use windows dns instead.\r
+May be disabled by applications if desired. (e.g. if setting a custom\r
+dnsserverlist).\r
+\r
+note: this unit will not be able to self populate it's dns server list on\r
+older versions of windows.}\r
+\r
+const\r
+  useaf_default=0;\r
+  useaf_preferv4=1;\r
+  useaf_preferv6=2;\r
+  useaf_v4=3;\r
+  useaf_v6=4;\r
+{\r
+hint to users of this unit to how to deal with connecting to hostnames regarding ipv4 or ipv6 usage\r
+can be set by apps as desired\r
+}\r
+var useaf:integer = useaf_default;\r
 \r
-//note: this unit will not be able to self populate it's dns server list on\r
-//older versions of windows.\r
+{\r
+(temporarily) use a different nameserver, regardless of the dnsserverlist\r
+}\r
+var overridednsserver:string;\r
 \r
 const\r
   maxnamelength=127;\r
@@ -85,12 +98,14 @@ const
   querytype_a=1;\r
   querytype_cname=5;\r
   querytype_aaaa=28;\r
+  querytype_a6=38;\r
   querytype_ptr=12;\r
   querytype_ns=2;\r
   querytype_soa=6;\r
   querytype_mx=15;\r
-\r
-  maxrecursion=10;\r
+  querytype_txt=16;\r
+  querytype_spf=99;\r
+  maxrecursion=50;\r
   maxrrofakind=20;\r
 \r
   retryafter=300000; //microseconds must be less than one second;\r
@@ -115,6 +130,7 @@ type
     parsepacket:boolean;\r
     resultstr:string;\r
     resultbin:tbinip;\r
+    resultlist:tbiniplist;\r
     resultaction:integer;\r
     numrr1:array[0..3] of integer;\r
     numrr2:integer;\r
@@ -147,29 +163,27 @@ type
 //if you must but please document them at the same time --plugwash\r
 \r
 //function buildrequest(const name:string;var packet:tdnspacket;requesttype:word):integer;\r
-//function makereversename(const binip:tbinip):string;\r
+\r
+//returns the DNS name used to reverse look up an IP, such as 4.3.2.1.in-addr.arpa for 1.2.3.4\r
+function makereversename(const binip:tbinip):string;\r
 \r
 procedure setstate_request_init(const name:string;var state:tdnsstate);\r
 \r
 //set up state for a foward lookup. A family value of AF_INET6 will give only\r
-//ipv6 results. Any other value will give ipv4 results in preference and ipv6\r
-//results if ipv4 results are not available;\r
+//ipv6 results. Any other value will give only ipv4 results\r
 procedure setstate_forward(const name:string;var state:tdnsstate;family:integer);\r
 \r
 procedure setstate_reverse(const binip:tbinip;var state:tdnsstate);\r
 procedure setstate_failure(var state:tdnsstate);\r
 //procedure setstate_return(const rrp:trrpointer;len:integer;var state:tdnsstate);\r
 \r
+//for custom raw lookups such as TXT, as desired by the user\r
+procedure setstate_custom(const name:string; requesttype:integer; var state:tdnsstate);\r
 \r
 procedure state_process(var state:tdnsstate);\r
 \r
 //function decodename(const packet:tdnspacket;len,start,recursion:integer;var numread:integer):string;\r
 \r
-//presumablly this is exported to allow more secure random functions\r
-//to be substituted?\r
-var randomfunction:function:integer;\r
-\r
-\r
 procedure populatednsserverlist;\r
 procedure cleardnsservercache;\r
 \r
@@ -177,17 +191,32 @@ var
   dnsserverlist : tstringlist;\r
 //  currentdnsserverno : integer;\r
 \r
+\r
+//getcurrentsystemnameserver returns the nameserver the app should use and sets\r
+//id to the id of that nameserver. id should later be used to report how laggy\r
+//the servers response was and if it was timed out.\r
 function getcurrentsystemnameserver(var id:integer) :string;\r
+procedure reportlag(id:integer;lag:integer); //lag should be in microseconds and should be -1 to report a timeout\r
 \r
 //var\r
 //  unixnameservercache:string;\r
 { $endif}\r
 \r
 \r
-procedure reportlag(id:integer;lag:integer); //lag should be in microseconds and should be -1 to report a timeout\r
+{$ifdef ipv6}\r
+function getv6localips:tbiniplist;\r
+procedure initpreferredmode;\r
+\r
+var\r
+  preferredmodeinited:boolean;\r
+\r
+{$endif}\r
+\r
 var\r
   failurereason:string;\r
 \r
+function getquerytype(s:string):integer;\r
+\r
 implementation\r
 \r
 uses\r
@@ -197,6 +226,24 @@ uses
 \r
   sysutils;\r
 \r
+\r
+\r
+function getquerytype(s:string):integer;\r
+begin\r
+  s := uppercase(s);\r
+  result := 0;\r
+  if (s = 'A') then result := querytype_a else\r
+  if (s = 'CNAME') then result := querytype_cname else\r
+  if (s = 'AAAA') then result := querytype_aaaa else\r
+  if (s = 'PTR') then result := querytype_ptr else\r
+  if (s = 'NS') then result := querytype_ns else\r
+  if (s = 'MX') then result := querytype_mx else\r
+  if (s = 'A6') then result := querytype_a6 else\r
+  if (s = 'TXT') then result := querytype_txt else\r
+  if (s = 'SOA') then result := querytype_soa else\r
+  if (s = 'SPF') then result := querytype_spf;\r
+end;\r
+\r
 function buildrequest(const name:string;var packet:tdnspacket;requesttype:word):integer;\r
 var\r
   a,b:integer;\r
@@ -206,7 +253,8 @@ begin
  { writeln('buildrequest: name: ',name);}\r
   result := 0;\r
   fillchar(packet,sizeof(packet),0);\r
-  if assigned(randomfunction) then packet.id := (randomfunction and $ffff) else packet.id := random(65536);\r
+  packet.id := randominteger($10000);\r
+\r
   packet.flags := htons($0100);\r
   packet.rrcount[0] := htons($0001);\r
 \r
@@ -337,25 +385,55 @@ end;
 \r
 {==============================================================================}\r
 \r
-procedure setstate_return(const rrp:trrpointer;len:integer;var state:tdnsstate);\r
-var\r
-  a:integer;\r
+function getrawfromrr(const rrp:trrpointer;len:integer):string;\r
 begin\r
-  state.resultaction := action_done;\r
-  state.resultstr := '';\r
+  setlength(result,htons(trr(rrp.p^).datalen));\r
+  uniquestring(result);\r
+  move(trr(rrp.p^).data,result[1],length(result));\r
+end;\r
+\r
+\r
+function getipfromrr(const rrp:trrpointer;len:integer):tbinip;\r
+begin\r
+  fillchar(result,sizeof(result),0);\r
   case trr(rrp.p^).requesttype of\r
     querytype_a: begin\r
       if htons(trr(rrp.p^).datalen) <> 4 then exit;\r
-      move(trr(rrp.p^).data,state.resultbin.ip,4);\r
-      state.resultbin.family :=AF_INET;\r
+      move(trr(rrp.p^).data,result.ip,4);\r
+      result.family :=AF_INET;\r
     end;\r
     {$ifdef ipv6}\r
     querytype_aaaa: begin\r
       if htons(trr(rrp.p^).datalen) <> 16 then exit;\r
-      state.resultbin.family := AF_INET6;\r
-      move(trr(rrp.p^).data,state.resultbin.ip6,16);\r
+      result.family := AF_INET6;\r
+      move(trr(rrp.p^).data,result.ip6,16);\r
     end;\r
     {$endif}\r
+  else\r
+    {}\r
+  end;\r
+end;\r
+\r
+procedure setstate_return(const rrp:trrpointer;len:integer;var state:tdnsstate);\r
+var\r
+  a:integer;\r
+begin\r
+  state.resultaction := action_done;\r
+  state.resultstr := '';\r
+  case trr(rrp.p^).requesttype of\r
+    querytype_a{$ifdef ipv6},querytype_aaaa{$endif}: begin\r
+      state.resultbin := getipfromrr(rrp,len);\r
+    end;\r
+    querytype_txt:begin\r
+      {TXT returns a raw string}\r
+      state.resultstr := copy(getrawfromrr(rrp,len),2,9999);\r
+      fillchar(state.resultbin,sizeof(state.resultbin),0);\r
+    end;\r
+    querytype_mx:begin\r
+      {MX is a name after a 16 bits word}\r
+      state.resultstr := decodename(state.recvpacket,state.recvpacketlen,taddrint(rrp.p)-taddrint(@state.recvpacket)+12,0,a);\r
+      fillchar(state.resultbin,sizeof(state.resultbin),0);\r
+    end;\r
   else\r
     {other reply types (PTR, MX) return a hostname}\r
     state.resultstr := decodename(state.recvpacket,state.recvpacketlen,taddrint(rrp.p)-taddrint(@state.recvpacket)+10,0,a);\r
@@ -390,6 +468,13 @@ begin
   state.requesttype := querytype_ptr;\r
 end;\r
 \r
+procedure setstate_custom(const name:string; requesttype:integer; var state:tdnsstate);\r
+begin\r
+  setstate_request_init(name,state);\r
+  state.requesttype := requesttype;\r
+end;\r
+\r
+\r
 procedure setstate_failure(var state:tdnsstate);\r
 begin\r
   state.resultstr := '';\r
@@ -456,6 +541,19 @@ begin
       goto failure;\r
     end;\r
 \r
+    {if we requested A or AAAA build a list of all replies}\r
+    if (state.requesttype = querytype_a) or (state.requesttype = querytype_aaaa) then begin\r
+      state.resultlist := biniplist_new;\r
+      for a := state.numrr1[0] to (state.numrr1[0]+state.numrr1[1]-1) do begin\r
+        rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];\r
+        rrtemp := rrptemp.p;\r
+        b := rrptemp.len;\r
+        if rrtemp.requesttype = state.requesttype then begin\r
+          biniplist_add(state.resultlist,getipfromrr(rrptemp^,b));\r
+        end;\r
+      end;\r
+    end;\r
+\r
     {- check for items of the requested type in answer section, if so return success first}\r
     for a := state.numrr1[0] to (state.numrr1[0]+state.numrr1[1]-1) do begin\r
       rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];\r
@@ -480,23 +578,7 @@ begin
 \r
     {no cnames found, no items of correct type found}\r
     if state.forwardfamily <> 0 then goto failure;\r
-{$ifdef ipv6}\r
-    if (state.requesttype = querytype_a) then begin\r
-      {v6 only: in case of forward, look for AAAA in alternative section}\r
-      for a := state.numrr1[0]+state.numrr1[1]+state.numrr1[2] to (state.numrr2-1) do begin\r
-        rrptemp := @state.rrdata[1+a*sizeof(trrpointer)];\r
-        rrtemp := rrptemp.p;\r
-        b := rrptemp.len;\r
-        if rrtemp.requesttype = querytype_aaaa then begin\r
-          setstate_return(rrptemp^,b,state);\r
-          exit;\r
-        end;\r
-      end;\r
-      {no AAAA's found in alternative, do a recursive lookup for them}\r
-      state.requesttype := querytype_aaaa;\r
-      goto recursed;\r
-    end;\r
-{$endif}\r
+\r
     goto failure;\r
 recursed:\r
     {here it needs recursed lookup}\r
@@ -587,6 +669,7 @@ begin
   {$ifdef win32}\r
     if iphlpapi=0 then iphlpapi := loadlibrary('iphlpapi.dll');\r
     if not assigned(getnetworkparams) then @getnetworkparams := getprocaddress(iphlpapi,'GetNetworkParams');\r
+    if not assigned(getnetworkparams) then exit;\r
     fixed_info_len := 0;\r
     if GetNetworkParams(nil,@fixed_info_len)<>ERROR_BUFFER_OVERFLOW then exit;\r
     //fixed_info_len :=sizeof(tfixed_info);\r
@@ -635,7 +718,7 @@ begin
 end;\r
 \r
 function getcurrentsystemnameserver(var id:integer):string;\r
-var \r
+var\r
   counter : integer;\r
 \r
 begin\r
@@ -666,6 +749,75 @@ begin
 \r
 end;\r
 \r
+\r
+\r
+{$ifdef ipv6}\r
+\r
+{$ifdef linux}\r
+function getv6localips:tbiniplist;\r
+var\r
+  t:textfile;\r
+  s,s2:string;\r
+  ip:tbinip;\r
+  a:integer;\r
+begin\r
+  result := biniplist_new;\r
+\r
+  assignfile(t,'/proc/net/if_inet6');\r
+  {$i-}reset(t);{$i+}\r
+  if ioresult <> 0 then exit; {none found, return empty list}\r
+\r
+  while not eof(t) do begin\r
+    readln(t,s);\r
+    s2 := '';\r
+    for a := 0 to 7 do begin\r
+      if (s2 <> '') then s2 := s2 + ':';\r
+      s2 := s2 + copy(s,(a shl 2)+1,4);\r
+    end;\r
+    ipstrtobin(s2,ip);\r
+    if ip.family <> 0 then biniplist_add(result,ip);\r
+  end;\r
+  closefile(t);\r
+end;\r
+\r
+{$else}\r
+function getv6localips:tbiniplist;\r
+begin\r
+  result := biniplist_new;\r
+end;\r
+{$endif}\r
+\r
+procedure initpreferredmode;\r
+var\r
+  l:tbiniplist;\r
+  a:integer;\r
+  ip:tbinip;\r
+  ipmask_global,ipmask_6to4,ipmask_teredo:tbinip;\r
+\r
+begin\r
+  if preferredmodeinited then exit;\r
+  if useaf <> useaf_default then exit;\r
+  l := getv6localips;\r
+  if biniplist_getcount(l) = 0 then exit;\r
+  useaf := useaf_preferv4;\r
+  ipstrtobin('2000::',ipmask_global);\r
+  ipstrtobin('2001::',ipmask_teredo);\r
+  ipstrtobin('2002::',ipmask_6to4);\r
+  {if there is any v6 IP which is globally routable and not 6to4 and not teredo, prefer v6}\r
+  for a := biniplist_getcount(l)-1 downto 0 do begin\r
+    ip := biniplist_get(l,a);\r
+    if not comparebinipmask(ip,ipmask_global,3) then continue;\r
+    if comparebinipmask(ip,ipmask_teredo,32) then continue;\r
+    if comparebinipmask(ip,ipmask_6to4,16) then continue;\r
+    useaf := useaf_preferv6;\r
+    preferredmodeinited := true;\r
+    exit;\r
+  end;\r
+end;\r
+\r
+{$endif}\r
+\r
+\r
 {  quick and dirty description of dns packet structure to aid writing and\r
    understanding of parser code, refer to appropriate RFCs for proper specs\r
 - all words are network order\r