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 }