8c3ce3a01d4afca44a754a3f268ca826fb6452ed
[lcore.git] / dnsasync.pas
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
5 \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
8 \r
9 unit dnsasync;\r
10 \r
11 interface\r
12 \r
13 uses\r
14   {$ifdef win32}\r
15     dnswin,\r
16   {$endif}\r
17   lsocket,lcore,\r
18   classes,binipstuff,dnscore,btime,lcorernd;\r
19 \r
20 {$include lcoreconfig.inc}\r
21 \r
22 const\r
23   numsock=1{$ifdef ipv6}+1{$endif};\r
24 \r
25 type\r
26 \r
27   //after completion or cancelation a dnswinasync may be reused\r
28   tdnsasync=class(tcomponent)\r
29 \r
30   private\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
35 \r
36     states: array[0..numsock-1] of tdnsstate;\r
37 \r
38     destinations: array[0..numsock-1] of tbinip;\r
39 \r
40     dnsserverids : array[0..numsock-1] of integer;\r
41     startts:double;\r
42     {$ifdef win32}\r
43       dwas : tdnswinasync;\r
44     {$endif}\r
45 \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
52     {$ifdef win32}\r
53       procedure winrequestdone(sender:tobject;error:word);\r
54     {$endif}\r
55 \r
56   public\r
57     onrequestdone:tsocketevent;\r
58 \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
62     //and special uses.\r
63     addr,port:string;\r
64 \r
65     overrideaf : integer;\r
66 \r
67     procedure cancel;//cancel an outstanding dns request\r
68     function dnsresult:string; //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:string); //start forward lookup,\r
72                                                 //preffering ipv4\r
73     procedure reverselookup(const binip:tbinip); //start reverse lookup\r
74     procedure customlookup(const name:string;querytype:integer); //start custom type lookup\r
75 \r
76     constructor create(aowner:tcomponent); override;\r
77     destructor destroy; override;\r
78 \r
79   end;\r
80 \r
81 implementation\r
82 \r
83 uses sysutils;\r
84 \r
85 constructor tdnsasync.create;\r
86 begin\r
87   inherited create(aowner);\r
88   dnsserverids[0] := -1;\r
89   sockets[0] := twsocket.create(self);\r
90   sockets[0].tag := 0;\r
91   {$ifdef ipv6}\r
92     dnsserverids[1] := -1;\r
93     sockets[1] := twsocket.Create(self);\r
94     sockets[1].tag := 1;\r
95   {$endif}\r
96 end;\r
97 \r
98 destructor tdnsasync.destroy;\r
99 var\r
100   socketno : integer;\r
101 begin\r
102   for socketno := 0 to numsock -1 do begin\r
103     if dnsserverids[socketno] >= 0 then begin\r
104       reportlag(dnsserverids[socketno],-1);\r
105       dnsserverids[socketno] := -1;\r
106     end;\r
107     sockets[socketno].release;\r
108     setstate_request_init('',states[socketno]);\r
109   end;\r
110   inherited destroy;\r
111 end;\r
112 \r
113 procedure tdnsasync.receivehandler(sender:tobject;error:word);\r
114 var\r
115   socketno : integer;\r
116   Src    : TInetSockAddrV;\r
117   SrcLen : Integer;\r
118   fromip:tbinip;\r
119   fromport:string;\r
120 begin\r
121   socketno := tlsocket(sender).tag;\r
122   //writeln('got a reply on socket number ',socketno);\r
123   fillchar(states[socketno].recvpacket,sizeof(states[socketno].recvpacket),0);\r
124 \r
125   SrcLen := SizeOf(Src);\r
126   states[socketno].recvpacketlen := twsocket(sender).ReceiveFrom(@(states[socketno].recvpacket), SizeOf(states[socketno].recvpacket), Src, SrcLen);\r
127 \r
128   fromip := inaddrvtobinip(Src);\r
129   fromport := inttostr(htons(src.InAddr.port));\r
130 \r
131   if ((not comparebinip(fromip,destinations[socketno])) or (fromport <> port)) then begin\r
132    // writeln('dnsasync received from wrong IP:port ',ipbintostr(fromip),'#',fromport,', expected ',ipbintostr(destinations[socketno]),'#',port);\r
133     exit;\r
134   end;\r
135 \r
136   states[socketno].parsepacket := true;\r
137   if states[socketno].resultaction <> action_done then begin\r
138     //we ignore packets that come after we are done\r
139     if dnsserverids[socketno] >= 0 then begin\r
140       reportlag(dnsserverids[socketno],trunc((unixtimefloat-startts)*1000000));\r
141       dnsserverids[socketno] := -1;\r
142     end;\r
143   {  writeln('received reply');}\r
144 \r
145     asyncprocess(socketno);\r
146     //writeln('processed it');\r
147   end else begin\r
148     //writeln('ignored it because request is done');\r
149   end;\r
150 end;\r
151 \r
152 function tdnsasync.sendquery(socketno:integer;const packet:tdnspacket;len:integer):boolean;\r
153 var\r
154   destination : string;\r
155   inaddr : tinetsockaddrv;\r
156   trytolisten:integer;\r
157 begin\r
158 {  writeln('sendquery ',decodename(state.packet,state.packetlen,12,0,a),' ',state.requesttype);}\r
159   //writeln('trying to send query on socket number ',socketno);\r
160   result := false;\r
161   if len = 0 then exit; {no packet}\r
162   if sockets[socketno].state <> wsconnected then begin\r
163     startts := unixtimefloat;\r
164     if port = '' then port := '53';\r
165     sockets[socketno].Proto := 'udp';\r
166     sockets[socketno].ondataavailable := receivehandler;\r
167 \r
168     {we are going to bind on a random local port for the DNS request, against the kaminsky attack\r
169     there is a small chance that we're trying to bind on an already used port, so retry a few times}\r
170     for trytolisten := 3 downto 0 do begin\r
171       try\r
172         sockets[socketno].port := inttostr(1024 + randominteger(65536 - 1024));\r
173         sockets[socketno].listen;\r
174       except\r
175         {writeln('failed to listen ',sockets[socketno].localport,' ',trytolisten);}\r
176         if (trytolisten = 0) then begin\r
177           result := false;\r
178           exit;\r
179         end;\r
180       end;\r
181     end;\r
182 \r
183   end;\r
184   if addr <> '' then begin\r
185     dnsserverids[socketno] := -1;\r
186     destination := addr\r
187   end else begin\r
188     destination := getcurrentsystemnameserver(dnsserverids[socketno]);\r
189   end;\r
190   destinations[socketno] := ipstrtobinf(destination);\r
191 \r
192   {$ifdef ipv6}{$ifdef win32}\r
193   if destinations[socketno].family = AF_INET6 then if (requestaf = useaf_default) then requestaf := useaf_preferv6;\r
194   {$endif}{$endif}\r
195 \r
196   makeinaddrv(destinations[socketno],port,inaddr);\r
197   sockets[socketno].sendto(inaddr,sizeof(inaddr), @packet,len);\r
198   result := true;\r
199 \r
200 \r
201 end;\r
202 \r
203 procedure tdnsasync.asyncprocess(socketno:integer);\r
204 begin\r
205   state_process(states[socketno]);\r
206   case states[socketno].resultaction of\r
207     action_ignore: begin {do nothing} end;\r
208     action_done: begin\r
209       {$ifdef ipv6}\r
210       if (numsockused = 1) or (states[socketno xor 1].resultaction=action_done) then\r
211       //if using two sockets we need to wait until both sockets are in the done\r
212       //state before firing the event\r
213       {$endif}\r
214       begin\r
215         fresultlist := biniplist_new;\r
216         if (numsockused = 1) then begin\r
217           //writeln('processing for one state');\r
218           biniplist_addlist(fresultlist,states[0].resultlist);\r
219         {$ifdef ipv6}\r
220         end else if (requestaf = useaf_preferv6) then begin\r
221           //writeln('processing for two states, ipv6 preference');\r
222           //writeln('merging lists '+biniplist_tostr(states[1].resultlist)+' and '+biniplist_tostr(states[0].resultlist));\r
223           biniplist_addlist(fresultlist,states[1].resultlist);\r
224           biniplist_addlist(fresultlist,states[0].resultlist);\r
225         end else begin\r
226           //writeln('processing for two states, ipv4 preference');\r
227           biniplist_addlist(fresultlist,states[0].resultlist);\r
228           biniplist_addlist(fresultlist,states[1].resultlist);\r
229         {$endif}\r
230         end;\r
231         //writeln(biniplist_tostr(fresultlist));\r
232         onrequestdone(self,0);\r
233       end;\r
234     end;\r
235     action_sendquery:begin\r
236       sendquery(socketno,states[socketno].sendpacket,states[socketno].sendpacketlen);\r
237     end;\r
238   end;\r
239 end;\r
240 \r
241 procedure tdnsasync.forwardlookup;\r
242 var\r
243   bip : tbinip;\r
244   i : integer;\r
245 begin\r
246   ipstrtobin(name,bip);\r
247 \r
248   if bip.family <> 0 then begin\r
249     // it was an IP address\r
250     fresultlist := biniplist_new;\r
251     biniplist_add(fresultlist,bip);\r
252     onrequestdone(self,0);\r
253     exit;\r
254   end;\r
255 \r
256   if (overridednsserver <> '') and (addr = '') then addr := overridednsserver;\r
257 \r
258   if overrideaf = useaf_default then begin\r
259     {$ifdef ipv6}\r
260       {$ifdef win32}if not (usewindns and (addr = '')) then{$endif}\r
261       initpreferredmode;\r
262     {$endif}\r
263     requestaf := useaf;\r
264   end else begin\r
265     requestaf := overrideaf;\r
266   end;\r
267 \r
268   {$ifdef win32}\r
269     if usewindns and (addr = '') then begin\r
270       dwas := tdnswinasync.create;\r
271       dwas.onrequestdone := winrequestdone;\r
272 \r
273       dwas.forwardlookup(name);\r
274 \r
275       exit;\r
276     end;\r
277   {$endif}\r
278 \r
279   numsockused := 0;\r
280   fresultlist := biniplist_new;\r
281   if (requestaf <> useaf_v6) then begin\r
282     setstate_forward(name,states[numsockused],af_inet);\r
283     inc(numsockused);\r
284   end;\r
285 \r
286   {$ifdef ipv6}\r
287     if (requestaf <> useaf_v4) then begin\r
288       setstate_forward(name,states[numsockused],af_inet6);\r
289       inc(numsockused);\r
290     end;\r
291   {$endif}\r
292   for i := 0 to numsockused-1 do begin\r
293     asyncprocess(i);\r
294   end;\r
295 \r
296 end;\r
297 \r
298 procedure tdnsasync.reverselookup;\r
299 begin\r
300   if (overridednsserver <> '') and (addr = '') then addr := overridednsserver;\r
301   {$ifdef win32}\r
302     if usewindns and (addr = '') then begin\r
303       dwas := tdnswinasync.create;\r
304       dwas.onrequestdone := winrequestdone;\r
305       dwas.reverselookup(binip);\r
306       exit;\r
307     end;\r
308   {$endif}\r
309 \r
310   setstate_reverse(binip,states[0]);\r
311   numsockused := 1;\r
312   asyncprocess(0);\r
313 end;\r
314 \r
315 procedure tdnsasync.customlookup;\r
316 begin\r
317   if (overridednsserver <> '') and (addr = '') then addr := overridednsserver;\r
318   setstate_custom(name,querytype,states[0]);\r
319   numsockused := 1;\r
320   asyncprocess(0);\r
321 end;\r
322 \r
323 function tdnsasync.dnsresult;\r
324 begin\r
325   if states[0].resultstr <> '' then result := states[0].resultstr else begin\r
326     result := ipbintostr(biniplist_get(fresultlist,0));\r
327   end;\r
328 end;\r
329 \r
330 procedure tdnsasync.dnsresultbin(var binip:tbinip);\r
331 begin\r
332   binip := biniplist_get(fresultlist,0);\r
333 end;\r
334 \r
335 procedure tdnsasync.cancel;\r
336 var\r
337   socketno : integer;\r
338 begin\r
339   {$ifdef win32}\r
340     if assigned(dwas) then begin\r
341       dwas.release;\r
342       dwas := nil;\r
343     end else\r
344   {$endif}\r
345   begin\r
346     for socketno := 0 to numsock-1 do begin\r
347       reportlag(dnsserverids[socketno],-1);\r
348       dnsserverids[socketno] := -1;\r
349 \r
350       sockets[socketno].close;\r
351     end;\r
352 \r
353   end;\r
354   for socketno := 0 to numsock-1 do begin\r
355     setstate_failure(states[socketno]);\r
356 \r
357   end;\r
358   fresultlist := biniplist_new;\r
359   onrequestdone(self,0);\r
360 end;\r
361 \r
362 {$ifdef win32}\r
363   procedure tdnsasync.winrequestdone(sender:tobject;error:word);\r
364  \r
365   begin\r
366     if dwas.reverse then begin\r
367       states[0].resultstr := dwas.name;\r
368     end else begin \r
369 \r
370       {$ifdef ipv6}\r
371       if (requestaf = useaf_preferv4) then begin\r
372         {prefer mode: sort the IP's}\r
373         fresultlist := biniplist_new;\r
374         addipsoffamily(fresultlist,dwas.iplist,af_inet);\r
375         addipsoffamily(fresultlist,dwas.iplist,af_inet6);\r
376 \r
377       end else if (requestaf = useaf_preferv6) then begin\r
378         {prefer mode: sort the IP's}\r
379         fresultlist := biniplist_new;\r
380         addipsoffamily(fresultlist,dwas.iplist,af_inet6);\r
381         addipsoffamily(fresultlist,dwas.iplist,af_inet);\r
382         \r
383       end else\r
384       {$endif}\r
385       begin\r
386         fresultlist := dwas.iplist;\r
387       end;\r
388 \r
389     end;\r
390     dwas.release;\r
391     onrequestdone(self,error);\r
392   end;\r
393 {$endif}\r
394 end.\r