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 
  15 {$include lcoreconfig.inc}
\r 
  18   {$ifdef winasyncdns}
\r 
  22   classes,binipstuff,dnscore,btime,lcorernd;
\r 
  25   numsock=1{$ifdef ipv6}+1{$endif};
\r 
  29   //after completion or cancelation a dnswinasync may be reused
\r 
  30   tdnsasync=class(tcomponent)
\r 
  33     //made a load of stuff private that does not appear to be part of the main
\r 
  34     //public interface. If you make any of it public again please consider the
\r 
  35     //consequences when using windows dns. --plugwash.
\r 
  36     sockets: array[0..numsock-1] of tlsocket;
\r 
  38     states: array[0..numsock-1] of tdnsstate;
\r 
  40     destinations: array[0..numsock-1] of tbinip;
\r 
  42     dnsserverids : array[0..numsock-1] of integer;
\r 
  44     {$ifdef winasyncdns}
\r 
  45       dwas : tdnswinasync;
\r 
  48     numsockused : integer;
\r 
  49     fresultlist : tbiniplist;
\r 
  50     requestaf : integer;
\r 
  51     procedure asyncprocess(socketno:integer);
\r 
  52     procedure receivehandler(sender:tobject;error:word);
\r 
  53     function sendquery(socketno:integer;const packet:tdnspacket;len:integer):boolean;
\r 
  54     {$ifdef winasyncdns}
\r 
  55       procedure winrequestdone(sender:tobject;error:word);
\r 
  59     onrequestdone:tsocketevent;
\r 
  61     //addr and port allow the application to specify a dns server specifically
\r 
  62     //for this dnsasync object. This is not a recommended mode of operation
\r 
  63     //because it limits the app to one dns server but is kept for compatibility
\r 
  65     addr,port:ansistring;
\r 
  67     overrideaf : integer;
\r 
  69     procedure cancel;//cancel an outstanding dns request
\r 
  70     function dnsresult:ansistring; //get result of dnslookup as a string
\r 
  71     procedure dnsresultbin(var binip:tbinip); //get result of dnslookup as a tbinip
\r 
  72     property dnsresultlist : tbiniplist read fresultlist;
\r 
  73     procedure forwardlookup(const name:ansistring); //start forward lookup,
\r 
  75     procedure reverselookup(const binip:tbinip); //start reverse lookup
\r 
  76     procedure customlookup(const name:ansistring;querytype:integer); //start custom type lookup
\r 
  78     constructor create(aowner:tcomponent); override;
\r 
  79     destructor destroy; override;
\r 
  83 //function that the app can use to know whether the builtin or system resolver is being used
\r 
  84 function willusebuiltindns_async:boolean;
\r 
  91 function willusebuiltindns_async:boolean;
\r 
  94   {$ifdef winasyncdns}if usewindns and (overridednsserver = '') and not (hostsfile_disabled or hostsfile_onlylocalhost) then result := false;{$endif}
\r 
  98 constructor tdnsasync.create;
\r 
 100   inherited create(aowner);
\r 
 101   dnsserverids[0] := -1;
\r 
 102   sockets[0] := twsocket.create(self);
\r 
 103   sockets[0].tag := 0;
\r 
 105     dnsserverids[1] := -1;
\r 
 106     sockets[1] := twsocket.Create(self);
\r 
 107     sockets[1].tag := 1;
\r 
 111 destructor tdnsasync.destroy;
\r 
 113   socketno : integer;
\r 
 115   for socketno := 0 to numsock -1 do begin
\r 
 116     if assigned(sockets[socketno]) then begin
\r 
 117       if dnsserverids[socketno] >= 0 then begin
\r 
 118         reportlag(dnsserverids[socketno],-1);
\r 
 119         dnsserverids[socketno] := -1;
\r 
 121       sockets[socketno].release;
\r 
 122       setstate_request_init('',states[socketno]);
\r 
 126   {$ifdef winasyncdns}
\r 
 127   if assigned(dwas) then begin
\r 
 136 procedure tdnsasync.receivehandler(sender:tobject;error:word);
\r 
 138   socketno : integer;
\r 
 139   Src    : TInetSockAddrV;
\r 
 142   fromport:ansistring;
\r 
 144   socketno := tlsocket(sender).tag;
\r 
 145   //writeln('got a reply on socket number ',socketno);
\r 
 146   fillchar(states[socketno].recvpacket,sizeof(states[socketno].recvpacket),0);
\r 
 148   SrcLen := SizeOf(Src);
\r 
 149   states[socketno].recvpacketlen := twsocket(sender).ReceiveFrom(@(states[socketno].recvpacket), SizeOf(states[socketno].recvpacket), Src, SrcLen);
\r 
 151   fromip := inaddrvtobinip(Src);
\r 
 152   fromport := inttostr(htons(src.InAddr.port));
\r 
 154   if ((not comparebinip(fromip,destinations[socketno])) or (fromport <> port)) then begin
\r 
 155    // writeln('dnsasync received from wrong IP:port ',ipbintostr(fromip),'#',fromport,', expected ',ipbintostr(destinations[socketno]),'#',port);
\r 
 159   states[socketno].parsepacket := true;
\r 
 160   if states[socketno].resultaction <> action_done then begin
\r 
 161     //we ignore packets that come after we are done
\r 
 162     if dnsserverids[socketno] >= 0 then begin
\r 
 163       reportlag(dnsserverids[socketno],trunc((wintimefloat-startts)*1000000));
\r 
 164       dnsserverids[socketno] := -1;
\r 
 166   {  writeln('received reply');}
\r 
 168     asyncprocess(socketno);
\r 
 169     //writeln('processed it');
\r 
 171     //writeln('ignored it because request is done');
\r 
 175 function tdnsasync.sendquery(socketno:integer;const packet:tdnspacket;len:integer):boolean;
\r 
 177   destination : tbinip;
\r 
 178   inaddr : tinetsockaddrv;
\r 
 179   trytolisten:integer;
\r 
 181 {  writeln('sendquery ',decodename(state.packet,state.packetlen,12,0,a),' ',state.requesttype);}
\r 
 182   //writeln('trying to send query on socket number ',socketno);
\r 
 184   if len = 0 then exit; {no packet}
\r 
 185   if sockets[socketno].state <> wsconnected then begin
\r 
 186     startts := wintimefloat;
\r 
 187     if port = '' then port := '53';
\r 
 188     sockets[socketno].Proto := 'udp';
\r 
 189     sockets[socketno].ondataavailable := receivehandler;
\r 
 191     {we are going to bind on a random local port for the DNS request, against the kaminsky attack
\r 
 192     there is a small chance that we're trying to bind on an already used port, so retry a few times}
\r 
 193     for trytolisten := 3 downto 0 do begin
\r 
 195         sockets[socketno].port := inttostr(1024 + randominteger(65536 - 1024));
\r 
 196         sockets[socketno].listen;
\r 
 198         {writeln('failed to listen ',sockets[socketno].localport,' ',trytolisten);}
\r 
 199         if (trytolisten = 0) then begin
\r 
 207   if addr <> '' then begin
\r 
 208     dnsserverids[socketno] := -1;
\r 
 209     destination := ipstrtobinf(addr);
\r 
 211     destination := getcurrentsystemnameserverbin(dnsserverids[socketno]);
\r 
 213   destinations[socketno] := destination;
\r 
 215   {$ifdef ipv6}{$ifdef mswindows}
\r 
 216   if destinations[socketno].family = AF_INET6 then if (requestaf = useaf_default) then requestaf := useaf_preferv6;
\r 
 219   makeinaddrv(destinations[socketno],port,inaddr);
\r 
 220   sockets[socketno].sendto(inaddr,sizeof(inaddr), @packet,len);
\r 
 226 procedure tdnsasync.asyncprocess(socketno:integer);
\r 
 228   state_process(states[socketno]);
\r 
 229   case states[socketno].resultaction of
\r 
 230     action_ignore: begin {do nothing} end;
\r 
 233       if (numsockused = 1) or (states[socketno xor 1].resultaction=action_done) then
\r 
 234       //if using two sockets we need to wait until both sockets are in the done
\r 
 235       //state before firing the event
\r 
 238         fresultlist := biniplist_new;
\r 
 239         if (numsockused = 1) then begin
\r 
 240           //writeln('processing for one state');
\r 
 241           biniplist_addlist(fresultlist,states[0].resultlist);
\r 
 243         end else if (requestaf = useaf_preferv6) then begin
\r 
 244           //writeln('processing for two states, ipv6 preference');
\r 
 245           //writeln('merging lists '+biniplist_tostr(states[1].resultlist)+' and '+biniplist_tostr(states[0].resultlist));
\r 
 246           biniplist_addlist(fresultlist,states[1].resultlist);
\r 
 247           biniplist_addlist(fresultlist,states[0].resultlist);
\r 
 249           //writeln('processing for two states, ipv4 preference');
\r 
 250           biniplist_addlist(fresultlist,states[0].resultlist);
\r 
 251           biniplist_addlist(fresultlist,states[1].resultlist);
\r 
 254         //writeln(biniplist_tostr(fresultlist));
\r 
 255         onrequestdone(self,0);
\r 
 258     action_sendquery:begin
\r 
 259       sendquery(socketno,states[socketno].sendpacket,states[socketno].sendpacketlen);
\r 
 264 procedure tdnsasync.forwardlookup;
\r 
 268   willusewindns:boolean;
\r 
 270   ipstrtobin(name,bip);
\r 
 272   if bip.family <> 0 then begin
\r 
 273     // it was an IP address
\r 
 274     fresultlist := biniplist_new;
\r 
 275     biniplist_add(fresultlist,bip);
\r 
 276     onrequestdone(self,0);
\r 
 280   willusewindns := false;
\r 
 281   {$ifdef winasyncdns}
\r 
 282   if usewindns and (addr = '') and (overridednsserver = '') and not (hostsfile_disabled or hostsfile_onlylocalhost) then willusewindns := true;
\r 
 285   if overrideaf = useaf_default then begin
\r 
 287       if not willusewindns then initpreferredmode;
\r 
 289     requestaf := useaf;
\r 
 291     requestaf := overrideaf;
\r 
 294   {$ifdef winasyncdns}
\r 
 295     if willusewindns then begin
\r 
 296       dwas := tdnswinasync.create;
\r 
 297       dwas.onrequestdone := winrequestdone;
\r 
 299       dwas.forwardlookup(name);
\r 
 305   if (((overridednsserver = '') and (addr = '')) or hostsfile_alsocustomserver) and (not hostsfile_disabled) then begin
\r 
 306     //try a hosts file lookup
\r 
 307     fresultlist := hostsfile_forwardlookuplist(name);
\r 
 308     if (biniplist_getcount(fresultlist) > 0) then begin
\r 
 309       onrequestdone(self,0);
\r 
 315   fresultlist := biniplist_new;
\r 
 316   if (requestaf <> useaf_v6) then begin
\r 
 317     setstate_forward(name,states[numsockused],af_inet);
\r 
 322     if (requestaf <> useaf_v4) then begin
\r 
 323       setstate_forward(name,states[numsockused],af_inet6);
\r 
 328   for i := 0 to numsockused-1 do begin
\r 
 333 procedure tdnsasync.reverselookup;
\r 
 335   {$ifdef winasyncdns}
\r 
 336     if usewindns and (addr = '') and (overridednsserver = '') and not (hostsfile_disabled or hostsfile_onlylocalhost) then begin
\r 
 337       dwas := tdnswinasync.create;
\r 
 338       dwas.onrequestdone := winrequestdone;
\r 
 339       dwas.reverselookup(binip);
\r 
 344   if (((overridednsserver = '') and (addr = '')) or hostsfile_alsocustomserver) and (not hostsfile_disabled) then begin
\r 
 345     //try a hosts file lookup
\r 
 346     states[0].resultstr := hostsfile_reverselookup(binip);
\r 
 347     if (states[0].resultstr <> '') then begin
\r 
 348       onrequestdone(self,0);
\r 
 353   setstate_reverse(binip,states[0]);
\r 
 358 procedure tdnsasync.customlookup;
\r 
 360   setstate_custom(name,querytype,states[0]);
\r 
 365 function tdnsasync.dnsresult;
\r 
 367   if states[0].resultstr <> '' then result := states[0].resultstr else begin
\r 
 368     result := ipbintostr(biniplist_get(fresultlist,0));
\r 
 372 procedure tdnsasync.dnsresultbin(var binip:tbinip);
\r 
 374   binip := biniplist_get(fresultlist,0);
\r 
 377 procedure tdnsasync.cancel;
\r 
 379   socketno : integer;
\r 
 381   {$ifdef winasyncdns}
\r 
 382     if assigned(dwas) then begin
\r 
 388     for socketno := 0 to numsock-1 do begin
\r 
 389       reportlag(dnsserverids[socketno],-1);
\r 
 390       dnsserverids[socketno] := -1;
\r 
 392       sockets[socketno].close;
\r 
 396   for socketno := 0 to numsock-1 do begin
\r 
 397     setstate_failure(states[socketno]);
\r 
 400   fresultlist := biniplist_new;
\r 
 401   onrequestdone(self,0);
\r 
 404 {$ifdef winasyncdns}
\r 
 405   procedure tdnsasync.winrequestdone(sender:tobject;error:word);
\r 
 408     if dwas.reverse then begin
\r 
 409       states[0].resultstr := dwas.name;
\r 
 413       if (requestaf = useaf_preferv4) then begin
\r 
 414         {prefer mode: sort the IP's}
\r 
 415         fresultlist := biniplist_new;
\r 
 416         addipsoffamily(fresultlist,dwas.iplist,af_inet);
\r 
 417         addipsoffamily(fresultlist,dwas.iplist,af_inet6);
\r 
 419       end else if (requestaf = useaf_preferv6) then begin
\r 
 420         {prefer mode: sort the IP's}
\r 
 421         fresultlist := biniplist_new;
\r 
 422         addipsoffamily(fresultlist,dwas.iplist,af_inet6);
\r 
 423         addipsoffamily(fresultlist,dwas.iplist,af_inet);
\r 
 428         fresultlist := dwas.iplist;
\r 
 433     onrequestdone(self,error);
\r