Mercurial > pidgin
comparison libgaim/dnsquery.c @ 14238:f189327b9968
[gaim-migrate @ 16920]
Cancelable DNS queries. This eliminates crashes when you cancel
a connection attempt while we're waiting for a response from a
DNS server. I tested with all three methods, so they SHOULD be ok.
Let me know if you have problems. I should be around today, starting
in maybe an hour.
I feel like it's kinda dumb for us to have three implementations for
the same thing. I want to get rid of the child-process method
(currently used in Unix and OS-X) and use the thread-based method
(currently used in Windows) everywhere. Then we can get rid of the
third method, too (currently used when !Unix and !OS-X and !Windows)
Any objections?
committer: Tailor Script <tailor@pidgin.im>
author | Mark Doliner <mark@kingant.net> |
---|---|
date | Sun, 20 Aug 2006 22:24:13 +0000 |
parents | 60b1bc8dbf37 |
children | 935f8b258d1b |
comparison
equal
deleted
inserted
replaced
14237:7cf90e0b6180 | 14238:f189327b9968 |
---|---|
24 * | 24 * |
25 */ | 25 */ |
26 | 26 |
27 #include "internal.h" | 27 #include "internal.h" |
28 #include "debug.h" | 28 #include "debug.h" |
29 #include "dnsquery.h" | |
29 #include "notify.h" | 30 #include "notify.h" |
30 #include "prefs.h" | 31 #include "prefs.h" |
31 #include "dnsquery.h" | |
32 #include "util.h" | 32 #include "util.h" |
33 | 33 |
34 /************************************************************************** | 34 /************************************************************************** |
35 * DNS query API | 35 * DNS query API |
36 **************************************************************************/ | 36 **************************************************************************/ |
37 | 37 |
38 typedef struct _GaimDnsQueryResolverProcess GaimDnsQueryResolverProcess; | |
39 | |
38 struct _GaimDnsQueryData { | 40 struct _GaimDnsQueryData { |
39 }; | 41 char *hostname; |
40 | |
41 #if defined(__unix__) || defined(__APPLE__) | |
42 | |
43 #define MAX_DNS_CHILDREN 4 | |
44 | |
45 /* | |
46 * This structure represents both a pending DNS request and | |
47 * a free child process. | |
48 */ | |
49 typedef struct { | |
50 char *host; | |
51 int port; | 42 int port; |
52 GaimDnsQueryConnectFunction callback; | 43 GaimDnsQueryConnectFunction callback; |
53 gpointer data; | 44 gpointer data; |
45 guint timeout; | |
46 | |
47 #if defined(__unix__) || defined(__APPLE__) | |
48 GaimDnsQueryResolverProcess *resolver; | |
49 #elif defined _WIN32 /* end __unix__ || __APPLE__ */ | |
50 GThread *resolver; | |
51 GSList *hosts; | |
52 gchar *error_message; | |
53 #endif | |
54 }; | |
55 | |
56 #if defined(__unix__) || defined(__APPLE__) | |
57 | |
58 #define MAX_DNS_CHILDREN 4 | |
59 | |
60 /* | |
61 * This structure keeps a reference to a child resolver process. | |
62 */ | |
63 struct _GaimDnsQueryResolverProcess { | |
54 guint inpa; | 64 guint inpa; |
55 int fd_in, fd_out; | 65 int fd_in, fd_out; |
56 pid_t dns_pid; | 66 pid_t dns_pid; |
57 } pending_dns_request_t; | 67 }; |
58 | 68 |
59 static GSList *free_dns_children = NULL; | 69 static GSList *free_dns_children = NULL; |
60 static GQueue *queued_requests = NULL; | 70 static GQueue *queued_requests = NULL; |
61 | 71 |
62 static int number_of_dns_children = 0; | 72 static int number_of_dns_children = 0; |
63 | 73 |
74 /* | |
75 * This is a convenience struct used to pass data to | |
76 * the child resolver process. | |
77 */ | |
64 typedef struct { | 78 typedef struct { |
65 char hostname[512]; | 79 char hostname[512]; |
66 int port; | 80 int port; |
67 } dns_params_t; | 81 } dns_params_t; |
68 | 82 #endif |
69 typedef struct { | 83 |
70 dns_params_t params; | 84 static void |
71 GaimDnsQueryConnectFunction callback; | 85 gaim_dnsquery_resolved(GaimDnsQueryData *query_data, GSList *hosts) |
72 gpointer data; | 86 { |
73 } queued_dns_request_t; | 87 if (query_data->callback != NULL) |
88 query_data->callback(hosts, query_data->data, NULL); | |
89 gaim_dnsquery_destroy(query_data); | |
90 } | |
91 | |
92 static void | |
93 gaim_dnsquery_failed(GaimDnsQueryData *query_data, const gchar *error_message) | |
94 { | |
95 gaim_debug_info("dnsquery", "%s\n", error_message); | |
96 if (query_data->callback != NULL) | |
97 query_data->callback(NULL, query_data->data, error_message); | |
98 gaim_dnsquery_destroy(query_data); | |
99 } | |
100 | |
101 #if defined(__unix__) || defined(__APPLE__) | |
102 | |
103 /* | |
104 * Unix! | |
105 */ | |
74 | 106 |
75 /* | 107 /* |
76 * Begin the DNS resolver child process functions. | 108 * Begin the DNS resolver child process functions. |
77 */ | 109 */ |
78 #ifdef HAVE_SIGNAL_H | 110 #ifdef HAVE_SIGNAL_H |
93 _exit(1); | 125 _exit(1); |
94 } | 126 } |
95 #endif | 127 #endif |
96 | 128 |
97 static void | 129 static void |
98 cope_with_gdb_brokenness() | 130 gaim_dnsquery_resolver_run(int child_out, int child_in, gboolean show_debug) |
99 { | |
100 #ifdef __linux__ | |
101 static gboolean already_done = FALSE; | |
102 char s[256], e[512]; | |
103 int n; | |
104 pid_t ppid; | |
105 | |
106 if(already_done) | |
107 return; | |
108 already_done = TRUE; | |
109 ppid = getppid(); | |
110 snprintf(s, sizeof(s), "/proc/%d/exe", ppid); | |
111 n = readlink(s, e, sizeof(e)); | |
112 if(n < 0) | |
113 return; | |
114 | |
115 e[MIN(n,sizeof(e)-1)] = '\0'; | |
116 | |
117 if(strstr(e,"gdb")) { | |
118 gaim_debug_info("dns", | |
119 "Debugger detected, performing useless query...\n"); | |
120 gethostbyname("x.x.x.x.x"); | |
121 } | |
122 #endif | |
123 } | |
124 | |
125 static void | |
126 gaim_dns_resolverthread(int child_out, int child_in, gboolean show_debug) | |
127 { | 131 { |
128 dns_params_t dns_params; | 132 dns_params_t dns_params; |
129 const size_t zero = 0; | 133 const size_t zero = 0; |
130 int rc; | 134 int rc; |
131 #ifdef HAVE_GETADDRINFO | 135 #ifdef HAVE_GETADDRINFO |
242 close(child_out); | 246 close(child_out); |
243 close(child_in); | 247 close(child_in); |
244 | 248 |
245 _exit(0); | 249 _exit(0); |
246 } | 250 } |
247 | 251 /* |
248 static pending_dns_request_t * | 252 * End the DNS resolver child process functions. |
249 gaim_dns_new_resolverthread(gboolean show_debug) | 253 */ |
250 { | 254 |
251 pending_dns_request_t *req; | 255 /* |
256 * Begin the functions for dealing with the DNS child processes. | |
257 */ | |
258 static void | |
259 cope_with_gdb_brokenness() | |
260 { | |
261 #ifdef __linux__ | |
262 static gboolean already_done = FALSE; | |
263 char s[256], e[512]; | |
264 int n; | |
265 pid_t ppid; | |
266 | |
267 if(already_done) | |
268 return; | |
269 already_done = TRUE; | |
270 ppid = getppid(); | |
271 snprintf(s, sizeof(s), "/proc/%d/exe", ppid); | |
272 n = readlink(s, e, sizeof(e)); | |
273 if(n < 0) | |
274 return; | |
275 | |
276 e[MIN(n,sizeof(e)-1)] = '\0'; | |
277 | |
278 if(strstr(e,"gdb")) { | |
279 gaim_debug_info("dns", | |
280 "Debugger detected, performing useless query...\n"); | |
281 gethostbyname("x.x.x.x.x"); | |
282 } | |
283 #endif | |
284 } | |
285 | |
286 static void | |
287 gaim_dnsquery_resolver_destroy(GaimDnsQueryResolverProcess *resolver) | |
288 { | |
289 g_return_if_fail(resolver != NULL); | |
290 | |
291 /* | |
292 * We might as well attempt to kill our child process. It really | |
293 * doesn't matter if this fails, because children will expire on | |
294 * their own after a few seconds. | |
295 */ | |
296 if (resolver->dns_pid > 0) | |
297 kill(resolver->dns_pid, SIGKILL); | |
298 | |
299 if (resolver->inpa != 0) | |
300 gaim_input_remove(resolver->inpa); | |
301 | |
302 close(resolver->fd_in); | |
303 close(resolver->fd_out); | |
304 | |
305 g_free(resolver); | |
306 | |
307 number_of_dns_children--; | |
308 } | |
309 | |
310 static GaimDnsQueryResolverProcess * | |
311 gaim_dnsquery_resolver_new(gboolean show_debug) | |
312 { | |
313 GaimDnsQueryResolverProcess *resolver; | |
252 int child_out[2], child_in[2]; | 314 int child_out[2], child_in[2]; |
253 | 315 |
254 /* Create pipes for communicating with the child process */ | 316 /* Create pipes for communicating with the child process */ |
255 if (pipe(child_out) || pipe(child_in)) { | 317 if (pipe(child_out) || pipe(child_in)) { |
256 gaim_debug_error("dns", | 318 gaim_debug_error("dns", |
257 "Could not create pipes: %s\n", strerror(errno)); | 319 "Could not create pipes: %s\n", strerror(errno)); |
258 return NULL; | 320 return NULL; |
259 } | 321 } |
260 | 322 |
261 req = g_new(pending_dns_request_t, 1); | 323 resolver = g_new(GaimDnsQueryResolverProcess, 1); |
324 resolver->inpa = 0; | |
262 | 325 |
263 cope_with_gdb_brokenness(); | 326 cope_with_gdb_brokenness(); |
264 | 327 |
265 /* Fork! */ | 328 /* "Go fork and multiply." --Tommy Caldwell (Emily's dad, not the climber) */ |
266 req->dns_pid = fork(); | 329 resolver->dns_pid = fork(); |
267 | 330 |
268 /* If we are the child process... */ | 331 /* If we are the child process... */ |
269 if (req->dns_pid == 0) { | 332 if (resolver->dns_pid == 0) { |
270 /* We should not access the parent's side of the pipes, so close them */ | 333 /* We should not access the parent's side of the pipes, so close them */ |
271 close(child_out[0]); | 334 close(child_out[0]); |
272 close(child_in[1]); | 335 close(child_in[1]); |
273 | 336 |
274 gaim_dns_resolverthread(child_out[1], child_in[0], show_debug); | 337 gaim_dnsquery_resolver_run(child_out[1], child_in[0], show_debug); |
275 /* The thread calls _exit() rather than returning, so we never get here */ | 338 /* The thread calls _exit() rather than returning, so we never get here */ |
276 } | 339 } |
277 | 340 |
278 /* We should not access the child's side of the pipes, so close them */ | 341 /* We should not access the child's side of the pipes, so close them */ |
279 close(child_out[1]); | 342 close(child_out[1]); |
280 close(child_in[0]); | 343 close(child_in[0]); |
281 if (req->dns_pid == -1) { | 344 if (resolver->dns_pid == -1) { |
282 gaim_debug_error("dns", | 345 gaim_debug_error("dns", |
283 "Could not create child process for DNS: %s\n", | 346 "Could not create child process for DNS: %s\n", |
284 strerror(errno)); | 347 strerror(errno)); |
285 g_free(req); | 348 gaim_dnsquery_resolver_destroy(resolver); |
286 return NULL; | 349 return NULL; |
287 } | 350 } |
288 | 351 |
289 req->fd_out = child_out[0]; | 352 resolver->fd_out = child_out[0]; |
290 req->fd_in = child_in[1]; | 353 resolver->fd_in = child_in[1]; |
291 number_of_dns_children++; | 354 number_of_dns_children++; |
292 gaim_debug_info("dns", | 355 gaim_debug_info("dns", |
293 "Created new DNS child %d, there are now %d children.\n", | 356 "Created new DNS child %d, there are now %d children.\n", |
294 req->dns_pid, number_of_dns_children); | 357 resolver->dns_pid, number_of_dns_children); |
295 | 358 |
296 return req; | 359 return resolver; |
297 } | 360 } |
298 /* | 361 |
299 * End the DNS resolver child process functions. | 362 /** |
300 */ | 363 * @return TRUE if the request was sent succesfully. FALSE |
301 | 364 * if the request could not be sent. This isn't |
302 /* | 365 * necessarily an error. If the child has expired, |
303 * Begin the functions for dealing with the DNS child processes. | 366 * for example, we won't be able to send the message. |
304 */ | 367 */ |
305 static void | 368 static gboolean |
306 req_free(pending_dns_request_t *req) | 369 send_dns_request_to_child(GaimDnsQueryData *query_data, |
307 { | 370 GaimDnsQueryResolverProcess *resolver) |
308 g_return_if_fail(req != NULL); | 371 { |
309 | 372 pid_t pid; |
310 close(req->fd_in); | 373 dns_params_t dns_params; |
311 close(req->fd_out); | 374 int rc; |
312 | |
313 g_free(req->host); | |
314 g_free(req); | |
315 | |
316 number_of_dns_children--; | |
317 } | |
318 | |
319 static int | |
320 send_dns_request_to_child(pending_dns_request_t *req, dns_params_t *dns_params) | |
321 { | |
322 char ch; | 375 char ch; |
323 int rc; | |
324 pid_t pid; | |
325 | 376 |
326 /* This waitpid might return the child's PID if it has recently | 377 /* This waitpid might return the child's PID if it has recently |
327 * exited, or it might return an error if it exited "long | 378 * exited, or it might return an error if it exited "long |
328 * enough" ago that it has already been reaped; in either | 379 * enough" ago that it has already been reaped; in either |
329 * instance, we can't use it. */ | 380 * instance, we can't use it. */ |
330 if ((pid = waitpid (req->dns_pid, NULL, WNOHANG)) > 0) { | 381 pid = waitpid(resolver->dns_pid, NULL, WNOHANG); |
382 if (pid > 0) { | |
383 gaim_debug_warning("dns", "DNS child %d no longer exists\n", | |
384 resolver->dns_pid); | |
385 gaim_dnsquery_resolver_destroy(resolver); | |
386 return FALSE; | |
387 } else if (pid < 0) { | |
388 gaim_debug_warning("dns", "Wait for DNS child %d failed: %s\n", | |
389 resolver->dns_pid, strerror(errno)); | |
390 gaim_dnsquery_resolver_destroy(resolver); | |
391 return FALSE; | |
392 } | |
393 | |
394 /* Copy the hostname and port into a single data structure */ | |
395 strncpy(dns_params.hostname, query_data->hostname, sizeof(dns_params.hostname) - 1); | |
396 dns_params.hostname[sizeof(dns_params.hostname) - 1] = '\0'; | |
397 dns_params.port = query_data->port; | |
398 | |
399 /* Send the data structure to the child */ | |
400 rc = write(resolver->fd_in, &dns_params, sizeof(dns_params)); | |
401 if (rc < 0) { | |
402 gaim_debug_error("dns", "Unable to write to DNS child %d: %d\n", | |
403 resolver->dns_pid, strerror(errno)); | |
404 gaim_dnsquery_resolver_destroy(resolver); | |
405 return FALSE; | |
406 } | |
407 | |
408 g_return_val_if_fail(rc == sizeof(dns_params), -1); | |
409 | |
410 /* Did you hear me? (This avoids some race conditions) */ | |
411 rc = read(resolver->fd_out, &ch, sizeof(ch)); | |
412 if (rc != 1 || ch != 'Y') | |
413 { | |
331 gaim_debug_warning("dns", | 414 gaim_debug_warning("dns", |
332 "DNS child %d no longer exists\n", req->dns_pid); | 415 "DNS child %d not responding. Killing it!\n", |
333 return -1; | 416 resolver->dns_pid); |
334 } else if (pid < 0) { | 417 gaim_dnsquery_resolver_destroy(resolver); |
335 gaim_debug_warning("dns", | 418 return FALSE; |
336 "Wait for DNS child %d failed: %s\n", | |
337 req->dns_pid, strerror(errno)); | |
338 return -1; | |
339 } | |
340 | |
341 /* Let's contact this lost child! */ | |
342 rc = write(req->fd_in, dns_params, sizeof(*dns_params)); | |
343 if (rc < 0) { | |
344 gaim_debug_error("dns", | |
345 "Unable to write to DNS child %d: %d\n", | |
346 req->dns_pid, strerror(errno)); | |
347 close(req->fd_in); | |
348 return -1; | |
349 } | |
350 | |
351 g_return_val_if_fail(rc == sizeof(*dns_params), -1); | |
352 | |
353 /* Did you hear me? (This avoids some race conditions) */ | |
354 rc = read(req->fd_out, &ch, sizeof(ch)); | |
355 if (rc != 1 || ch != 'Y') | |
356 { | |
357 gaim_debug_warning("dns", | |
358 "DNS child %d not responding. Killing it!\n", | |
359 req->dns_pid); | |
360 kill(req->dns_pid, SIGKILL); | |
361 return -1; | |
362 } | 419 } |
363 | 420 |
364 gaim_debug_info("dns", | 421 gaim_debug_info("dns", |
365 "Successfully sent DNS request to child %d\n", req->dns_pid); | 422 "Successfully sent DNS request to child %d\n", |
366 | 423 resolver->dns_pid); |
367 return 0; | 424 |
368 } | 425 query_data->resolver = resolver; |
426 | |
427 return TRUE; | |
428 } | |
429 | |
430 static void host_resolved(gpointer data, gint source, GaimInputCondition cond); | |
369 | 431 |
370 static void | 432 static void |
371 host_resolved(gpointer data, gint source, GaimInputCondition cond); | 433 handle_next_queued_request() |
372 | 434 { |
373 static void | 435 GaimDnsQueryData *query_data; |
374 release_dns_child(pending_dns_request_t *req) | 436 GaimDnsQueryResolverProcess *resolver; |
375 { | 437 |
376 g_free(req->host); | 438 if ((queued_requests == NULL) || (g_queue_is_empty(queued_requests))) |
377 req->host = NULL; | 439 /* No more DNS queries, yay! */ |
378 | 440 return; |
379 if (queued_requests && !g_queue_is_empty(queued_requests)) { | 441 |
380 queued_dns_request_t *r = g_queue_pop_head(queued_requests); | 442 query_data = g_queue_pop_head(queued_requests); |
381 req->host = g_strdup(r->params.hostname); | 443 |
382 req->port = r->params.port; | 444 /* |
383 req->callback = r->callback; | 445 * If we have any children, attempt to have them perform the DNS |
384 req->data = r->data; | 446 * query. If we're able to send the query then resolver will be |
385 | 447 * set to the GaimDnsQueryResolverProcess. Otherwise, resolver |
386 gaim_debug_info("dns", | 448 * will be NULL and we'll need to create a new DNS request child. |
387 "Processing queued DNS query for '%s' with child %d\n", | 449 */ |
388 req->host, req->dns_pid); | 450 while (free_dns_children != NULL) |
389 | 451 { |
390 if (send_dns_request_to_child(req, &(r->params)) != 0) { | 452 resolver = free_dns_children->data; |
391 req_free(req); | 453 free_dns_children = g_slist_remove(free_dns_children, resolver); |
392 req = NULL; | 454 |
393 | 455 if (send_dns_request_to_child(query_data, resolver)) |
394 gaim_debug_warning("dns", | 456 /* We found an acceptable child, yay */ |
395 "Intent of process queued query of '%s' failed, " | 457 break; |
396 "requeueing...\n", r->params.hostname); | 458 } |
397 g_queue_push_head(queued_requests, r); | 459 |
398 } else { | 460 /* We need to create a new DNS request child */ |
399 req->inpa = gaim_input_add(req->fd_out, GAIM_INPUT_READ, host_resolved, req); | 461 if (query_data->resolver == NULL) |
400 g_free(r); | 462 { |
401 } | 463 if (number_of_dns_children >= MAX_DNS_CHILDREN) |
402 | 464 { |
403 } else { | 465 /* Apparently all our children are busy */ |
404 req->host = NULL; | 466 g_queue_push_head(queued_requests, query_data); |
405 req->callback = NULL; | 467 return; |
406 req->data = NULL; | 468 } |
407 free_dns_children = g_slist_append(free_dns_children, req); | 469 |
408 } | 470 resolver = gaim_dnsquery_resolver_new(gaim_debug_is_enabled()); |
409 } | 471 if (resolver == NULL) |
472 { | |
473 gaim_dnsquery_failed(query_data, _("Unable to create new resolver process\n")); | |
474 return; | |
475 } | |
476 if (!send_dns_request_to_child(query_data, resolver)) | |
477 { | |
478 gaim_dnsquery_failed(query_data, _("Unable to send request to resolver process\n")); | |
479 return; | |
480 } | |
481 } | |
482 | |
483 query_data->resolver->inpa = gaim_input_add(query_data->resolver->fd_out, | |
484 GAIM_INPUT_READ, host_resolved, query_data); | |
485 } | |
486 | |
487 /* | |
488 * End the functions for dealing with the DNS child processes. | |
489 */ | |
410 | 490 |
411 static void | 491 static void |
412 host_resolved(gpointer data, gint source, GaimInputCondition cond) | 492 host_resolved(gpointer data, gint source, GaimInputCondition cond) |
413 { | 493 { |
414 pending_dns_request_t *req = (pending_dns_request_t*)data; | 494 GaimDnsQueryData *query_data; |
415 int rc, err; | 495 int rc, err; |
416 GSList *hosts = NULL; | 496 GSList *hosts = NULL; |
417 struct sockaddr *addr = NULL; | 497 struct sockaddr *addr = NULL; |
418 size_t addrlen; | 498 size_t addrlen; |
419 | 499 char message[1024]; |
420 gaim_debug_info("dns", "Got response for '%s'\n", req->host); | 500 |
421 gaim_input_remove(req->inpa); | 501 query_data = data; |
422 | 502 |
423 rc = read(req->fd_out, &err, sizeof(err)); | 503 gaim_debug_info("dns", "Got response for '%s'\n", query_data->hostname); |
504 gaim_input_remove(query_data->resolver->inpa); | |
505 query_data->resolver->inpa = 0; | |
506 | |
507 rc = read(query_data->resolver->fd_out, &err, sizeof(err)); | |
424 if ((rc == 4) && (err != 0)) | 508 if ((rc == 4) && (err != 0)) |
425 { | 509 { |
426 char message[1024]; | |
427 #ifdef HAVE_GETADDRINFO | 510 #ifdef HAVE_GETADDRINFO |
428 g_snprintf(message, sizeof(message), "DNS error: %s (pid=%d)", | 511 g_snprintf(message, sizeof(message), _("Error resolving %s: %s"), |
429 gai_strerror(err), req->dns_pid); | 512 query_data->hostname, gai_strerror(err)); |
430 #else | 513 #else |
431 g_snprintf(message, sizeof(message), "DNS error: %d (pid=%d)", | 514 g_snprintf(message, sizeof(message), _("Error resolving %s: %d"), |
432 err, req->dns_pid); | 515 query_data->hostname, err); |
433 #endif | 516 #endif |
434 gaim_debug_error("dns", "%s\n", message); | 517 gaim_dnsquery_failed(query_data, message); |
435 req->callback(NULL, req->data, message); | 518 |
436 release_dns_child(req); | 519 } else if (rc > 0) { |
437 return; | 520 /* Success! */ |
438 } | |
439 if (rc > 0) | |
440 { | |
441 while (rc > 0) { | 521 while (rc > 0) { |
442 rc = read(req->fd_out, &addrlen, sizeof(addrlen)); | 522 rc = read(query_data->resolver->fd_out, &addrlen, sizeof(addrlen)); |
443 if (rc > 0 && addrlen > 0) { | 523 if (rc > 0 && addrlen > 0) { |
444 addr = g_malloc(addrlen); | 524 addr = g_malloc(addrlen); |
445 rc = read(req->fd_out, addr, addrlen); | 525 rc = read(query_data->resolver->fd_out, addr, addrlen); |
446 hosts = g_slist_append(hosts, GINT_TO_POINTER(addrlen)); | 526 hosts = g_slist_append(hosts, GINT_TO_POINTER(addrlen)); |
447 hosts = g_slist_append(hosts, addr); | 527 hosts = g_slist_append(hosts, addr); |
448 } else { | 528 } else { |
449 break; | 529 break; |
450 } | 530 } |
451 } | 531 } |
532 /* wait4(resolver->dns_pid, NULL, WNOHANG, NULL); */ | |
533 gaim_dnsquery_resolved(query_data, hosts); | |
534 | |
452 } else if (rc == -1) { | 535 } else if (rc == -1) { |
453 char message[1024]; | 536 g_snprintf(message, sizeof(message), _("Error reading from resolver process: %s"), strerror(errno)); |
454 g_snprintf(message, sizeof(message), "Error reading from DNS child: %s",strerror(errno)); | 537 gaim_dnsquery_failed(query_data, message); |
455 gaim_debug_error("dns", "%s\n", message); | 538 |
456 req->callback(NULL, req->data, message); | |
457 req_free(req); | |
458 return; | |
459 } else if (rc == 0) { | 539 } else if (rc == 0) { |
460 char message[1024]; | 540 g_snprintf(message, sizeof(message), _("EOF while reading from resolver process")); |
461 g_snprintf(message, sizeof(message), "EOF reading from DNS child"); | 541 gaim_dnsquery_failed(query_data, message); |
462 close(req->fd_out); | 542 } |
463 gaim_debug_error("dns", "%s\n", message); | 543 |
464 req->callback(NULL, req->data, message); | 544 handle_next_queued_request(); |
465 req_free(req); | 545 } |
466 return; | 546 |
467 } | 547 static gboolean |
468 | 548 resolve_host(gpointer data) |
469 /* wait4(req->dns_pid, NULL, WNOHANG, NULL); */ | 549 { |
470 | 550 GaimDnsQueryData *query_data; |
471 req->callback(hosts, req->data, NULL); | 551 |
472 | 552 query_data = data; |
473 release_dns_child(req); | 553 query_data->timeout = 0; |
474 } | 554 |
555 handle_next_queued_request(); | |
556 | |
557 return FALSE; | |
558 } | |
559 | |
560 GaimDnsQueryData * | |
561 gaim_dnsquery_a(const char *hostname, int port, | |
562 GaimDnsQueryConnectFunction callback, gpointer data) | |
563 { | |
564 GaimDnsQueryData *query_data; | |
565 | |
566 g_return_val_if_fail(hostname != NULL, NULL); | |
567 g_return_val_if_fail(port != 0, NULL); | |
568 | |
569 query_data = g_new(GaimDnsQueryData, 1); | |
570 query_data->hostname = g_strdup(hostname); | |
571 g_strstrip(query_data->hostname); | |
572 query_data->port = port; | |
573 query_data->callback = callback; | |
574 query_data->data = data; | |
575 query_data->resolver = NULL; | |
576 | |
577 if (!queued_requests) | |
578 queued_requests = g_queue_new(); | |
579 g_queue_push_tail(queued_requests, query_data); | |
580 | |
581 gaim_debug_info("dns", "DNS query for '%s' queued\n", query_data->hostname); | |
582 | |
583 query_data->timeout = gaim_timeout_add(0, resolve_host, query_data); | |
584 | |
585 return query_data; | |
586 } | |
587 | |
588 #elif defined _WIN32 /* end __unix__ || __APPLE__ */ | |
589 | |
475 /* | 590 /* |
476 * End the functions for dealing with the DNS child processes. | 591 * Windows! |
477 */ | 592 */ |
478 | 593 |
479 GaimDnsQueryData * | 594 static gboolean |
480 gaim_dnsquery_a(const char *hostname, int port, GaimDnsQueryConnectFunction callback, gpointer data) | 595 dns_main_thread_cb(gpointer data) |
481 { | 596 { |
482 pending_dns_request_t *req = NULL; | 597 GaimDnsQueryData *query_data; |
483 dns_params_t dns_params; | 598 |
484 gchar *host_temp; | 599 query_data = data; |
485 gboolean show_debug; | 600 |
486 | 601 if (query_data->error_message != NULL) |
487 show_debug = gaim_debug_is_enabled(); | 602 gaim_dnsquery_failed(query_data, query_data->error_message); |
488 | 603 else |
489 host_temp = g_strstrip(g_strdup(hostname)); | 604 { |
490 strncpy(dns_params.hostname, host_temp, sizeof(dns_params.hostname) - 1); | 605 GSList *hosts; |
491 g_free(host_temp); | 606 /* We don't want gaim_dns_query_resolved() to free(hosts) */ |
492 dns_params.hostname[sizeof(dns_params.hostname) - 1] = '\0'; | 607 hosts = query_data->hosts; |
493 dns_params.port = port; | 608 query_data->hosts = NULL; |
494 | 609 gaim_dnsquery_resolved(query_data, hosts); |
495 /* | 610 } |
496 * If we have any children, attempt to have them perform the DNS | 611 |
497 * query. If we're able to send the query to a child, then req | |
498 * will be set to the pending_dns_request_t. Otherwise, req will | |
499 * be NULL and we'll need to create a new DNS request child. | |
500 */ | |
501 while (free_dns_children != NULL) { | |
502 req = free_dns_children->data; | |
503 free_dns_children = g_slist_remove(free_dns_children, req); | |
504 | |
505 if (send_dns_request_to_child(req, &dns_params) == 0) | |
506 /* We found an acceptable child, yay */ | |
507 break; | |
508 | |
509 req_free(req); | |
510 req = NULL; | |
511 } | |
512 | |
513 /* We need to create a new DNS request child */ | |
514 if (req == NULL) { | |
515 if (number_of_dns_children >= MAX_DNS_CHILDREN) { | |
516 queued_dns_request_t *r = g_new(queued_dns_request_t, 1); | |
517 memcpy(&(r->params), &dns_params, sizeof(dns_params)); | |
518 r->callback = callback; | |
519 r->data = data; | |
520 if (!queued_requests) | |
521 queued_requests = g_queue_new(); | |
522 g_queue_push_tail(queued_requests, r); | |
523 | |
524 gaim_debug_info("dns", | |
525 "DNS query for '%s' queued\n", dns_params.hostname); | |
526 | |
527 return (GaimDnsQueryData *)1; | |
528 } | |
529 | |
530 req = gaim_dns_new_resolverthread(show_debug); | |
531 if (req == NULL) | |
532 { | |
533 gaim_debug_error("proxy", "oh dear, this is going to explode, I give up\n"); | |
534 return NULL; | |
535 } | |
536 send_dns_request_to_child(req, &dns_params); | |
537 } | |
538 | |
539 req->host = g_strdup(hostname); | |
540 req->port = port; | |
541 req->callback = callback; | |
542 req->data = data; | |
543 req->inpa = gaim_input_add(req->fd_out, GAIM_INPUT_READ, host_resolved, req); | |
544 | |
545 return (GaimDnsQueryData *)1; | |
546 } | |
547 | |
548 #elif defined _WIN32 /* end __unix__ || __APPLE__ */ | |
549 | |
550 typedef struct _dns_tdata { | |
551 char *hostname; | |
552 int port; | |
553 GaimDnsQueryConnectFunction callback; | |
554 gpointer data; | |
555 GSList *hosts; | |
556 char *errmsg; | |
557 } dns_tdata; | |
558 | |
559 static gboolean dns_main_thread_cb(gpointer data) { | |
560 dns_tdata *td = (dns_tdata*)data; | |
561 if (td->errmsg != NULL) { | |
562 gaim_debug_info("dns", "%s\n", td->errmsg); | |
563 } | |
564 td->callback(td->hosts, td->data, td->errmsg); | |
565 g_free(td->hostname); | |
566 g_free(td->errmsg); | |
567 g_free(td); | |
568 return FALSE; | 612 return FALSE; |
569 } | 613 } |
570 | 614 |
571 static gpointer dns_thread(gpointer data) { | 615 static gpointer |
572 | 616 dns_thread(gpointer data) |
617 { | |
618 GaimDnsQueryData *query_data; | |
573 #ifdef HAVE_GETADDRINFO | 619 #ifdef HAVE_GETADDRINFO |
574 int rc; | 620 int rc; |
575 struct addrinfo hints, *res, *tmp; | 621 struct addrinfo hints, *res, *tmp; |
576 char servname[20]; | 622 char servname[20]; |
577 #else | 623 #else |
578 struct sockaddr_in sin; | 624 struct sockaddr_in sin; |
579 struct hostent *hp; | 625 struct hostent *hp; |
580 #endif | 626 #endif |
581 dns_tdata *td = (dns_tdata*)data; | 627 |
628 query_data = data; | |
582 | 629 |
583 #ifdef HAVE_GETADDRINFO | 630 #ifdef HAVE_GETADDRINFO |
584 g_snprintf(servname, sizeof(servname), "%d", td->port); | 631 g_snprintf(servname, sizeof(servname), "%d", query_data->port); |
585 memset(&hints,0,sizeof(hints)); | 632 memset(&hints,0,sizeof(hints)); |
586 | 633 |
587 /* This is only used to convert a service | 634 /* |
635 * This is only used to convert a service | |
588 * name to a port number. As we know we are | 636 * name to a port number. As we know we are |
589 * passing a number already, we know this | 637 * passing a number already, we know this |
590 * value will not be really used by the C | 638 * value will not be really used by the C |
591 * library. | 639 * library. |
592 */ | 640 */ |
593 hints.ai_socktype = SOCK_STREAM; | 641 hints.ai_socktype = SOCK_STREAM; |
594 if ((rc = getaddrinfo(td->hostname, servname, &hints, &res)) == 0) { | 642 if ((rc = getaddrinfo(query_data->hostname, servname, &hints, &res)) == 0) { |
595 tmp = res; | 643 tmp = res; |
596 while(res) { | 644 while(res) { |
597 td->hosts = g_slist_append(td->hosts, | 645 query_data->hosts = g_slist_append(query_data->hosts, |
598 GSIZE_TO_POINTER(res->ai_addrlen)); | 646 GSIZE_TO_POINTER(res->ai_addrlen)); |
599 td->hosts = g_slist_append(td->hosts, | 647 query_data->hosts = g_slist_append(query_data->hosts, |
600 g_memdup(res->ai_addr, res->ai_addrlen)); | 648 g_memdup(res->ai_addr, res->ai_addrlen)); |
601 res = res->ai_next; | 649 res = res->ai_next; |
602 } | 650 } |
603 freeaddrinfo(tmp); | 651 freeaddrinfo(tmp); |
604 } else { | 652 } else { |
605 td->errmsg = g_strdup_printf("DNS getaddrinfo(\"%s\", \"%s\") error: %d", td->hostname, servname, rc); | 653 query_data->error_message = g_strdup_printf(_("Error resolving %s: %s"), query_data->hostname, gai_strerror(rc)); |
606 } | 654 } |
607 #else | 655 #else |
608 if ((hp = gethostbyname(td->hostname))) { | 656 if ((hp = gethostbyname(query_data->hostname))) { |
609 memset(&sin, 0, sizeof(struct sockaddr_in)); | 657 memset(&sin, 0, sizeof(struct sockaddr_in)); |
610 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); | 658 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); |
611 sin.sin_family = hp->h_addrtype; | 659 sin.sin_family = hp->h_addrtype; |
612 sin.sin_port = htons(td->port); | 660 sin.sin_port = htons(query_data->port); |
613 | 661 |
614 td->hosts = g_slist_append(td->hosts, | 662 query_data->hosts = g_slist_append(query_data->hosts, |
615 GSIZE_TO_POINTER(sizeof(sin))); | 663 GSIZE_TO_POINTER(sizeof(sin))); |
616 td->hosts = g_slist_append(td->hosts, | 664 query_data->hosts = g_slist_append(query_data->hosts, |
617 g_memdup(&sin, sizeof(sin))); | 665 g_memdup(&sin, sizeof(sin))); |
618 } else { | 666 } else { |
619 td->errmsg = g_strdup_printf("DNS gethostbyname(\"%s\") error: %d", td->hostname, h_errno); | 667 query_data->error_message = g_strdup_printf(_("Error resolving %s: %d"), query_data->hostname, h_errno); |
620 } | 668 } |
621 #endif | 669 #endif |
670 | |
622 /* back to main thread */ | 671 /* back to main thread */ |
623 g_idle_add(dns_main_thread_cb, td); | 672 g_idle_add(dns_main_thread_cb, query_data); |
673 | |
624 return 0; | 674 return 0; |
675 } | |
676 | |
677 static gboolean | |
678 resolve_host(gpointer data) | |
679 { | |
680 GaimDnsQueryData *query_data; | |
681 struct sockaddr_in sin; | |
682 GError *err = NULL; | |
683 | |
684 query_data = data; | |
685 query_data->timeout = 0; | |
686 | |
687 if (inet_aton(query_data->hostname, &sin.sin_addr)) | |
688 { | |
689 GSList *hosts = NULL; | |
690 sin.sin_family = AF_INET; | |
691 sin.sin_port = htons(query_data->port); | |
692 hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin))); | |
693 hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin))); | |
694 gaim_dnsquery_resolved(query_data, hosts); | |
695 } | |
696 else | |
697 { | |
698 query_data->resolver = g_thread_create(dns_thread, | |
699 query_data, FALSE, &err); | |
700 if (query_data->resolver == NULL) | |
701 { | |
702 char message[1024]; | |
703 g_snprintf(message, sizeof(message), _("Thread creation failure: %s"), | |
704 err ? err->message : _("Unknown reason")); | |
705 g_error_free(err); | |
706 gaim_dnsquery_failed(query_data, message); | |
707 } | |
708 } | |
709 | |
710 return FALSE; | |
625 } | 711 } |
626 | 712 |
627 GaimDnsQueryData * | 713 GaimDnsQueryData * |
628 gaim_dnsquery_a(const char *hostname, int port, | 714 gaim_dnsquery_a(const char *hostname, int port, |
629 GaimDnsQueryConnectFunction callback, gpointer data) | 715 GaimDnsQueryConnectFunction callback, gpointer data) |
630 { | 716 { |
631 dns_tdata *td; | 717 GaimDnsQueryData *query_data; |
718 | |
719 g_return_val_if_fail(hostname != NULL, NULL); | |
720 g_return_val_if_fail(port != 0, NULL); | |
721 | |
722 query_data = g_new(GaimDnsQueryData, 1); | |
723 query_data->hostname = g_strdup(hostname); | |
724 g_strstrip(query_data->hostname); | |
725 query_data->port = port; | |
726 query_data->callback = callback; | |
727 query_data->data = data; | |
728 query_data->error_message = NULL; | |
729 query_data->hosts = NULL; | |
730 | |
731 /* Don't call the callback before returning */ | |
732 query_data->timeout = gaim_timeout_add(0, resolve_host, query_data); | |
733 | |
734 return query_data; | |
735 } | |
736 | |
737 #else /* not __unix__ or __APPLE__ or _WIN32 */ | |
738 | |
739 /* | |
740 * We weren't able to do anything fancier above, so use the | |
741 * fail-safe name resolution code, which is blocking. | |
742 */ | |
743 | |
744 static gboolean | |
745 resolve_host(gpointer data) | |
746 { | |
747 GaimDnsQueryData *query_data; | |
632 struct sockaddr_in sin; | 748 struct sockaddr_in sin; |
633 GError* err = NULL; | |
634 | |
635 if(inet_aton(hostname, &sin.sin_addr)) { | |
636 GSList *hosts = NULL; | |
637 sin.sin_family = AF_INET; | |
638 sin.sin_port = htons(port); | |
639 hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin))); | |
640 hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin))); | |
641 callback(hosts, data, NULL); | |
642 return (GaimDnsQueryData *)1; | |
643 } | |
644 | |
645 gaim_debug_info("dns", "DNS Lookup for: %s\n", hostname); | |
646 td = g_new0(dns_tdata, 1); | |
647 td->hostname = g_strdup(hostname); | |
648 td->port = port; | |
649 td->callback = callback; | |
650 td->data = data; | |
651 | |
652 if(!g_thread_create(dns_thread, td, FALSE, &err)) { | |
653 gaim_debug_error("dns", "DNS thread create failure: %s\n", err?err->message:""); | |
654 g_error_free(err); | |
655 g_free(td->hostname); | |
656 g_free(td); | |
657 return NULL; | |
658 } | |
659 return (GaimDnsQueryData *)1; | |
660 } | |
661 | |
662 #else /* not __unix__ or __APPLE__ or _WIN32 */ | |
663 | |
664 typedef struct { | |
665 gpointer data; | |
666 size_t addrlen; | |
667 struct sockaddr *addr; | |
668 GaimDnsQueryConnectFunction callback; | |
669 } pending_dns_request_t; | |
670 | |
671 static gboolean host_resolved(gpointer data) | |
672 { | |
673 pending_dns_request_t *req = (pending_dns_request_t*)data; | |
674 GSList *hosts = NULL; | 749 GSList *hosts = NULL; |
675 hosts = g_slist_append(hosts, GINT_TO_POINTER(req->addrlen)); | 750 |
676 hosts = g_slist_append(hosts, req->addr); | 751 query_data = data; |
677 req->callback(hosts, req->data, NULL); | 752 query_data->timeout = 0; |
678 g_free(req); | 753 |
679 return FALSE; | 754 if (!inet_aton(query_data->hostname, &sin.sin_addr)) { |
680 } | |
681 | |
682 GaimDnsQueryData * | |
683 gaim_dnsquery_a(const char *hostname, int port, | |
684 GaimDnsQueryConnectFunction callback, gpointer data) | |
685 { | |
686 struct sockaddr_in sin; | |
687 pending_dns_request_t *req; | |
688 | |
689 if (!inet_aton(hostname, &sin.sin_addr)) { | |
690 struct hostent *hp; | 755 struct hostent *hp; |
691 if(!(hp = gethostbyname(hostname))) { | 756 if(!(hp = gethostbyname(query_data->hostname))) { |
692 gaim_debug_error("dns", | 757 char message[1024]; |
693 "gaim_gethostbyname(\"%s\", %d) failed: %d\n", | 758 g_snprintf(message, sizeof(message), _("Error resolving %s: %d"), |
694 hostname, port, h_errno); | 759 query_data->hostname, h_errno); |
695 return NULL; | 760 gaim_dnsquery_failed(query_data, message); |
761 return FALSE; | |
696 } | 762 } |
697 memset(&sin, 0, sizeof(struct sockaddr_in)); | 763 memset(&sin, 0, sizeof(struct sockaddr_in)); |
698 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); | 764 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); |
699 sin.sin_family = hp->h_addrtype; | 765 sin.sin_family = hp->h_addrtype; |
700 } else | 766 } else |
701 sin.sin_family = AF_INET; | 767 sin.sin_family = AF_INET; |
702 sin.sin_port = htons(port); | 768 sin.sin_port = htons(query_data->port); |
703 | 769 |
704 req = g_new(pending_dns_request_t, 1); | 770 hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin))); |
705 req->addr = (struct sockaddr*) g_memdup(&sin, sizeof(sin)); | 771 hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin))); |
706 req->addrlen = sizeof(sin); | 772 |
707 req->data = data; | 773 gaim_dnsquery_resolved(query_data, hosts); |
708 req->callback = callback; | 774 |
709 gaim_timeout_add(10, host_resolved, req); | 775 return FALSE; |
710 return (GaimDnsQueryData *)1; | 776 } |
777 | |
778 GaimDnsQueryData * | |
779 gaim_dnsquery_a(const char *hostname, int port, | |
780 GaimDnsQueryConnectFunction callback, gpointer data) | |
781 { | |
782 GaimDnsQueryData *query_data; | |
783 | |
784 g_return_val_if_fail(hostname != NULL, NULL); | |
785 g_return_val_if_fail(port != 0, NULL); | |
786 | |
787 query_data = g_new(GaimDnsQueryData, 1); | |
788 query_data->hostname = g_strdup(hostname); | |
789 g_strstrip(query_data->hostname); | |
790 query_data->port = port; | |
791 query_data->callback = callback; | |
792 query_data->data = data; | |
793 | |
794 /* Don't call the callback before returning */ | |
795 query_data->timeout = gaim_timeout_add(0, resolve_host, query_data); | |
796 | |
797 return query_data; | |
711 } | 798 } |
712 | 799 |
713 #endif /* not __unix__ or __APPLE__ or _WIN32 */ | 800 #endif /* not __unix__ or __APPLE__ or _WIN32 */ |
801 | |
802 void | |
803 gaim_dnsquery_destroy(GaimDnsQueryData *query_data) | |
804 { | |
805 #if defined(__unix__) || defined(__APPLE__) | |
806 if (query_data->resolver != NULL) | |
807 /* | |
808 * Ideally we would tell our resolver child to stop resolving | |
809 * shit and then we would add it back to the free_dns_children | |
810 * linked list. However, it's hard to tell children stuff, | |
811 * they just don't listen. | |
812 */ | |
813 gaim_dnsquery_resolver_destroy(query_data->resolver); | |
814 #elif defined _WIN32 /* end __unix__ || __APPLE__ */ | |
815 if (query_data->resolver != NULL) | |
816 { | |
817 /* | |
818 * It's not really possible to kill a thread. So instead we | |
819 * just set the callback to NULL and let the DNS lookup | |
820 * finish. | |
821 */ | |
822 query_data->callback = NULL; | |
823 return; | |
824 } | |
825 | |
826 while (query_data->hosts != NULL) | |
827 { | |
828 /* Discard the length... */ | |
829 query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data); | |
830 /* Free the address... */ | |
831 g_free(query_data->hosts->data); | |
832 query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data); | |
833 } | |
834 g_free(query_data->error_message); | |
835 #endif | |
836 | |
837 if (query_data->timeout > 0) | |
838 gaim_timeout_remove(query_data->timeout); | |
839 | |
840 g_free(query_data->hostname); | |
841 g_free(query_data); | |
842 } | |
843 | |
844 void | |
845 gaim_dnsquery_init(void) | |
846 { | |
847 #ifdef _WIN32 | |
848 if (!g_thread_supported()) | |
849 g_thread_init(NULL); | |
850 #endif | |
851 } | |
852 | |
853 void | |
854 gaim_dnsquery_uninit(void) | |
855 { | |
856 #if defined(__unix__) || defined(__APPLE__) | |
857 while (free_dns_children != NULL) | |
858 { | |
859 gaim_dnsquery_resolver_destroy(free_dns_children->data); | |
860 free_dns_children = g_slist_remove(free_dns_children, free_dns_children->data); | |
861 } | |
862 #endif | |
863 } |