* make disabling/enabling a timer on windows reset it like on linux
[lcore.git] / dnswin.pas
1 unit dnswin;\r
2 \r
3 interface\r
4 \r
5 uses binipstuff,classes,lcore;\r
6 \r
7 {$include lcoreconfig.inc}\r
8 \r
9 //on failure a null string or zeroed out binip will be retuned and error will be\r
10 //set to a windows error code (error will be left untouched under non error\r
11 //conditions).\r
12 function winforwardlookuplist(name : string;familyhint:integer;var error : integer) : tbiniplist;\r
13 function winreverselookup(ip:tbinip;var error:integer):string;\r
14 \r
15 \r
16 type\r
17   //do not call destroy on a tdnswinasync instead call release and the\r
18   //dnswinasync will be freed when appropriate. Calling destroy will block\r
19   //the calling thread until the dns lookup completes.\r
20   //release should only be called from the main thread\r
21   tdnswinasync=class(tthread)\r
22   private\r
23     ipv6preffered : boolean;\r
24     freverse : boolean;\r
25     error : integer;\r
26     freewhendone : boolean;\r
27     hadevent : boolean;\r
28   protected\r
29     procedure execute; override;\r
30   public\r
31     onrequestdone:tsocketevent;\r
32     name : string;\r
33     ip : tbinip;\r
34 \r
35     procedure forwardlookup(name:string;ipv6preffered:boolean);\r
36     procedure reverselookup(ip:tbinip);\r
37     destructor destroy; override;\r
38     procedure release;\r
39     constructor create;\r
40     property reverse : boolean read freverse;\r
41 \r
42   end;\r
43 \r
44 implementation\r
45 uses\r
46   lsocket,pgtypes,sysutils,winsock,windows,messages;\r
47 \r
48 type\r
49   //taddrinfo = record; //forward declaration\r
50   paddrinfo = ^taddrinfo;\r
51   taddrinfo = packed record\r
52     ai_flags : longint;\r
53     ai_family : longint;\r
54     ai_socktype : longint;\r
55     ai_protocol : longint;\r
56     ai_addrlen : taddrint;\r
57     ai_canonname : pchar;\r
58     ai_addr : pinetsockaddrv;\r
59     ai_next : paddrinfo;\r
60   end;\r
61   ppaddrinfo = ^paddrinfo;\r
62   tgetaddrinfo = function(nodename : pchar; servname : pchar; hints : paddrinfo;res : ppaddrinfo) : longint; stdcall;\r
63   tfreeaddrinfo = procedure(ai : paddrinfo); stdcall;\r
64   tgetnameinfo = function(sa:Pinetsockaddrv;salen : longint; host:pchar;hostlen : longint;serv:pchar;servlen:longint;flags:longint) : longint;stdcall;\r
65 var\r
66   getaddrinfo : tgetaddrinfo;\r
67   freeaddrinfo : tfreeaddrinfo;\r
68   getnameinfo : tgetnameinfo;\r
69 procedure v4onlyfreeaddrinfo(ai : paddrinfo); stdcall;\r
70 var\r
71   next:paddrinfo;\r
72 begin\r
73   while assigned(ai) do begin\r
74     freemem(ai.ai_addr);\r
75     next := ai.ai_next;\r
76     freemem(ai);\r
77     ai := next;\r
78   end;\r
79 end;\r
80 \r
81 type\r
82   plongint = ^longint;\r
83   pplongint = ^plongint;\r
84 \r
85 function v4onlygetaddrinfo(nodename : pchar; servname : pchar; hints : paddrinfo;res : ppaddrinfo) : longint; stdcall;\r
86 var\r
87   output,prev,first : paddrinfo;\r
88   hostent : phostent;\r
89   addrlist:^pointer;\r
90 begin\r
91   if hints.ai_family <> af_inet6 then begin\r
92     result := 0;\r
93 \r
94 \r
95     hostent := gethostbyname(nodename);\r
96     if hostent = nil then begin\r
97       result := wsagetlasterror;\r
98       v4onlyfreeaddrinfo(output);\r
99       exit;\r
100     end;\r
101     addrlist := pointer(hostent.h_addr_list);\r
102 \r
103     //ipint := pplongint(hostent.h_addr_list)^^;\r
104     prev := nil;\r
105     first := nil;\r
106     repeat\r
107       if not assigned(addrlist^) then break;\r
108 \r
109       getmem(output,sizeof(taddrinfo));\r
110       if assigned(prev) then prev.ai_next := output;\r
111       getmem(output.ai_addr,sizeof(tinetsockaddr));\r
112       if servname <> nil then output.ai_addr.InAddr.port := htons(strtoint(servname)) else output.ai_addr.InAddr.port := 0;\r
113       output.ai_addr.InAddr.addr := longint(addrlist^^);\r
114       inc(integer(addrlist),4);\r
115       output.ai_flags := 0;\r
116       output.ai_family := af_inet;\r
117       output.ai_socktype := 0;\r
118       output.ai_protocol := 0;\r
119       output.ai_addrlen := sizeof(tinetsockaddr);\r
120       output.ai_canonname := nil;\r
121       output.ai_next := nil;\r
122       prev := output;\r
123       if not assigned(first) then first := output;\r
124     until false;\r
125     res^ := first;\r
126   end else begin\r
127     result := WSANO_RECOVERY;\r
128   end;\r
129 end;\r
130 \r
131 function min(a,b : integer):integer;\r
132 begin\r
133   if a<b then result := a else result := b;\r
134 end;\r
135 \r
136 function v4onlygetnameinfo(sa:Pinetsockaddrv;salen : longint; host:pchar;hostlen : longint;serv:pchar;servlen:longint;flags:longint) : longint;stdcall;\r
137 var\r
138   hostent : phostent;\r
139   bytestocopy : integer;\r
140 begin\r
141   if sa.InAddr.family = af_inet then begin\r
142     result := 0;\r
143     hostent := gethostbyaddr(@(sa.inaddr.addr),4,AF_INET);\r
144     if hostent = nil then begin\r
145       result := wsagetlasterror;\r
146       exit;\r
147     end;\r
148     bytestocopy := min(strlen(hostent.h_name)+1,hostlen);\r
149     move((hostent.h_name)^,host^,bytestocopy);\r
150 \r
151 \r
152   end else begin\r
153     result := WSANO_RECOVERY;\r
154   end;\r
155 end;\r
156 \r
157 \r
158 procedure populateprocvars;\r
159 var\r
160   libraryhandle : hmodule;\r
161   i : integer;\r
162   dllname : string;\r
163 \r
164 begin\r
165   if assigned(getaddrinfo) then exit; //procvars already populated\r
166   for i := 0 to 1 do begin\r
167     if i=0 then dllname := 'Ws2_32.dll' else dllname := 'Wship6.dll';\r
168     libraryhandle := LoadLibrary(pchar(dllname));\r
169     getaddrinfo := getprocaddress(libraryhandle,'getaddrinfo');\r
170     freeaddrinfo := getprocaddress(libraryhandle,'freeaddrinfo');\r
171     getnameinfo := getprocaddress(libraryhandle,'getnameinfo');\r
172     if assigned(getaddrinfo) and assigned(freeaddrinfo) and assigned(getnameinfo) then begin\r
173       //writeln('found getaddrinfo and freeaddrinfo in'+dllname);\r
174       exit; //success\r
175     end;\r
176 \r
177   end;\r
178   //writeln('could not find getaddrinfo and freeaddrinfo, falling back to ipv4 only lookup');\r
179   getaddrinfo := v4onlygetaddrinfo;\r
180   freeaddrinfo := v4onlyfreeaddrinfo;\r
181   getnameinfo := v4onlygetnameinfo;\r
182 end;\r
183 \r
184 \r
185 function winforwardlookuplist(name : string;familyhint:integer;var error : integer) : tbiniplist;\r
186 var\r
187   hints: taddrinfo;\r
188   res0,res : paddrinfo;\r
189   getaddrinforesult : integer;\r
190   biniptemp:tbinip;\r
191 begin\r
192   populateprocvars;\r
193 \r
194   hints.ai_flags := 0;\r
195   hints.ai_family := familyhint;\r
196   hints.ai_socktype := 0;\r
197   hints.ai_protocol := 0;\r
198   hints.ai_addrlen := 0;\r
199   hints.ai_canonname := nil;\r
200   hints.ai_addr := nil;\r
201   hints.ai_next := nil;\r
202   getaddrinforesult := getaddrinfo(pchar(name),'1',@hints,@res);\r
203   res0 := res;\r
204   result := biniplist_new;\r
205   if getaddrinforesult = 0 then begin\r
206 \r
207     while assigned(res) do begin\r
208       if res.ai_family = af_inet then begin\r
209         biniptemp.family := af_inet;\r
210         biniptemp.ip := res.ai_addr.InAddr.addr;\r
211         biniplist_add(result,biniptemp);\r
212       {$ifdef ipv6}\r
213       end else if res.ai_family = af_inet6 then begin\r
214         biniptemp.family := af_inet6;\r
215         biniptemp.ip6 := res.ai_addr.InAddr6.sin6_addr;\r
216         biniplist_add(result,biniptemp);\r
217       {$endif}\r
218       end;\r
219       res := res.ai_next;\r
220     end;\r
221     freeaddrinfo(res0);\r
222     exit;\r
223   end;\r
224 \r
225   if getaddrinforesult <> 0 then begin\r
226     fillchar(result,0,sizeof(result));\r
227     error := getaddrinforesult;\r
228   end;\r
229 end;\r
230 \r
231 function winreverselookup(ip:tbinip;var error : integer):string;\r
232 var\r
233   sa : tinetsockaddrv;\r
234   getnameinforesult : integer;\r
235 begin\r
236 \r
237   if ip.family = AF_INET then begin\r
238     sa.InAddr.family := AF_INET;\r
239     sa.InAddr.port := 1;\r
240     sa.InAddr.addr := ip.ip;\r
241   end else {$ifdef ipv6}if ip.family = AF_INET6 then begin\r
242     sa.InAddr6.sin6_family  := AF_INET6;\r
243     sa.InAddr6.sin6_port := 1;\r
244     sa.InAddr6.sin6_addr := ip.ip6;\r
245   end else{$endif} begin\r
246     raise exception.create('unrecognised address family');\r
247   end;\r
248   populateprocvars;\r
249   setlength(result,1025);\r
250   getnameinforesult := getnameinfo(@sa,sizeof(tinetsockaddrv),pchar(result),length(result),nil,0,0);\r
251   if getnameinforesult <> 0 then begin\r
252     error := getnameinforesult;\r
253     result := '';\r
254     exit;\r
255   end;\r
256   if pos(#0,result) >= 0 then begin\r
257     setlength(result,pos(#0,result)-1);\r
258   end;\r
259 end;\r
260 \r
261 var\r
262   hwnddnswin : hwnd;\r
263 \r
264 function MyWindowProc(\r
265     ahWnd   : HWND;\r
266     auMsg   : Integer;\r
267     awParam : WPARAM;\r
268     alParam : LPARAM): Integer; stdcall;\r
269 var\r
270   dwas : tdnswinasync;\r
271 begin\r
272   if (ahwnd=hwnddnswin) and (aumsg=wm_user) then begin\r
273     Dwas := tdnswinasync(alparam);\r
274     if assigned (dwas.onrequestdone) then dwas.onrequestdone(dwas,awparam);\r
275     dwas.hadevent := true;\r
276     if dwas.freewhendone then dwas.free;\r
277   end else begin\r
278     //not passing unknown messages on to defwindowproc will cause window\r
279     //creation to fail! --plugwash\r
280     Result := DefWindowProc(ahWnd, auMsg, awParam, alParam)\r
281   end;\r
282 end;\r
283 \r
284 procedure tdnswinasync.forwardlookup(name:string;ipv6preffered:boolean);\r
285 begin\r
286   self.name := name;\r
287   self.ipv6preffered := ipv6preffered;\r
288   freverse := false;\r
289   resume;\r
290 end;\r
291 procedure tdnswinasync.reverselookup(ip:tbinip);\r
292 begin\r
293   self.ip := ip;\r
294   freverse := true;\r
295   resume;\r
296 end;\r
297 \r
298 procedure tdnswinasync.execute;\r
299 var\r
300   error : integer;\r
301   l:tbiniplist;\r
302 begin\r
303   error := 0;\r
304   if reverse then begin\r
305     name := winreverselookup(ip,error);\r
306   end else begin\r
307     l := winforwardlookuplist(name,0,error);\r
308     ip := biniplist_get(l,0);\r
309   end;\r
310   postmessage(hwnddnswin,wm_user,error,taddrint(self));\r
311 end;\r
312 \r
313 destructor tdnswinasync.destroy;\r
314 begin\r
315   WaitFor;\r
316   inherited destroy;\r
317 end;\r
318 procedure tdnswinasync.release;\r
319 begin\r
320   if hadevent then destroy else begin\r
321     onrequestdone := nil;\r
322     freewhendone := true;\r
323   end;\r
324 end;\r
325 \r
326 constructor tdnswinasync.create;\r
327 begin\r
328   inherited create(true);\r
329 end;\r
330 \r
331 var\r
332   MyWindowClass : TWndClass = (style         : 0;\r
333                                  lpfnWndProc   : @MyWindowProc;\r
334                                  cbClsExtra    : 0;\r
335                                  cbWndExtra    : 0;\r
336                                  hInstance     : 0;\r
337                                  hIcon         : 0;\r
338                                  hCursor       : 0;\r
339                                  hbrBackground : 0;\r
340                                  lpszMenuName  : nil;\r
341                                  lpszClassName : 'dnswinClass');\r
342 begin\r
343 \r
344     if Windows.RegisterClass(MyWindowClass) = 0 then halt;\r
345   //writeln('about to create lcore handle, hinstance=',hinstance);\r
346   hwnddnswin := CreateWindowEx(WS_EX_TOOLWINDOW,\r
347                                MyWindowClass.lpszClassName,\r
348                                '',        { Window name   }\r
349                                WS_POPUP,  { Window Style  }\r
350                                0, 0,      { X, Y          }\r
351                                0, 0,      { Width, Height }\r
352                                0,         { hWndParent    }\r
353                                0,         { hMenu         }\r
354                                HInstance, { hInstance     }\r
355                                nil);      { CreateParam   }\r
356   //writeln('dnswin hwnd is ',hwnddnswin);\r
357   //writeln('last error is ',GetLastError);\r
358 end.\r