Remove useless comparison
[pidgin-git.git] / libpurple / dnsquery.c
blob2cacb5c9281587990ea331b8c0b443839572fbb3
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 #define MAX_ADDR_RESPONSE_LEN 1048576
43 #if (defined(__APPLE__) || defined (__unix__)) && !defined(__osf__)
44 #define PURPLE_DNSQUERY_USE_FORK
45 #endif
46 /**************************************************************************
47 * DNS query API
48 **************************************************************************/
50 static PurpleDnsQueryUiOps *dns_query_ui_ops = NULL;
52 typedef struct _PurpleDnsQueryResolverProcess PurpleDnsQueryResolverProcess;
54 struct _PurpleDnsQueryData {
55 char *hostname;
56 int port;
57 PurpleDnsQueryConnectFunction callback;
58 gpointer data;
59 guint timeout;
60 PurpleAccount *account;
62 #if defined(PURPLE_DNSQUERY_USE_FORK)
63 PurpleDnsQueryResolverProcess *resolver;
64 #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */
65 GThread *resolver;
66 GSList *hosts;
67 gchar *error_message;
68 #endif
71 #if defined(PURPLE_DNSQUERY_USE_FORK)
73 #define MAX_DNS_CHILDREN 4
76 * This structure keeps a reference to a child resolver process.
78 struct _PurpleDnsQueryResolverProcess {
79 guint inpa;
80 int fd_in, fd_out;
81 pid_t dns_pid;
84 static GSList *free_dns_children = NULL;
85 /* TODO: Make me a GQueue when we require >= glib 2.4 */
86 static GSList *queued_requests = NULL;
88 static int number_of_dns_children = 0;
91 * This is a convenience struct used to pass data to
92 * the child resolver process.
94 typedef struct {
95 char hostname[512];
96 int port;
97 } dns_params_t;
98 #endif /* end PURPLE_DNSQUERY_USE_FORK */
100 static void
101 purple_dnsquery_resolved(PurpleDnsQueryData *query_data, GSList *hosts)
103 purple_debug_info("dnsquery", "IP resolved for %s\n", query_data->hostname);
104 if (query_data->callback != NULL)
105 query_data->callback(hosts, query_data->data, NULL);
106 else
109 * Callback is a required parameter, but it can get set to
110 * NULL if we cancel a thread-based DNS lookup. So we need
111 * to free hosts.
113 while (hosts != NULL)
115 hosts = g_slist_remove(hosts, hosts->data);
116 g_free(hosts->data);
117 hosts = g_slist_remove(hosts, hosts->data);
121 #ifdef PURPLE_DNSQUERY_USE_FORK
123 * Add the resolver to the list of available resolvers, and set it
124 * to NULL so that it doesn't get destroyed along with the query_data
126 if (query_data->resolver)
128 free_dns_children = g_slist_prepend(free_dns_children, query_data->resolver);
129 query_data->resolver = NULL;
131 #endif /* PURPLE_DNSQUERY_USE_FORK */
133 purple_dnsquery_destroy(query_data);
136 static void
137 purple_dnsquery_failed(PurpleDnsQueryData *query_data, const gchar *error_message)
139 purple_debug_error("dnsquery", "%s\n", error_message);
140 if (query_data->callback != NULL)
141 query_data->callback(NULL, query_data->data, error_message);
142 purple_dnsquery_destroy(query_data);
145 static gboolean
146 purple_dnsquery_ui_resolve(PurpleDnsQueryData *query_data)
148 PurpleDnsQueryUiOps *ops = purple_dnsquery_get_ui_ops();
150 if (ops && ops->resolve_host)
151 return ops->resolve_host(query_data, purple_dnsquery_resolved, purple_dnsquery_failed);
153 return FALSE;
156 static gboolean
157 resolve_ip(PurpleDnsQueryData *query_data)
159 #if defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST)
160 struct addrinfo hints, *res;
161 char servname[20];
163 g_snprintf(servname, sizeof(servname), "%d", query_data->port);
164 memset(&hints, 0, sizeof(hints));
165 hints.ai_family = AF_UNSPEC;
166 hints.ai_flags |= AI_NUMERICHOST;
168 if (0 == getaddrinfo(query_data->hostname, servname, &hints, &res))
170 GSList *hosts = NULL;
171 hosts = g_slist_append(hosts, GINT_TO_POINTER(res->ai_addrlen));
172 hosts = g_slist_append(hosts, g_memdup(res->ai_addr, res->ai_addrlen));
173 purple_dnsquery_resolved(query_data, hosts);
175 freeaddrinfo(res);
176 return TRUE;
178 #else /* defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST) */
179 struct sockaddr_in sin;
180 if (inet_aton(query_data->hostname, &sin.sin_addr))
183 * The given "hostname" is actually an IP address, so we
184 * don't need to do anything.
186 GSList *hosts = NULL;
187 sin.sin_family = AF_INET;
188 sin.sin_port = htons(query_data->port);
189 hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin)));
190 hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin)));
191 purple_dnsquery_resolved(query_data, hosts);
193 return TRUE;
195 #endif
197 return FALSE;
200 #ifdef USE_IDN
201 static gboolean
202 dns_str_is_ascii(const char *name)
204 guchar *c;
205 for (c = (guchar *)name; c && *c; ++c) {
206 if (*c > 0x7f)
207 return FALSE;
210 return TRUE;
212 #endif
214 #if defined(PURPLE_DNSQUERY_USE_FORK)
217 * Unix!
221 * Begin the DNS resolver child process functions.
223 #ifdef HAVE_SIGNAL_H
224 G_GNUC_NORETURN static void
225 trap_gdb_bug(int sig)
227 const char *message =
228 "Purple's DNS child got a SIGTRAP signal.\n"
229 "This can be caused by trying to run purple inside gdb.\n"
230 "There is a known gdb bug which prevents this. Supposedly purple\n"
231 "should have detected you were using gdb and used an ugly hack,\n"
232 "check cope_with_gdb_brokenness() in dnsquery.c.\n\n"
233 "For more info about this bug, see http://sources.redhat.com/ml/gdb/2001-07/msg00349.html\n";
234 fputs("\n* * *\n",stderr);
235 fputs(message,stderr);
236 fputs("* * *\n\n",stderr);
237 execlp("xmessage","xmessage","-center", message, NULL);
238 _exit(1);
240 #endif
242 static void
243 write_to_parent(int fd, const void *buf, size_t count)
245 ssize_t written;
247 written = write(fd, buf, count);
248 if (written < 0 || (gsize)written != count) {
249 if (written < 0)
250 fprintf(stderr, "dns[%d]: Error writing data to "
251 "parent: %s\n", getpid(), strerror(errno));
252 else
253 fprintf(stderr, "dns[%d]: Error: Tried to write %"
254 G_GSIZE_FORMAT " bytes to parent but instead "
255 "wrote %" G_GSIZE_FORMAT " bytes\n",
256 getpid(), count, written);
260 G_GNUC_NORETURN static void
261 purple_dnsquery_resolver_run(int child_out, int child_in, gboolean show_debug)
263 dns_params_t dns_params;
264 const size_t zero = 0;
265 int rc;
266 #ifdef HAVE_GETADDRINFO
267 struct addrinfo hints, *res, *tmp;
268 char servname[20];
269 #else
270 struct sockaddr_in sin;
271 const size_t addrlen = sizeof(sin);
272 #endif
273 char *hostname;
275 #ifdef HAVE_SIGNAL_H
276 purple_restore_default_signal_handlers();
277 signal(SIGTRAP, trap_gdb_bug);
278 #endif
281 * We resolve 1 host name for each iteration of this
282 * while loop.
284 * The top half of this reads in the hostname and port
285 * number from the socket with our parent. The bottom
286 * half of this resolves the IP (blocking) and sends
287 * the result back to our parent, when finished.
289 while (1) {
290 fd_set fds;
291 struct timeval tv = { .tv_sec = 20, .tv_usec = 0 };
292 FD_ZERO(&fds);
293 FD_SET(child_in, &fds);
294 rc = select(child_in + 1, &fds, NULL, NULL, &tv);
295 if (!rc) {
296 if (show_debug)
297 printf("dns[%d]: nobody needs me... =(\n", getpid());
298 break;
300 rc = read(child_in, &dns_params, sizeof(dns_params_t));
301 if (rc < 0) {
302 fprintf(stderr, "dns[%d]: Error: Could not read dns_params: "
303 "%s\n", getpid(), strerror(errno));
304 break;
306 if (rc == 0) {
307 if (show_debug)
308 printf("dns[%d]: Oops, father has gone, wait for me, wait...!\n", getpid());
309 _exit(0);
311 if (dns_params.hostname[0] == '\0') {
312 fprintf(stderr, "dns[%d]: Error: Parent requested resolution "
313 "of an empty hostname (port = %d)!!!\n", getpid(),
314 dns_params.port);
315 _exit(1);
318 #ifdef USE_IDN
319 if (!dns_str_is_ascii(dns_params.hostname)) {
320 rc = purple_network_convert_idn_to_ascii(dns_params.hostname, &hostname);
321 if (rc != 0) {
322 write_to_parent(child_out, &rc, sizeof(rc));
323 if (show_debug)
324 fprintf(stderr, "dns[%d] Error: IDN conversion returned "
325 "%d\n", getpid(), rc);
326 dns_params.hostname[0] = '\0';
327 break;
329 } else /* intentional to execute the g_strdup */
330 #endif
331 hostname = g_strdup(dns_params.hostname);
333 /* We have the hostname and port, now resolve the IP */
335 #ifdef HAVE_GETADDRINFO
336 g_snprintf(servname, sizeof(servname), "%d", dns_params.port);
337 memset(&hints, 0, sizeof(hints));
339 /* This is only used to convert a service
340 * name to a port number. As we know we are
341 * passing a number already, we know this
342 * value will not be really used by the C
343 * library.
345 hints.ai_socktype = SOCK_STREAM;
346 #ifdef AI_ADDRCONFIG
347 hints.ai_flags |= AI_ADDRCONFIG;
348 #endif /* AI_ADDRCONFIG */
349 rc = getaddrinfo(hostname, servname, &hints, &res);
350 write_to_parent(child_out, &rc, sizeof(rc));
351 if (rc != 0) {
352 if (show_debug)
353 printf("dns[%d] Error: getaddrinfo returned %d\n",
354 getpid(), rc);
355 dns_params.hostname[0] = '\0';
356 g_free(hostname);
357 hostname = NULL;
358 break;
360 tmp = res;
361 while (res) {
362 size_t ai_addrlen = res->ai_addrlen;
363 write_to_parent(child_out, &ai_addrlen, sizeof(ai_addrlen));
364 write_to_parent(child_out, res->ai_addr, res->ai_addrlen);
365 res = res->ai_next;
367 freeaddrinfo(tmp);
368 #else
369 struct hostent *hp;
370 if (!(hp = gethostbyname(hostname))) {
371 write_to_parent(child_out, &h_errno, sizeof(int));
372 close(child_out);
373 if (show_debug)
374 printf("DNS Error: %d\n", h_errno);
375 _exit(0);
377 memset(&sin, 0, sizeof(struct sockaddr_in));
378 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
379 sin.sin_family = hp->h_addrtype;
381 sin.sin_port = htons(dns_params.port);
382 rc = 0;
383 write_to_parent(child_out, &rc, sizeof(rc));
384 write_to_parent(child_out, &addrlen, sizeof(addrlen));
385 write_to_parent(child_out, &sin, addrlen);
386 #endif
387 write_to_parent(child_out, &zero, sizeof(zero));
388 dns_params.hostname[0] = '\0';
390 g_free(hostname);
391 hostname = NULL;
394 close(child_out);
395 close(child_in);
397 _exit(0);
400 * End the DNS resolver child process functions.
404 * Begin the functions for dealing with the DNS child processes.
406 static void
407 cope_with_gdb_brokenness(void)
409 #ifdef __linux__
410 static gboolean already_done = FALSE;
411 char s[256], e[512];
412 int n;
413 pid_t ppid;
415 if(already_done)
416 return;
417 already_done = TRUE;
418 ppid = getppid();
419 g_snprintf(s, sizeof(s), "/proc/%d/exe", ppid);
420 n = readlink(s, e, sizeof(e));
421 if(n < 0)
422 return;
424 e[MIN((gsize)n,sizeof(e)-1)] = '\0';
426 if(strstr(e,"gdb")) {
427 purple_debug_info("dns",
428 "Debugger detected, performing useless query...\n");
429 gethostbyname("x.x.x.x.x");
431 #endif
434 static void
435 purple_dnsquery_resolver_destroy(PurpleDnsQueryResolverProcess *resolver)
437 g_return_if_fail(resolver != NULL);
439 /* Keep this before the kill() call below. */
440 if (resolver->inpa != 0) {
441 purple_input_remove(resolver->inpa);
442 resolver->inpa = 0;
446 * We might as well attempt to kill our child process. It really
447 * doesn't matter if this fails, because children will expire on
448 * their own after a few seconds.
450 if (resolver->dns_pid > 0)
451 kill(resolver->dns_pid, SIGKILL);
453 close(resolver->fd_in);
454 close(resolver->fd_out);
456 g_free(resolver);
458 number_of_dns_children--;
461 static PurpleDnsQueryResolverProcess *
462 purple_dnsquery_resolver_new(gboolean show_debug)
464 PurpleDnsQueryResolverProcess *resolver;
465 int child_out[2], child_in[2];
467 /* Create pipes for communicating with the child process */
468 if (pipe(child_out) || pipe(child_in)) {
469 purple_debug_error("dns",
470 "Could not create pipes: %s\n", g_strerror(errno));
471 return NULL;
474 resolver = g_new(PurpleDnsQueryResolverProcess, 1);
475 resolver->inpa = 0;
477 cope_with_gdb_brokenness();
479 /* "Go fork and multiply." --Tommy Caldwell (Emily's dad, not the climber) */
480 resolver->dns_pid = fork();
482 /* If we are the child process... */
483 if (resolver->dns_pid == 0) {
484 /* We should not access the parent's side of the pipes, so close them */
485 close(child_out[0]);
486 close(child_in[1]);
488 purple_dnsquery_resolver_run(child_out[1], child_in[0], show_debug);
489 /* The thread calls _exit() rather than returning, so we never get here */
492 /* We should not access the child's side of the pipes, so close them */
493 close(child_out[1]);
494 close(child_in[0]);
495 if (resolver->dns_pid == -1) {
496 purple_debug_error("dns",
497 "Could not create child process for DNS: %s\n",
498 g_strerror(errno));
499 purple_dnsquery_resolver_destroy(resolver);
500 return NULL;
503 resolver->fd_out = child_out[0];
504 resolver->fd_in = child_in[1];
505 number_of_dns_children++;
506 purple_debug_info("dns",
507 "Created new DNS child %d, there are now %d children.\n",
508 resolver->dns_pid, number_of_dns_children);
510 return resolver;
514 * @return TRUE if the request was sent succesfully. FALSE
515 * if the request could not be sent. This isn't
516 * necessarily an error. If the child has expired,
517 * for example, we won't be able to send the message.
519 static gboolean
520 send_dns_request_to_child(PurpleDnsQueryData *query_data,
521 PurpleDnsQueryResolverProcess *resolver)
523 pid_t pid;
524 dns_params_t dns_params;
525 ssize_t rc;
527 /* This waitpid might return the child's PID if it has recently
528 * exited, or it might return an error if it exited "long
529 * enough" ago that it has already been reaped; in either
530 * instance, we can't use it. */
531 pid = waitpid(resolver->dns_pid, NULL, WNOHANG);
532 if (pid > 0) {
533 purple_debug_warning("dns", "DNS child %d no longer exists\n",
534 resolver->dns_pid);
535 purple_dnsquery_resolver_destroy(resolver);
536 return FALSE;
537 } else if (pid < 0) {
538 purple_debug_warning("dns", "Wait for DNS child %d failed: %s\n",
539 resolver->dns_pid, g_strerror(errno));
540 purple_dnsquery_resolver_destroy(resolver);
541 return FALSE;
544 /* Copy the hostname and port into a single data structure */
545 strncpy(dns_params.hostname, query_data->hostname, sizeof(dns_params.hostname) - 1);
546 dns_params.hostname[sizeof(dns_params.hostname) - 1] = '\0';
547 dns_params.port = query_data->port;
549 /* Send the data structure to the child */
550 rc = write(resolver->fd_in, &dns_params, sizeof(dns_params));
551 if (rc < 0) {
552 purple_debug_error("dns", "Unable to write to DNS child %d: %s\n",
553 resolver->dns_pid, g_strerror(errno));
554 purple_dnsquery_resolver_destroy(resolver);
555 return FALSE;
557 if ((gsize)rc < sizeof(dns_params)) {
558 purple_debug_error("dns", "Tried to write %" G_GSSIZE_FORMAT
559 " bytes to child but only wrote %" G_GSSIZE_FORMAT "\n",
560 sizeof(dns_params), rc);
561 purple_dnsquery_resolver_destroy(resolver);
562 return FALSE;
565 purple_debug_info("dns",
566 "Successfully sent DNS request to child %d\n",
567 resolver->dns_pid);
569 query_data->resolver = resolver;
571 return TRUE;
574 static void host_resolved(gpointer data, gint source, PurpleInputCondition cond);
576 static void
577 handle_next_queued_request(void)
579 PurpleDnsQueryData *query_data;
580 PurpleDnsQueryResolverProcess *resolver;
582 if (queued_requests == NULL)
583 /* No more DNS queries, yay! */
584 return;
586 query_data = queued_requests->data;
587 queued_requests = g_slist_delete_link(queued_requests, queued_requests);
590 * If we have any children, attempt to have them perform the DNS
591 * query. If we're able to send the query then resolver will be
592 * set to the PurpleDnsQueryResolverProcess. Otherwise, resolver
593 * will be NULL and we'll need to create a new DNS request child.
595 while (free_dns_children != NULL)
597 resolver = free_dns_children->data;
598 free_dns_children = g_slist_remove(free_dns_children, resolver);
600 if (send_dns_request_to_child(query_data, resolver))
601 /* We found an acceptable child, yay */
602 break;
605 /* We need to create a new DNS request child */
606 if (query_data->resolver == NULL)
608 if (number_of_dns_children >= MAX_DNS_CHILDREN)
610 /* Apparently all our children are busy */
611 queued_requests = g_slist_prepend(queued_requests, query_data);
612 return;
615 resolver = purple_dnsquery_resolver_new(purple_debug_is_enabled());
616 if (resolver == NULL)
618 purple_dnsquery_failed(query_data, _("Unable to create new resolver process\n"));
619 return;
621 if (!send_dns_request_to_child(query_data, resolver))
623 purple_dnsquery_failed(query_data, _("Unable to send request to resolver process\n"));
624 return;
628 query_data->resolver->inpa = purple_input_add(query_data->resolver->fd_out,
629 PURPLE_INPUT_READ, host_resolved, query_data);
633 * End the functions for dealing with the DNS child processes.
636 static void
637 host_resolved(gpointer data, gint source, PurpleInputCondition cond)
639 PurpleDnsQueryData *query_data;
640 int rc, err;
641 GSList *hosts = NULL;
642 struct sockaddr *addr = NULL;
643 size_t addrlen;
644 char message[1024];
646 query_data = data;
648 purple_debug_info("dns", "Got response for '%s'\n", query_data->hostname);
649 purple_input_remove(query_data->resolver->inpa);
650 query_data->resolver->inpa = 0;
652 rc = read(query_data->resolver->fd_out, &err, sizeof(err));
653 if ((rc == 4) && (err != 0))
655 #ifdef HAVE_GETADDRINFO
656 g_snprintf(message, sizeof(message), _("Error resolving %s:\n%s"),
657 query_data->hostname, purple_gai_strerror(err));
658 #else
659 g_snprintf(message, sizeof(message), _("Error resolving %s: %d"),
660 query_data->hostname, err);
661 #endif
662 /* Re-read resolv.conf and friends in case DNS servers have changed */
663 res_init();
665 purple_dnsquery_failed(query_data, message);
666 } else if (rc > 0) {
667 /* Success! */
668 while (rc > 0) {
669 rc = read(query_data->resolver->fd_out, &addrlen, sizeof(addrlen));
670 if (rc > 0 && addrlen > 0 && addrlen < MAX_ADDR_RESPONSE_LEN) {
671 addr = g_malloc(addrlen);
672 rc = read(query_data->resolver->fd_out, addr, addrlen);
673 hosts = g_slist_append(hosts, GINT_TO_POINTER(addrlen));
674 hosts = g_slist_append(hosts, addr);
675 } else {
676 break;
679 /* wait4(resolver->dns_pid, NULL, WNOHANG, NULL); */
680 purple_dnsquery_resolved(query_data, hosts);
682 } else if (rc == -1) {
683 g_snprintf(message, sizeof(message), _("Error reading from resolver process:\n%s"), g_strerror(errno));
684 purple_dnsquery_failed(query_data, message);
686 } else if (rc == 0) {
687 g_snprintf(message, sizeof(message), _("Resolver process exited without answering our request"));
688 purple_dnsquery_failed(query_data, message);
691 handle_next_queued_request();
694 static void
695 resolve_host(PurpleDnsQueryData *query_data)
697 queued_requests = g_slist_append(queued_requests, query_data);
699 handle_next_queued_request();
702 #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */
705 * Windows!
708 static gboolean
709 dns_main_thread_cb(gpointer data)
711 PurpleDnsQueryData *query_data = data;
713 /* We're done, so purple_dnsquery_destroy() shouldn't think it is canceling an in-progress lookup */
714 query_data->resolver = NULL;
716 if (query_data->error_message != NULL)
717 purple_dnsquery_failed(query_data, query_data->error_message);
718 else
720 GSList *hosts;
722 /* We don't want purple_dns_query_resolved() to free(hosts) */
723 hosts = query_data->hosts;
724 query_data->hosts = NULL;
725 purple_dnsquery_resolved(query_data, hosts);
728 return FALSE;
731 static gpointer
732 dns_thread(gpointer data)
734 PurpleDnsQueryData *query_data;
735 #ifdef HAVE_GETADDRINFO
736 int rc;
737 struct addrinfo hints, *res, *tmp;
738 char servname[20];
739 #else
740 struct sockaddr_in sin;
741 struct hostent *hp;
742 #endif
743 char *hostname;
745 query_data = data;
747 #ifdef USE_IDN
748 if (!dns_str_is_ascii(query_data->hostname)) {
749 rc = purple_network_convert_idn_to_ascii(query_data->hostname, &hostname);
750 if (rc != 0) {
751 query_data->error_message = g_strdup_printf(_("Error converting %s "
752 "to punycode: %d"), query_data->hostname, rc);
753 /* back to main thread */
754 purple_timeout_add(0, dns_main_thread_cb, query_data);
755 return 0;
757 } else /* intentional fallthru */
758 #endif
759 hostname = g_strdup(query_data->hostname);
761 #ifdef HAVE_GETADDRINFO
762 g_snprintf(servname, sizeof(servname), "%d", query_data->port);
763 memset(&hints,0,sizeof(hints));
766 * This is only used to convert a service
767 * name to a port number. As we know we are
768 * passing a number already, we know this
769 * value will not be really used by the C
770 * library.
772 hints.ai_socktype = SOCK_STREAM;
773 #ifdef AI_ADDRCONFIG
774 hints.ai_flags |= AI_ADDRCONFIG;
775 #endif /* AI_ADDRCONFIG */
776 if ((rc = getaddrinfo(hostname, servname, &hints, &res)) == 0) {
777 tmp = res;
778 while(res) {
779 query_data->hosts = g_slist_append(query_data->hosts,
780 GSIZE_TO_POINTER(res->ai_addrlen));
781 query_data->hosts = g_slist_append(query_data->hosts,
782 g_memdup(res->ai_addr, res->ai_addrlen));
783 res = res->ai_next;
785 freeaddrinfo(tmp);
786 } else {
787 query_data->error_message = g_strdup_printf(_("Error resolving %s:\n%s"), query_data->hostname, purple_gai_strerror(rc));
789 #else
790 if ((hp = gethostbyname(hostname))) {
791 memset(&sin, 0, sizeof(struct sockaddr_in));
792 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
793 sin.sin_family = hp->h_addrtype;
794 sin.sin_port = htons(query_data->port);
796 query_data->hosts = g_slist_append(query_data->hosts,
797 GSIZE_TO_POINTER(sizeof(sin)));
798 query_data->hosts = g_slist_append(query_data->hosts,
799 g_memdup(&sin, sizeof(sin)));
800 } else {
801 query_data->error_message = g_strdup_printf(_("Error resolving %s: %d"), query_data->hostname, h_errno);
803 #endif
804 g_free(hostname);
806 /* back to main thread */
807 purple_timeout_add(0, dns_main_thread_cb, query_data);
809 return 0;
812 static void
813 resolve_host(PurpleDnsQueryData *query_data)
815 GError *err = NULL;
818 * Spin off a separate thread to perform the DNS lookup so
819 * that we don't block the UI.
821 query_data->resolver = g_thread_create(dns_thread,
822 query_data, FALSE, &err);
823 if (query_data->resolver == NULL)
825 char message[1024];
826 g_snprintf(message, sizeof(message), _("Thread creation failure: %s"),
827 (err && err->message) ? err->message : _("Unknown reason"));
828 g_error_free(err);
829 purple_dnsquery_failed(query_data, message);
833 #else /* not PURPLE_DNSQUERY_USE_FORK or _WIN32 */
836 * We weren't able to do anything fancier above, so use the
837 * fail-safe name resolution code, which is blocking.
840 static void
841 resolve_host(PurpleDnsQueryData *query_data)
843 struct sockaddr_in sin;
844 GSList *hosts = NULL;
845 struct hostent *hp;
846 gchar *hostname;
847 #ifdef USE_IDN
848 if (!dns_str_is_ascii(query_data->hostname)) {
849 int ret = purple_network_convert_idn_to_ascii(query_data->hostname,
850 &hostname);
851 if (ret != 0) {
852 char message[1024];
853 g_snprintf(message, sizeof(message), _("Error resolving %s: %d"),
854 query_data->hostname, ret);
855 purple_dnsquery_failed(query_data, message);
856 return;
858 } else /* fallthrough is intentional to the g_strdup */
859 #endif
860 hostname = g_strdup(query_data->hostname);
862 if(!(hp = gethostbyname(hostname))) {
863 char message[1024];
864 g_snprintf(message, sizeof(message), _("Error resolving %s: %d"),
865 query_data->hostname, h_errno);
866 purple_dnsquery_failed(query_data, message);
867 g_free(hostname);
868 return;
870 memset(&sin, 0, sizeof(struct sockaddr_in));
871 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
872 sin.sin_family = hp->h_addrtype;
873 g_free(hostname);
874 sin.sin_port = htons(query_data->port);
876 hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin)));
877 hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin)));
879 purple_dnsquery_resolved(query_data, hosts);
882 #endif /* not PURPLE_DNSQUERY_USE_FORK or _WIN32 */
884 static gboolean
885 initiate_resolving(gpointer data)
887 PurpleDnsQueryData *query_data;
888 PurpleProxyType proxy_type;
890 query_data = data;
891 query_data->timeout = 0;
893 if (resolve_ip(query_data))
894 /* resolve_ip calls purple_dnsquery_resolved */
895 return FALSE;
897 proxy_type = purple_proxy_info_get_type(
898 purple_proxy_get_setup(query_data->account));
899 if (proxy_type == PURPLE_PROXY_TOR) {
900 purple_dnsquery_failed(query_data,
901 _("Aborting DNS lookup in Tor Proxy mode."));
902 return FALSE;
905 if (purple_dnsquery_ui_resolve(query_data))
906 /* The UI is handling the resolve; we're done */
907 return FALSE;
909 resolve_host(query_data);
911 return FALSE;
914 PurpleDnsQueryData *
915 purple_dnsquery_a_account(PurpleAccount *account, const char *hostname, int port,
916 PurpleDnsQueryConnectFunction callback, gpointer data)
918 PurpleDnsQueryData *query_data;
920 g_return_val_if_fail(hostname != NULL, NULL);
921 g_return_val_if_fail(port != 0, NULL);
922 g_return_val_if_fail(callback != NULL, NULL);
924 purple_debug_info("dnsquery", "Performing DNS lookup for %s\n", hostname);
926 query_data = g_new0(PurpleDnsQueryData, 1);
927 query_data->hostname = g_strdup(hostname);
928 g_strstrip(query_data->hostname);
929 query_data->port = port;
930 query_data->callback = callback;
931 query_data->data = data;
932 query_data->account = account;
934 if (*query_data->hostname == '\0')
936 purple_dnsquery_destroy(query_data);
937 g_return_val_if_reached(NULL);
940 query_data->timeout = purple_timeout_add(0, initiate_resolving, query_data);
942 return query_data;
945 PurpleDnsQueryData *
946 purple_dnsquery_a(const char *hostname, int port,
947 PurpleDnsQueryConnectFunction callback, gpointer data)
949 return purple_dnsquery_a_account(NULL, hostname, port, callback, data);
952 void
953 purple_dnsquery_destroy(PurpleDnsQueryData *query_data)
955 PurpleDnsQueryUiOps *ops = purple_dnsquery_get_ui_ops();
957 if (ops && ops->destroy)
958 ops->destroy(query_data);
960 #if defined(PURPLE_DNSQUERY_USE_FORK)
961 queued_requests = g_slist_remove(queued_requests, query_data);
963 if (query_data->resolver != NULL)
965 * This is only non-NULL when we're cancelling an in-progress
966 * query. Ideally we would tell our resolver child to stop
967 * resolving shit and then we would add it back to the
968 * free_dns_children linked list. However, it's hard to tell
969 * children stuff, they just don't listen. So we'll just
970 * kill the process and allow a new child to be started if we
971 * have more stuff to resolve.
973 purple_dnsquery_resolver_destroy(query_data->resolver);
974 #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */
975 if (query_data->resolver != NULL)
978 * It's not really possible to kill a thread. So instead we
979 * just set the callback to NULL and let the DNS lookup
980 * finish.
982 query_data->callback = NULL;
983 return;
986 while (query_data->hosts != NULL)
988 /* Discard the length... */
989 query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data);
990 /* Free the address... */
991 g_free(query_data->hosts->data);
992 query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data);
994 g_free(query_data->error_message);
995 #endif /* end _WIN32 */
997 if (query_data->timeout > 0)
998 purple_timeout_remove(query_data->timeout);
1000 g_free(query_data->hostname);
1001 g_free(query_data);
1004 char *
1005 purple_dnsquery_get_host(PurpleDnsQueryData *query_data)
1007 g_return_val_if_fail(query_data != NULL, NULL);
1009 return query_data->hostname;
1012 unsigned short
1013 purple_dnsquery_get_port(PurpleDnsQueryData *query_data)
1015 g_return_val_if_fail(query_data != NULL, 0);
1017 return query_data->port;
1020 void
1021 purple_dnsquery_set_ui_ops(PurpleDnsQueryUiOps *ops)
1023 dns_query_ui_ops = ops;
1026 PurpleDnsQueryUiOps *
1027 purple_dnsquery_get_ui_ops(void)
1029 /* It is perfectly acceptable for dns_query_ui_ops to be NULL; this just
1030 * means that the default platform-specific implementation will be used.
1032 return dns_query_ui_ops;
1035 void
1036 purple_dnsquery_init(void)
1040 void
1041 purple_dnsquery_uninit(void)
1043 #if defined(PURPLE_DNSQUERY_USE_FORK)
1044 while (free_dns_children != NULL)
1046 purple_dnsquery_resolver_destroy(free_dns_children->data);
1047 free_dns_children = g_slist_remove(free_dns_children, free_dns_children->data);
1049 #endif /* end PURPLE_DNSQUERY_USE_FORK */