1 { Copyright (C) 2005 Bas Steendijk and Peter Green
\r 
   2   For conditions of distribution and use, see copyright notice in zlib_license.txt
\r 
   3   which is included in the package
\r 
   4   ----------------------------------------------------------------------------- }
\r 
   6 //FIXME: this code only ever seems to use one dns server for a request and does
\r 
   7 //not seem to have any form of retry code.
\r 
  18   classes,binipstuff,dnscore,btime,lcorernd;
\r 
  20 {$include lcoreconfig.inc}
\r 
  23   numsock=1{$ifdef ipv6}+1{$endif};
\r 
  27   //after completion or cancelation a dnswinasync may be reused
\r 
  28   tdnsasync=class(tcomponent)
\r 
  31     //made a load of stuff private that does not appear to be part of the main
\r 
  32     //public interface. If you make any of it public again please consider the
\r 
  33     //consequences when using windows dns. --plugwash.
\r 
  34     sockets: array[0..numsock-1] of tlsocket;
\r 
  36     states: array[0..numsock-1] of tdnsstate;
\r 
  38     destinations: array[0..numsock-1] of tbinip;
\r 
  40     dnsserverids : array[0..numsock-1] of integer;
\r 
  43       dwas : tdnswinasync;
\r 
  46     numsockused : integer;
\r 
  47     fresultlist : tbiniplist;
\r 
  48     requestaf : integer;
\r 
  49     procedure asyncprocess(socketno:integer);
\r 
  50     procedure receivehandler(sender:tobject;error:word);
\r 
  51     function sendquery(socketno:integer;const packet:tdnspacket;len:integer):boolean;
\r 
  53       procedure winrequestdone(sender:tobject;error:word);
\r 
  57     onrequestdone:tsocketevent;
\r 
  59     //addr and port allow the application to specify a dns server specifically
\r 
  60     //for this dnsasync object. This is not a reccomended mode of operation
\r 
  61     //because it limits the app to one dns server but is kept for compatibility
\r 
  63     addr,port:ansistring;
\r 
  65     overrideaf : integer;
\r 
  67     procedure cancel;//cancel an outstanding dns request
\r 
  68     function dnsresult:ansistring; //get result of dnslookup as a string
\r 
  69     procedure dnsresultbin(var binip:tbinip); //get result of dnslookup as a tbinip
\r 
  70     property dnsresultlist : tbiniplist read fresultlist;
\r 
  71     procedure forwardlookup(const name:ansistring); //start forward lookup,
\r 
  73     procedure reverselookup(const binip:tbinip); //start reverse lookup
\r 
  74     procedure customlookup(const name:ansistring;querytype:integer); //start custom type lookup
\r 
  76     constructor create(aowner:tcomponent); override;
\r 
  77     destructor destroy; override;
\r 
  85 constructor tdnsasync.create;
\r 
  87   inherited create(aowner);
\r 
  88   dnsserverids[0] := -1;
\r 
  89   sockets[0] := twsocket.create(self);
\r 
  90   sockets[0].tag := 0;
\r 
  92     dnsserverids[1] := -1;
\r 
  93     sockets[1] := twsocket.Create(self);
\r 
  94     sockets[1].tag := 1;
\r 
  98 destructor tdnsasync.destroy;
\r 
 100   socketno : integer;
\r 
 102   for socketno := 0 to numsock -1 do begin
\r 
 103     if assigned(sockets[socketno]) then begin
\r 
 104       if dnsserverids[socketno] >= 0 then begin
\r 
 105         reportlag(dnsserverids[socketno],-1);
\r 
 106         dnsserverids[socketno] := -1;
\r 
 108       sockets[socketno].release;
\r 
 109       setstate_request_init('',states[socketno]);
\r 
 115 procedure tdnsasync.receivehandler(sender:tobject;error:word);
\r 
 117   socketno : integer;
\r 
 118   Src    : TInetSockAddrV;
\r 
 121   fromport:ansistring;
\r 
 123   socketno := tlsocket(sender).tag;
\r 
 124   //writeln('got a reply on socket number ',socketno);
\r 
 125   fillchar(states[socketno].recvpacket,sizeof(states[socketno].recvpacket),0);
\r 
 127   SrcLen := SizeOf(Src);
\r 
 128   states[socketno].recvpacketlen := twsocket(sender).ReceiveFrom(@(states[socketno].recvpacket), SizeOf(states[socketno].recvpacket), Src, SrcLen);
\r 
 130   fromip := inaddrvtobinip(Src);
\r 
 131   fromport := inttostr(htons(src.InAddr.port));
\r 
 133   if ((not comparebinip(fromip,destinations[socketno])) or (fromport <> port)) then begin
\r 
 134    // writeln('dnsasync received from wrong IP:port ',ipbintostr(fromip),'#',fromport,', expected ',ipbintostr(destinations[socketno]),'#',port);
\r 
 138   states[socketno].parsepacket := true;
\r 
 139   if states[socketno].resultaction <> action_done then begin
\r 
 140     //we ignore packets that come after we are done
\r 
 141     if dnsserverids[socketno] >= 0 then begin
\r 
 142       reportlag(dnsserverids[socketno],trunc((unixtimefloat-startts)*1000000));
\r 
 143       dnsserverids[socketno] := -1;
\r 
 145   {  writeln('received reply');}
\r 
 147     asyncprocess(socketno);
\r 
 148     //writeln('processed it');
\r 
 150     //writeln('ignored it because request is done');
\r 
 154 function tdnsasync.sendquery(socketno:integer;const packet:tdnspacket;len:integer):boolean;
\r 
 156   destination : ansistring;
\r 
 157   inaddr : tinetsockaddrv;
\r 
 158   trytolisten:integer;
\r 
 160 {  writeln('sendquery ',decodename(state.packet,state.packetlen,12,0,a),' ',state.requesttype);}
\r 
 161   //writeln('trying to send query on socket number ',socketno);
\r 
 163   if len = 0 then exit; {no packet}
\r 
 164   if sockets[socketno].state <> wsconnected then begin
\r 
 165     startts := unixtimefloat;
\r 
 166     if port = '' then port := '53';
\r 
 167     sockets[socketno].Proto := 'udp';
\r 
 168     sockets[socketno].ondataavailable := receivehandler;
\r 
 170     {we are going to bind on a random local port for the DNS request, against the kaminsky attack
\r 
 171     there is a small chance that we're trying to bind on an already used port, so retry a few times}
\r 
 172     for trytolisten := 3 downto 0 do begin
\r 
 174         sockets[socketno].port := inttostr(1024 + randominteger(65536 - 1024));
\r 
 175         sockets[socketno].listen;
\r 
 177         {writeln('failed to listen ',sockets[socketno].localport,' ',trytolisten);}
\r 
 178         if (trytolisten = 0) then begin
\r 
 186   if addr <> '' then begin
\r 
 187     dnsserverids[socketno] := -1;
\r 
 188     destination := addr
\r 
 190     destination := getcurrentsystemnameserver(dnsserverids[socketno]);
\r 
 192   destinations[socketno] := ipstrtobinf(destination);
\r 
 194   {$ifdef ipv6}{$ifdef win32}
\r 
 195   if destinations[socketno].family = AF_INET6 then if (requestaf = useaf_default) then requestaf := useaf_preferv6;
\r 
 198   makeinaddrv(destinations[socketno],port,inaddr);
\r 
 199   sockets[socketno].sendto(inaddr,sizeof(inaddr), @packet,len);
\r 
 205 procedure tdnsasync.asyncprocess(socketno:integer);
\r 
 207   state_process(states[socketno]);
\r 
 208   case states[socketno].resultaction of
\r 
 209     action_ignore: begin {do nothing} end;
\r 
 212       if (numsockused = 1) or (states[socketno xor 1].resultaction=action_done) then
\r 
 213       //if using two sockets we need to wait until both sockets are in the done
\r 
 214       //state before firing the event
\r 
 217         fresultlist := biniplist_new;
\r 
 218         if (numsockused = 1) then begin
\r 
 219           //writeln('processing for one state');
\r 
 220           biniplist_addlist(fresultlist,states[0].resultlist);
\r 
 222         end else if (requestaf = useaf_preferv6) then begin
\r 
 223           //writeln('processing for two states, ipv6 preference');
\r 
 224           //writeln('merging lists '+biniplist_tostr(states[1].resultlist)+' and '+biniplist_tostr(states[0].resultlist));
\r 
 225           biniplist_addlist(fresultlist,states[1].resultlist);
\r 
 226           biniplist_addlist(fresultlist,states[0].resultlist);
\r 
 228           //writeln('processing for two states, ipv4 preference');
\r 
 229           biniplist_addlist(fresultlist,states[0].resultlist);
\r 
 230           biniplist_addlist(fresultlist,states[1].resultlist);
\r 
 233         //writeln(biniplist_tostr(fresultlist));
\r 
 234         onrequestdone(self,0);
\r 
 237     action_sendquery:begin
\r 
 238       sendquery(socketno,states[socketno].sendpacket,states[socketno].sendpacketlen);
\r 
 243 procedure tdnsasync.forwardlookup;
\r 
 248   ipstrtobin(name,bip);
\r 
 250   if bip.family <> 0 then begin
\r 
 251     // it was an IP address
\r 
 252     fresultlist := biniplist_new;
\r 
 253     biniplist_add(fresultlist,bip);
\r 
 254     onrequestdone(self,0);
\r 
 258   if (overridednsserver <> '') and (addr = '') then addr := overridednsserver;
\r 
 260   if overrideaf = useaf_default then begin
\r 
 262       {$ifdef win32}if not (usewindns and (addr = '')) then{$endif}
\r 
 265     requestaf := useaf;
\r 
 267     requestaf := overrideaf;
\r 
 271     if usewindns and (addr = '') then begin
\r 
 272       dwas := tdnswinasync.create;
\r 
 273       dwas.onrequestdone := winrequestdone;
\r 
 275       dwas.forwardlookup(name);
\r 
 282   fresultlist := biniplist_new;
\r 
 283   if (requestaf <> useaf_v6) then begin
\r 
 284     setstate_forward(name,states[numsockused],af_inet);
\r 
 289     if (requestaf <> useaf_v4) then begin
\r 
 290       setstate_forward(name,states[numsockused],af_inet6);
\r 
 295   for i := 0 to numsockused-1 do begin
\r 
 300 procedure tdnsasync.reverselookup;
\r 
 302   if (overridednsserver <> '') and (addr = '') then addr := overridednsserver;
\r 
 304     if usewindns and (addr = '') then begin
\r 
 305       dwas := tdnswinasync.create;
\r 
 306       dwas.onrequestdone := winrequestdone;
\r 
 307       dwas.reverselookup(binip);
\r 
 312   setstate_reverse(binip,states[0]);
\r 
 317 procedure tdnsasync.customlookup;
\r 
 319   if (overridednsserver <> '') and (addr = '') then addr := overridednsserver;
\r 
 320   setstate_custom(name,querytype,states[0]);
\r 
 325 function tdnsasync.dnsresult;
\r 
 327   if states[0].resultstr <> '' then result := states[0].resultstr else begin
\r 
 328     result := ipbintostr(biniplist_get(fresultlist,0));
\r 
 332 procedure tdnsasync.dnsresultbin(var binip:tbinip);
\r 
 334   binip := biniplist_get(fresultlist,0);
\r 
 337 procedure tdnsasync.cancel;
\r 
 339   socketno : integer;
\r 
 342     if assigned(dwas) then begin
\r 
 348     for socketno := 0 to numsock-1 do begin
\r 
 349       reportlag(dnsserverids[socketno],-1);
\r 
 350       dnsserverids[socketno] := -1;
\r 
 352       sockets[socketno].close;
\r 
 356   for socketno := 0 to numsock-1 do begin
\r 
 357     setstate_failure(states[socketno]);
\r 
 360   fresultlist := biniplist_new;
\r 
 361   onrequestdone(self,0);
\r 
 365   procedure tdnsasync.winrequestdone(sender:tobject;error:word);
\r 
 368     if dwas.reverse then begin
\r 
 369       states[0].resultstr := dwas.name;
\r 
 373       if (requestaf = useaf_preferv4) then begin
\r 
 374         {prefer mode: sort the IP's}
\r 
 375         fresultlist := biniplist_new;
\r 
 376         addipsoffamily(fresultlist,dwas.iplist,af_inet);
\r 
 377         addipsoffamily(fresultlist,dwas.iplist,af_inet6);
\r 
 379       end else if (requestaf = useaf_preferv6) then begin
\r 
 380         {prefer mode: sort the IP's}
\r 
 381         fresultlist := biniplist_new;
\r 
 382         addipsoffamily(fresultlist,dwas.iplist,af_inet6);
\r 
 383         addipsoffamily(fresultlist,dwas.iplist,af_inet);
\r 
 388         fresultlist := dwas.iplist;
\r 
 393     onrequestdone(self,error);
\r