Increment version number
[pidgin-git.git] / libpurple / dnsquery.c
blob18246bc56b2fa213a2640668f3cace1f9aedaeaf
1 /**
2 * @file dnsquery.c DNS query API
3 * @ingroup core
4 */
6 /* purple
8 * Purple is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #define _PURPLE_DNSQUERY_C_
29 #include "internal.h"
30 #include "debug.h"
31 #include "dnsquery.h"
32 #include "network.h"
33 #include "notify.h"
34 #include "prefs.h"
35 #include "util.h"
37 #ifndef _WIN32
38 #include <resolv.h>
39 #endif
41 #if (defined(__APPLE__) || defined (__unix__)) && !defined(__osf__)
42 #define PURPLE_DNSQUERY_USE_FORK
43 #endif
44 /**************************************************************************
45 * DNS query API
46 **************************************************************************/
48 static PurpleDnsQueryUiOps *dns_query_ui_ops = NULL;
50 typedef struct _PurpleDnsQueryResolverProcess PurpleDnsQueryResolverProcess;
52 struct _PurpleDnsQueryData {
53 char *hostname;
54 int port;
55 PurpleDnsQueryConnectFunction callback;
56 gpointer data;
57 guint timeout;
58 PurpleAccount *account;
60 #if defined(PURPLE_DNSQUERY_USE_FORK)
61 PurpleDnsQueryResolverProcess *resolver;
62 #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */
63 GThread *resolver;
64 GSList *hosts;
65 gchar *error_message;
66 #endif
69 #if defined(PURPLE_DNSQUERY_USE_FORK)
71 #define MAX_DNS_CHILDREN 4
74 * This structure keeps a reference to a child resolver process.
76 struct _PurpleDnsQueryResolverProcess {
77 guint inpa;
78 int fd_in, fd_out;
79 pid_t dns_pid;
82 static GSList *free_dns_children = NULL;
83 /* TODO: Make me a GQueue when we require >= glib 2.4 */
84 static GSList *queued_requests = NULL;
86 static int number_of_dns_children = 0;
89 * This is a convenience struct used to pass data to
90 * the child resolver process.
92 typedef struct {
93 char hostname[512];
94 int port;
95 } dns_params_t;
96 #endif /* end PURPLE_DNSQUERY_USE_FORK */
98 static void
99 purple_dnsquery_resolved(PurpleDnsQueryData *query_data, GSList *hosts)
101 purple_debug_info("dnsquery", "IP resolved for %s\n", query_data->hostname);
102 if (query_data->callback != NULL)
103 query_data->callback(hosts, query_data->data, NULL);
104 else
107 * Callback is a required parameter, but it can get set to
108 * NULL if we cancel a thread-based DNS lookup. So we need
109 * to free hosts.
111 while (hosts != NULL)
113 hosts = g_slist_remove(hosts, hosts->data);
114 g_free(hosts->data);
115 hosts = g_slist_remove(hosts, hosts->data);
119 #ifdef PURPLE_DNSQUERY_USE_FORK
121 * Add the resolver to the list of available resolvers, and set it
122 * to NULL so that it doesn't get destroyed along with the query_data
124 if (query_data->resolver)
126 free_dns_children = g_slist_prepend(free_dns_children, query_data->resolver);
127 query_data->resolver = NULL;
129 #endif /* PURPLE_DNSQUERY_USE_FORK */
131 purple_dnsquery_destroy(query_data);
134 static void
135 purple_dnsquery_failed(PurpleDnsQueryData *query_data, const gchar *error_message)
137 purple_debug_error("dnsquery", "%s\n", error_message);
138 if (query_data->callback != NULL)
139 query_data->callback(NULL, query_data->data, error_message);
140 purple_dnsquery_destroy(query_data);
143 static gboolean
144 purple_dnsquery_ui_resolve(PurpleDnsQueryData *query_data)
146 PurpleDnsQueryUiOps *ops = purple_dnsquery_get_ui_ops();
148 if (ops && ops->resolve_host)
149 return ops->resolve_host(query_data, purple_dnsquery_resolved, purple_dnsquery_failed);
151 return FALSE;
154 static gboolean
155 resolve_ip(PurpleDnsQueryData *query_data)
157 #if defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST)
158 struct addrinfo hints, *res;
159 char servname[20];
161 g_snprintf(servname, sizeof(servname), "%d", query_data->port);
162 memset(&hints, 0, sizeof(hints));
163 hints.ai_family = AF_UNSPEC;
164 hints.ai_flags |= AI_NUMERICHOST;
166 if (0 == getaddrinfo(query_data->hostname, servname, &hints, &res))
168 GSList *hosts = NULL;
169 hosts = g_slist_append(hosts, GINT_TO_POINTER(res->ai_addrlen));
170 hosts = g_slist_append(hosts, g_memdup(res->ai_addr, res->ai_addrlen));
171 purple_dnsquery_resolved(query_data, hosts);
173 freeaddrinfo(res);
174 return TRUE;
176 #else /* defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST) */
177 struct sockaddr_in sin;
178 if (inet_aton(query_data->hostname, &sin.sin_addr))
181 * The given "hostname" is actually an IP address, so we
182 * don't need to do anything.
184 GSList *hosts = NULL;
185 sin.sin_family = AF_INET;
186 sin.sin_port = htons(query_data->port);
187 hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin)));
188 hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin)));
189 purple_dnsquery_resolved(query_data, hosts);
191 return TRUE;
193 #endif
195 return FALSE;
198 #ifdef USE_IDN
199 static gboolean
200 dns_str_is_ascii(const char *name)
202 guchar *c;
203 for (c = (guchar *)name; c && *c; ++c) {
204 if (*c > 0x7f)
205 return FALSE;
208 return TRUE;
210 #endif
212 #if defined(PURPLE_DNSQUERY_USE_FORK)
215 * Unix!
219 * Begin the DNS resolver child process functions.
221 #ifdef HAVE_SIGNAL_H
222 G_GNUC_NORETURN static void
223 trap_gdb_bug(int sig)
225 const char *message =
226 "Purple's DNS child got a SIGTRAP signal.\n"
227 "This can be caused by trying to run purple inside gdb.\n"
228 "There is a known gdb bug which prevents this. Supposedly purple\n"
229 "should have detected you were using gdb and used an ugly hack,\n"
230 "check cope_with_gdb_brokenness() in dnsquery.c.\n\n"
231 "For more info about this bug, see http://sources.redhat.com/ml/gdb/2001-07/msg00349.html\n";
232 fputs("\n* * *\n",stderr);
233 fputs(message,stderr);
234 fputs("* * *\n\n",stderr);
235 execlp("xmessage","xmessage","-center", message, NULL);
236 _exit(1);
238 #endif
240 static void
241 write_to_parent(int fd, const void *buf, size_t count)
243 ssize_t written;
245 written = write(fd, buf, count);
246 if (written != count) {
247 if (written < 0)
248 fprintf(stderr, "dns[%d]: Error writing data to "
249 "parent: %s\n", getpid(), strerror(errno));
250 else
251 fprintf(stderr, "dns[%d]: Error: Tried to write %"
252 G_GSIZE_FORMAT " bytes to parent but instead "
253 "wrote %" G_GSIZE_FORMAT " bytes\n",
254 getpid(), count, written);
258 G_GNUC_NORETURN static void
259 purple_dnsquery_resolver_run(int child_out, int child_in, gboolean show_debug)
261 dns_params_t dns_params;
262 const size_t zero = 0;
263 int rc;
264 #ifdef HAVE_GETADDRINFO
265 struct addrinfo hints, *res, *tmp;
266 char servname[20];
267 #else
268 struct sockaddr_in sin;
269 const size_t addrlen = sizeof(sin);
270 #endif
271 char *hostname;
273 #ifdef HAVE_SIGNAL_H
274 purple_restore_default_signal_handlers();
275 signal(SIGTRAP, trap_gdb_bug);
276 #endif
279 * We resolve 1 host name for each iteration of this
280 * while loop.
282 * The top half of this reads in the hostname and port
283 * number from the socket with our parent. The bottom
284 * half of this resolves the IP (blocking) and sends
285 * the result back to our parent, when finished.
287 while (1) {
288 fd_set fds;
289 struct timeval tv = { .tv_sec = 20, .tv_usec = 0 };
290 FD_ZERO(&fds);
291 FD_SET(child_in, &fds);
292 rc = select(child_in + 1, &fds, NULL, NULL, &tv);
293 if (!rc) {
294 if (show_debug)
295 printf("dns[%d]: nobody needs me... =(\n", getpid());
296 break;
298 rc = read(child_in, &dns_params, sizeof(dns_params_t));
299 if (rc < 0) {
300 fprintf(stderr, "dns[%d]: Error: Could not read dns_params: "
301 "%s\n", getpid(), strerror(errno));
302 break;
304 if (rc == 0) {
305 if (show_debug)
306 printf("dns[%d]: Oops, father has gone, wait for me, wait...!\n", getpid());
307 _exit(0);
309 if (dns_params.hostname[0] == '\0') {
310 fprintf(stderr, "dns[%d]: Error: Parent requested resolution "
311 "of an empty hostname (port = %d)!!!\n", getpid(),
312 dns_params.port);
313 _exit(1);
316 #ifdef USE_IDN
317 if (!dns_str_is_ascii(dns_params.hostname)) {
318 rc = purple_network_convert_idn_to_ascii(dns_params.hostname, &hostname);
319 if (rc != 0) {
320 write_to_parent(child_out, &rc, sizeof(rc));
321 if (show_debug)
322 fprintf(stderr, "dns[%d] Error: IDN conversion returned "
323 "%d\n", getpid(), rc);
324 dns_params.hostname[0] = '\0';
325 break;
327 } else /* intentional to execute the g_strdup */
328 #endif
329 hostname = g_strdup(dns_params.hostname);
331 /* We have the hostname and port, now resolve the IP */
333 #ifdef HAVE_GETADDRINFO
334 g_snprintf(servname, sizeof(servname), "%d", dns_params.port);
335 memset(&hints, 0, sizeof(hints));
337 /* This is only used to convert a service
338 * name to a port number. As we know we are
339 * passing a number already, we know this
340 * value will not be really used by the C
341 * library.
343 hints.ai_socktype = SOCK_STREAM;
344 #ifdef AI_ADDRCONFIG
345 hints.ai_flags |= AI_ADDRCONFIG;
346 #endif /* AI_ADDRCONFIG */
347 rc = getaddrinfo(hostname, servname, &hints, &res);
348 write_to_parent(child_out, &rc, sizeof(rc));
349 if (rc != 0) {
350 if (show_debug)
351 printf("dns[%d] Error: getaddrinfo returned %d\n",
352 getpid(), rc);
353 dns_params.hostname[0] = '\0';
354 g_free(hostname);
355 hostname = NULL;
356 break;
358 tmp = res;
359 while (res) {
360 size_t ai_addrlen = res->ai_addrlen;
361 write_to_parent(child_out, &ai_addrlen, sizeof(ai_addrlen));
362 write_to_parent(child_out, res->ai_addr, res->ai_addrlen);
363 res = res->ai_next;
365 freeaddrinfo(tmp);
366 #else
367 struct hostent *hp;
368 if (!(hp = gethostbyname(hostname))) {
369 write_to_parent(child_out, &h_errno, sizeof(int));
370 close(child_out);
371 if (show_debug)
372 printf("DNS Error: %d\n", h_errno);
373 _exit(0);
375 memset(&sin, 0, sizeof(struct sockaddr_in));
376 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
377 sin.sin_family = hp->h_addrtype;
379 sin.sin_port = htons(dns_params.port);
380 rc = 0;
381 write_to_parent(child_out, &rc, sizeof(rc));
382 write_to_parent(child_out, &addrlen, sizeof(addrlen));
383 write_to_parent(child_out, &sin, addrlen);
384 #endif
385 write_to_parent(child_out, &zero, sizeof(zero));
386 dns_params.hostname[0] = '\0';
388 g_free(hostname);
389 hostname = NULL;
392 close(child_out);
393 close(child_in);
395 _exit(0);
398 * End the DNS resolver child process functions.
402 * Begin the functions for dealing with the DNS child processes.
404 static void
405 cope_with_gdb_brokenness(void)
407 #ifdef __linux__
408 static gboolean already_done = FALSE;
409 char s[256], e[512];
410 int n;
411 pid_t ppid;
413 if(already_done)
414 return;
415 already_done = TRUE;
416 ppid = getppid();
417 g_snprintf(s, sizeof(s), "/proc/%d/exe", ppid);
418 n = readlink(s, e, sizeof(e));
419 if(n < 0)
420 return;
422 e[MIN(n,sizeof(e)-1)] = '\0';
424 if(strstr(e,"gdb")) {
425 purple_debug_info("dns",
426 "Debugger detected, performing useless query...\n");
427 gethostbyname("x.x.x.x.x");
429 #endif
432 static void
433 purple_dnsquery_resolver_destroy(PurpleDnsQueryResolverProcess *resolver)
435 g_return_if_fail(resolver != NULL);
437 /* Keep this before the kill() call below. */
438 if (resolver->inpa != 0) {
439 purple_input_remove(resolver->inpa);
440 resolver->inpa = 0;
444 * We might as well attempt to kill our child process. It really
445 * doesn't matter if this fails, because children will expire on
446 * their own after a few seconds.
448 if (resolver->dns_pid > 0)
449 kill(resolver->dns_pid, SIGKILL);
451 close(resolver->fd_in);
452 close(resolver->fd_out);
454 g_free(resolver);
456 number_of_dns_children--;
459 static PurpleDnsQueryResolverProcess *
460 purple_dnsquery_resolver_new(gboolean show_debug)
462 PurpleDnsQueryResolverProcess *resolver;
463 int child_out[2], child_in[2];
465 /* Create pipes for communicating with the child process */
466 if (pipe(child_out) || pipe(child_in)) {
467 purple_debug_error("dns",
468 "Could not create pipes: %s\n", g_strerror(errno));
469 return NULL;
472 resolver = g_new(PurpleDnsQueryResolverProcess, 1);
473 resolver->inpa = 0;
475 cope_with_gdb_brokenness();
477 /* "Go fork and multiply." --Tommy Caldwell (Emily's dad, not the climber) */
478 resolver->dns_pid = fork();
480 /* If we are the child process... */
481 if (resolver->dns_pid == 0) {
482 /* We should not access the parent's side of the pipes, so close them */
483 close(child_out[0]);
484 close(child_in[1]);
486 purple_dnsquery_resolver_run(child_out[1], child_in[0], show_debug);
487 /* The thread calls _exit() rather than returning, so we never get here */
490 /* We should not access the child's side of the pipes, so close them */
491 close(child_out[1]);
492 close(child_in[0]);
493 if (resolver->dns_pid == -1) {
494 purple_debug_error("dns",
495 "Could not create child process for DNS: %s\n",
496 g_strerror(errno));
497 purple_dnsquery_resolver_destroy(resolver);
498 return NULL;
501 resolver->fd_out = child_out[0];
502 resolver->fd_in = child_in[1];
503 number_of_dns_children++;
504 purple_debug_info("dns",
505 "Created new DNS child %d, there are now %d children.\n",
506 resolver->dns_pid, number_of_dns_children);
508 return resolver;
512 * @return TRUE if the request was sent succesfully. FALSE
513 * if the request could not be sent. This isn't
514 * necessarily an error. If the child has expired,
515 * for example, we won't be able to send the message.
517 static gboolean
518 send_dns_request_to_child(PurpleDnsQueryData *query_data,
519 PurpleDnsQueryResolverProcess *resolver)
521 pid_t pid;
522 dns_params_t dns_params;
523 ssize_t rc;
525 /* This waitpid might return the child's PID if it has recently
526 * exited, or it might return an error if it exited "long
527 * enough" ago that it has already been reaped; in either
528 * instance, we can't use it. */
529 pid = waitpid(resolver->dns_pid, NULL, WNOHANG);
530 if (pid > 0) {
531 purple_debug_warning("dns", "DNS child %d no longer exists\n",
532 resolver->dns_pid);
533 purple_dnsquery_resolver_destroy(resolver);
534 return FALSE;
535 } else if (pid < 0) {
536 purple_debug_warning("dns", "Wait for DNS child %d failed: %s\n",
537 resolver->dns_pid, g_strerror(errno));
538 purple_dnsquery_resolver_destroy(resolver);
539 return FALSE;
542 /* Copy the hostname and port into a single data structure */
543 strncpy(dns_params.hostname, query_data->hostname, sizeof(dns_params.hostname) - 1);
544 dns_params.hostname[sizeof(dns_params.hostname) - 1] = '\0';
545 dns_params.port = query_data->port;
547 /* Send the data structure to the child */
548 rc = write(resolver->fd_in, &dns_params, sizeof(dns_params));
549 if (rc < 0) {
550 purple_debug_error("dns", "Unable to write to DNS child %d: %s\n",
551 resolver->dns_pid, g_strerror(errno));
552 purple_dnsquery_resolver_destroy(resolver);
553 return FALSE;
555 if (rc < sizeof(dns_params)) {
556 purple_debug_error("dns", "Tried to write %" G_GSSIZE_FORMAT
557 " bytes to child but only wrote %" G_GSSIZE_FORMAT "\n",
558 sizeof(dns_params), rc);
559 purple_dnsquery_resolver_destroy(resolver);
560 return FALSE;
563 purple_debug_info("dns",
564 "Successfully sent DNS request to child %d\n",
565 resolver->dns_pid);
567 query_data->resolver = resolver;
569 return TRUE;
572 static void host_resolved(gpointer data, gint source, PurpleInputCondition cond);
574 static void
575 handle_next_queued_request(void)
577 PurpleDnsQueryData *query_data;
578 PurpleDnsQueryResolverProcess *resolver;
580 if (queued_requests == NULL)
581 /* No more DNS queries, yay! */
582 return;
584 query_data = queued_requests->data;
585 queued_requests = g_slist_delete_link(queued_requests, queued_requests);
588 * If we have any children, attempt to have them perform the DNS
589 * query. If we're able to send the query then resolver will be
590 * set to the PurpleDnsQueryResolverProcess. Otherwise, resolver
591 * will be NULL and we'll need to create a new DNS request child.
593 while (free_dns_children != NULL)
595 resolver = free_dns_children->data;
596 free_dns_children = g_slist_remove(free_dns_children, resolver);
598 if (send_dns_request_to_child(query_data, resolver))
599 /* We found an acceptable child, yay */
600 break;
603 /* We need to create a new DNS request child */
604 if (query_data->resolver == NULL)
606 if (number_of_dns_children >= MAX_DNS_CHILDREN)
608 /* Apparently all our children are busy */
609 queued_requests = g_slist_prepend(queued_requests, query_data);
610 return;
613 resolver = purple_dnsquery_resolver_new(purple_debug_is_enabled());
614 if (resolver == NULL)
616 purple_dnsquery_failed(query_data, _("Unable to create new resolver process\n"));
617 return;
619 if (!send_dns_request_to_child(query_data, resolver))
621 purple_dnsquery_failed(query_data, _("Unable to send request to resolver process\n"));
622 return;
626 query_data->resolver->inpa = purple_input_add(query_data->resolver->fd_out,
627 PURPLE_INPUT_READ, host_resolved, query_data);
631 * End the functions for dealing with the DNS child processes.
634 static void
635 host_resolved(gpointer data, gint source, PurpleInputCondition cond)
637 PurpleDnsQueryData *query_data;
638 int rc, err;
639 GSList *hosts = NULL;
640 struct sockaddr *addr = NULL;
641 size_t addrlen;
642 char message[1024];
644 query_data = data;
646 purple_debug_info("dns", "Got response for '%s'\n", query_data->hostname);
647 purple_input_remove(query_data->resolver->inpa);
648 query_data->resolver->inpa = 0;
650 rc = read(query_data->resolver->fd_out, &err, sizeof(err));
651 if ((rc == 4) && (err != 0))
653 #ifdef HAVE_GETADDRINFO
654 g_snprintf(message, sizeof(message), _("Error resolving %s:\n%s"),
655 query_data->hostname, purple_gai_strerror(err));
656 #else
657 g_snprintf(message, sizeof(message), _("Error resolving %s: %d"),
658 query_data->hostname, err);
659 #endif
660 /* Re-read resolv.conf and friends in case DNS servers have changed */
661 res_init();
663 purple_dnsquery_failed(query_data, message);
664 } else if (rc > 0) {
665 /* Success! */
666 while (rc > 0) {
667 rc = read(query_data->resolver->fd_out, &addrlen, sizeof(addrlen));
668 if (rc > 0 && addrlen > 0) {
669 addr = g_malloc(addrlen);
670 rc = read(query_data->resolver->fd_out, addr, addrlen);
671 hosts = g_slist_append(hosts, GINT_TO_POINTER(addrlen));
672 hosts = g_slist_append(hosts, addr);
673 } else {
674 break;
677 /* wait4(resolver->dns_pid, NULL, WNOHANG, NULL); */
678 purple_dnsquery_resolved(query_data, hosts);
680 } else if (rc == -1) {
681 g_snprintf(message, sizeof(message), _("Error reading from resolver process:\n%s"), g_strerror(errno));
682 purple_dnsquery_failed(query_data, message);
684 } else if (rc == 0) {
685 g_snprintf(message, sizeof(message), _("Resolver process exited without answering our request"));
686 purple_dnsquery_failed(query_data, message);
689 handle_next_queued_request();
692 static void
693 resolve_host(PurpleDnsQueryData *query_data)
695 queued_requests = g_slist_append(queued_requests, query_data);
697 handle_next_queued_request();
700 #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */
703 * Windows!
706 static gboolean
707 dns_main_thread_cb(gpointer data)
709 PurpleDnsQueryData *query_data = data;
711 /* We're done, so purple_dnsquery_destroy() shouldn't think it is canceling an in-progress lookup */
712 query_data->resolver = NULL;
714 if (query_data->error_message != NULL)
715 purple_dnsquery_failed(query_data, query_data->error_message);
716 else
718 GSList *hosts;
720 /* We don't want purple_dns_query_resolved() to free(hosts) */
721 hosts = query_data->hosts;
722 query_data->hosts = NULL;
723 purple_dnsquery_resolved(query_data, hosts);
726 return FALSE;
729 static gpointer
730 dns_thread(gpointer data)
732 PurpleDnsQueryData *query_data;
733 #ifdef HAVE_GETADDRINFO
734 int rc;
735 struct addrinfo hints, *res, *tmp;
736 char servname[20];
737 #else
738 struct sockaddr_in sin;
739 struct hostent *hp;
740 #endif
741 char *hostname;
743 query_data = data;
745 #ifdef USE_IDN
746 if (!dns_str_is_ascii(query_data->hostname)) {
747 rc = purple_network_convert_idn_to_ascii(query_data->hostname, &hostname);
748 if (rc != 0) {
749 query_data->error_message = g_strdup_printf(_("Error converting %s "
750 "to punycode: %d"), query_data->hostname, rc);
751 /* back to main thread */
752 purple_timeout_add(0, dns_main_thread_cb, query_data);
753 return 0;
755 } else /* intentional fallthru */
756 #endif
757 hostname = g_strdup(query_data->hostname);
759 #ifdef HAVE_GETADDRINFO
760 g_snprintf(servname, sizeof(servname), "%d", query_data->port);
761 memset(&hints,0,sizeof(hints));
764 * This is only used to convert a service
765 * name to a port number. As we know we are
766 * passing a number already, we know this
767 * value will not be really used by the C
768 * library.
770 hints.ai_socktype = SOCK_STREAM;
771 #ifdef AI_ADDRCONFIG
772 hints.ai_flags |= AI_ADDRCONFIG;
773 #endif /* AI_ADDRCONFIG */
774 if ((rc = getaddrinfo(hostname, servname, &hints, &res)) == 0) {
775 tmp = res;
776 while(res) {
777 query_data->hosts = g_slist_append(query_data->hosts,
778 GSIZE_TO_POINTER(res->ai_addrlen));
779 query_data->hosts = g_slist_append(query_data->hosts,
780 g_memdup(res->ai_addr, res->ai_addrlen));
781 res = res->ai_next;
783 freeaddrinfo(tmp);
784 } else {
785 query_data->error_message = g_strdup_printf(_("Error resolving %s:\n%s"), query_data->hostname, purple_gai_strerror(rc));
787 #else
788 if ((hp = gethostbyname(hostname))) {
789 memset(&sin, 0, sizeof(struct sockaddr_in));
790 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
791 sin.sin_family = hp->h_addrtype;
792 sin.sin_port = htons(query_data->port);
794 query_data->hosts = g_slist_append(query_data->hosts,
795 GSIZE_TO_POINTER(sizeof(sin)));
796 query_data->hosts = g_slist_append(query_data->hosts,
797 g_memdup(&sin, sizeof(sin)));
798 } else {
799 query_data->error_message = g_strdup_printf(_("Error resolving %s: %d"), query_data->hostname, h_errno);
801 #endif
802 g_free(hostname);
804 /* back to main thread */
805 purple_timeout_add(0, dns_main_thread_cb, query_data);
807 return 0;
810 static void
811 resolve_host(PurpleDnsQueryData *query_data)
813 GError *err = NULL;
816 * Spin off a separate thread to perform the DNS lookup so
817 * that we don't block the UI.
819 query_data->resolver = g_thread_create(dns_thread,
820 query_data, FALSE, &err);
821 if (query_data->resolver == NULL)
823 char message[1024];
824 g_snprintf(message, sizeof(message), _("Thread creation failure: %s"),
825 (err && err->message) ? err->message : _("Unknown reason"));
826 g_error_free(err);
827 purple_dnsquery_failed(query_data, message);
831 #else /* not PURPLE_DNSQUERY_USE_FORK or _WIN32 */
834 * We weren't able to do anything fancier above, so use the
835 * fail-safe name resolution code, which is blocking.
838 static void
839 resolve_host(PurpleDnsQueryData *query_data)
841 struct sockaddr_in sin;
842 GSList *hosts = NULL;
843 struct hostent *hp;
844 gchar *hostname;
845 #ifdef USE_IDN
846 if (!dns_str_is_ascii(query_data->hostname)) {
847 int ret = purple_network_convert_idn_to_ascii(query_data->hostname,
848 &hostname);
849 if (ret != 0) {
850 char message[1024];
851 g_snprintf(message, sizeof(message), _("Error resolving %s: %d"),
852 query_data->hostname, ret);
853 purple_dnsquery_failed(query_data, message);
854 return;
856 } else /* fallthrough is intentional to the g_strdup */
857 #endif
858 hostname = g_strdup(query_data->hostname);
860 if(!(hp = gethostbyname(hostname))) {
861 char message[1024];
862 g_snprintf(message, sizeof(message), _("Error resolving %s: %d"),
863 query_data->hostname, h_errno);
864 purple_dnsquery_failed(query_data, message);
865 g_free(hostname);
866 return;
868 memset(&sin, 0, sizeof(struct sockaddr_in));
869 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
870 sin.sin_family = hp->h_addrtype;
871 g_free(hostname);
872 sin.sin_port = htons(query_data->port);
874 hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin)));
875 hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin)));
877 purple_dnsquery_resolved(query_data, hosts);
880 #endif /* not PURPLE_DNSQUERY_USE_FORK or _WIN32 */
882 static gboolean
883 initiate_resolving(gpointer data)
885 PurpleDnsQueryData *query_data;
886 PurpleProxyType proxy_type;
888 query_data = data;
889 query_data->timeout = 0;
891 if (resolve_ip(query_data))
892 /* resolve_ip calls purple_dnsquery_resolved */
893 return FALSE;
895 proxy_type = purple_proxy_info_get_type(
896 purple_proxy_get_setup(query_data->account));
897 if (proxy_type == PURPLE_PROXY_TOR) {
898 purple_dnsquery_failed(query_data,
899 _("Aborting DNS lookup in Tor Proxy mode."));
900 return FALSE;
903 if (purple_dnsquery_ui_resolve(query_data))
904 /* The UI is handling the resolve; we're done */
905 return FALSE;
907 resolve_host(query_data);
909 return FALSE;
912 PurpleDnsQueryData *
913 purple_dnsquery_a_account(PurpleAccount *account, const char *hostname, int port,
914 PurpleDnsQueryConnectFunction callback, gpointer data)
916 PurpleDnsQueryData *query_data;
918 g_return_val_if_fail(hostname != NULL, NULL);
919 g_return_val_if_fail(port != 0, NULL);
920 g_return_val_if_fail(callback != NULL, NULL);
922 purple_debug_info("dnsquery", "Performing DNS lookup for %s\n", hostname);
924 query_data = g_new0(PurpleDnsQueryData, 1);
925 query_data->hostname = g_strdup(hostname);
926 g_strstrip(query_data->hostname);
927 query_data->port = port;
928 query_data->callback = callback;
929 query_data->data = data;
930 query_data->account = account;
932 if (*query_data->hostname == '\0')
934 purple_dnsquery_destroy(query_data);
935 g_return_val_if_reached(NULL);
938 query_data->timeout = purple_timeout_add(0, initiate_resolving, query_data);
940 return query_data;
943 PurpleDnsQueryData *
944 purple_dnsquery_a(const char *hostname, int port,
945 PurpleDnsQueryConnectFunction callback, gpointer data)
947 return purple_dnsquery_a_account(NULL, hostname, port, callback, data);
950 void
951 purple_dnsquery_destroy(PurpleDnsQueryData *query_data)
953 PurpleDnsQueryUiOps *ops = purple_dnsquery_get_ui_ops();
955 if (ops && ops->destroy)
956 ops->destroy(query_data);
958 #if defined(PURPLE_DNSQUERY_USE_FORK)
959 queued_requests = g_slist_remove(queued_requests, query_data);
961 if (query_data->resolver != NULL)
963 * This is only non-NULL when we're cancelling an in-progress
964 * query. Ideally we would tell our resolver child to stop
965 * resolving shit and then we would add it back to the
966 * free_dns_children linked list. However, it's hard to tell
967 * children stuff, they just don't listen. So we'll just
968 * kill the process and allow a new child to be started if we
969 * have more stuff to resolve.
971 purple_dnsquery_resolver_destroy(query_data->resolver);
972 #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */
973 if (query_data->resolver != NULL)
976 * It's not really possible to kill a thread. So instead we
977 * just set the callback to NULL and let the DNS lookup
978 * finish.
980 query_data->callback = NULL;
981 return;
984 while (query_data->hosts != NULL)
986 /* Discard the length... */
987 query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data);
988 /* Free the address... */
989 g_free(query_data->hosts->data);
990 query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data);
992 g_free(query_data->error_message);
993 #endif /* end _WIN32 */
995 if (query_data->timeout > 0)
996 purple_timeout_remove(query_data->timeout);
998 g_free(query_data->hostname);
999 g_free(query_data);
1002 char *
1003 purple_dnsquery_get_host(PurpleDnsQueryData *query_data)
1005 g_return_val_if_fail(query_data != NULL, NULL);
1007 return query_data->hostname;
1010 unsigned short
1011 purple_dnsquery_get_port(PurpleDnsQueryData *query_data)
1013 g_return_val_if_fail(query_data != NULL, 0);
1015 return query_data->port;
1018 void
1019 purple_dnsquery_set_ui_ops(PurpleDnsQueryUiOps *ops)
1021 dns_query_ui_ops = ops;
1024 PurpleDnsQueryUiOps *
1025 purple_dnsquery_get_ui_ops(void)
1027 /* It is perfectly acceptable for dns_query_ui_ops to be NULL; this just
1028 * means that the default platform-specific implementation will be used.
1030 return dns_query_ui_ops;
1033 void
1034 purple_dnsquery_init(void)
1038 void
1039 purple_dnsquery_uninit(void)
1041 #if defined(PURPLE_DNSQUERY_USE_FORK)
1042 while (free_dns_children != NULL)
1044 purple_dnsquery_resolver_destroy(free_dns_children->data);
1045 free_dns_children = g_slist_remove(free_dns_children, free_dns_children->data);
1047 #endif /* end PURPLE_DNSQUERY_USE_FORK */