These changes are reuired to make the Windows installer build.
[pidgin-git.git] / libpurple / dnsquery.c
blob4e0ed4687d4579bfd7f678413ffcb8f119006683
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
28 #include "internal.h"
29 #include "debug.h"
30 #include "dnsquery.h"
31 #include "network.h"
32 #include "notify.h"
33 #include "prefs.h"
34 #include "util.h"
36 #ifndef _WIN32
37 #include <resolv.h>
38 #endif
40 #if (defined(__APPLE__) || defined (__unix__)) && !defined(__osf__)
41 #define PURPLE_DNSQUERY_USE_FORK
42 #endif
43 /**************************************************************************
44 * DNS query API
45 **************************************************************************/
47 static PurpleDnsQueryUiOps *dns_query_ui_ops = NULL;
49 typedef struct _PurpleDnsQueryResolverProcess PurpleDnsQueryResolverProcess;
51 struct _PurpleDnsQueryData {
52 char *hostname;
53 int port;
54 PurpleDnsQueryConnectFunction callback;
55 gpointer data;
56 guint timeout;
58 #if defined(PURPLE_DNSQUERY_USE_FORK)
59 PurpleDnsQueryResolverProcess *resolver;
60 #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */
61 GThread *resolver;
62 GSList *hosts;
63 gchar *error_message;
64 #endif
67 #if defined(PURPLE_DNSQUERY_USE_FORK)
69 #define MAX_DNS_CHILDREN 4
72 * This structure keeps a reference to a child resolver process.
74 struct _PurpleDnsQueryResolverProcess {
75 guint inpa;
76 int fd_in, fd_out;
77 pid_t dns_pid;
80 static GSList *free_dns_children = NULL;
81 /* TODO: Make me a GQueue when we require >= glib 2.4 */
82 static GSList *queued_requests = NULL;
84 static int number_of_dns_children = 0;
87 * This is a convenience struct used to pass data to
88 * the child resolver process.
90 typedef struct {
91 char hostname[512];
92 int port;
93 } dns_params_t;
94 #endif /* end PURPLE_DNSQUERY_USE_FORK */
96 static void
97 purple_dnsquery_resolved(PurpleDnsQueryData *query_data, GSList *hosts)
99 purple_debug_info("dnsquery", "IP resolved for %s\n", query_data->hostname);
100 if (query_data->callback != NULL)
101 query_data->callback(hosts, query_data->data, NULL);
102 else
105 * Callback is a required parameter, but it can get set to
106 * NULL if we cancel a thread-based DNS lookup. So we need
107 * to free hosts.
109 while (hosts != NULL)
111 hosts = g_slist_remove(hosts, hosts->data);
112 g_free(hosts->data);
113 hosts = g_slist_remove(hosts, hosts->data);
117 #ifdef PURPLE_DNSQUERY_USE_FORK
119 * Add the resolver to the list of available resolvers, and set it
120 * to NULL so that it doesn't get destroyed along with the query_data
122 if (query_data->resolver)
124 free_dns_children = g_slist_prepend(free_dns_children, query_data->resolver);
125 query_data->resolver = NULL;
127 #endif /* PURPLE_DNSQUERY_USE_FORK */
129 purple_dnsquery_destroy(query_data);
132 static void
133 purple_dnsquery_failed(PurpleDnsQueryData *query_data, const gchar *error_message)
135 purple_debug_error("dnsquery", "%s\n", error_message);
136 if (query_data->callback != NULL)
137 query_data->callback(NULL, query_data->data, error_message);
138 purple_dnsquery_destroy(query_data);
141 static gboolean
142 purple_dnsquery_ui_resolve(PurpleDnsQueryData *query_data)
144 PurpleDnsQueryUiOps *ops = purple_dnsquery_get_ui_ops();
146 if (ops && ops->resolve_host)
147 return ops->resolve_host(query_data, purple_dnsquery_resolved, purple_dnsquery_failed);
149 return FALSE;
152 static gboolean
153 resolve_ip(PurpleDnsQueryData *query_data)
155 struct sockaddr_in sin;
156 if (inet_aton(query_data->hostname, &sin.sin_addr))
159 * The given "hostname" is actually an IP address, so we
160 * don't need to do anything.
162 GSList *hosts = NULL;
163 sin.sin_family = AF_INET;
164 sin.sin_port = htons(query_data->port);
165 hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin)));
166 hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin)));
167 purple_dnsquery_resolved(query_data, hosts);
169 return TRUE;
172 return FALSE;
175 #ifdef USE_IDN
176 static gboolean
177 dns_str_is_ascii(const char *name)
179 guchar *c;
180 for (c = (guchar *)name; c && *c; ++c) {
181 if (*c > 0x7f)
182 return FALSE;
185 return TRUE;
187 #endif
189 #if defined(PURPLE_DNSQUERY_USE_FORK)
192 * Unix!
196 * Begin the DNS resolver child process functions.
198 #ifdef HAVE_SIGNAL_H
199 G_GNUC_NORETURN static void
200 trap_gdb_bug(int sig)
202 const char *message =
203 "Purple's DNS child got a SIGTRAP signal.\n"
204 "This can be caused by trying to run purple inside gdb.\n"
205 "There is a known gdb bug which prevents this. Supposedly purple\n"
206 "should have detected you were using gdb and used an ugly hack,\n"
207 "check cope_with_gdb_brokenness() in dnsquery.c.\n\n"
208 "For more info about this bug, see http://sources.redhat.com/ml/gdb/2001-07/msg00349.html\n";
209 fputs("\n* * *\n",stderr);
210 fputs(message,stderr);
211 fputs("* * *\n\n",stderr);
212 execlp("xmessage","xmessage","-center", message, NULL);
213 _exit(1);
215 #endif
217 static void
218 write_to_parent(int fd, const void *buf, size_t count)
220 ssize_t written;
222 written = write(fd, buf, count);
223 if (written != count) {
224 if (written < 0)
225 fprintf(stderr, "dns[%d]: Error writing data to "
226 "parent: %s\n", getpid(), strerror(errno));
227 else
228 fprintf(stderr, "dns[%d]: Error: Tried to write %"
229 G_GSIZE_FORMAT " bytes to parent but instead "
230 "wrote %" G_GSIZE_FORMAT " bytes\n",
231 getpid(), count, written);
235 G_GNUC_NORETURN static void
236 purple_dnsquery_resolver_run(int child_out, int child_in, gboolean show_debug)
238 dns_params_t dns_params;
239 const size_t zero = 0;
240 int rc;
241 #ifdef HAVE_GETADDRINFO
242 struct addrinfo hints, *res, *tmp;
243 char servname[20];
244 #else
245 struct sockaddr_in sin;
246 const size_t addrlen = sizeof(sin);
247 #endif
248 char *hostname;
250 #ifdef HAVE_SIGNAL_H
251 purple_restore_default_signal_handlers();
252 signal(SIGTRAP, trap_gdb_bug);
253 #endif
256 * We resolve 1 host name for each iteration of this
257 * while loop.
259 * The top half of this reads in the hostname and port
260 * number from the socket with our parent. The bottom
261 * half of this resolves the IP (blocking) and sends
262 * the result back to our parent, when finished.
264 while (1) {
265 fd_set fds;
266 struct timeval tv = { .tv_sec = 20, .tv_usec = 0 };
267 FD_ZERO(&fds);
268 FD_SET(child_in, &fds);
269 rc = select(child_in + 1, &fds, NULL, NULL, &tv);
270 if (!rc) {
271 if (show_debug)
272 printf("dns[%d]: nobody needs me... =(\n", getpid());
273 break;
275 rc = read(child_in, &dns_params, sizeof(dns_params_t));
276 if (rc < 0) {
277 fprintf(stderr, "dns[%d]: Error: Could not read dns_params: "
278 "%s\n", getpid(), strerror(errno));
279 break;
281 if (rc == 0) {
282 if (show_debug)
283 printf("dns[%d]: Oops, father has gone, wait for me, wait...!\n", getpid());
284 _exit(0);
286 if (dns_params.hostname[0] == '\0') {
287 fprintf(stderr, "dns[%d]: Error: Parent requested resolution "
288 "of an empty hostname (port = %d)!!!\n", getpid(),
289 dns_params.port);
290 _exit(1);
293 #ifdef USE_IDN
294 if (!dns_str_is_ascii(dns_params.hostname)) {
295 rc = purple_network_convert_idn_to_ascii(dns_params.hostname, &hostname);
296 if (rc != 0) {
297 write_to_parent(child_out, &rc, sizeof(rc));
298 if (show_debug)
299 fprintf(stderr, "dns[%d] Error: IDN conversion returned "
300 "%d\n", getpid(), rc);
301 dns_params.hostname[0] = '\0';
302 break;
304 } else /* intentional to execute the g_strdup */
305 #endif
306 hostname = g_strdup(dns_params.hostname);
308 /* We have the hostname and port, now resolve the IP */
310 #ifdef HAVE_GETADDRINFO
311 g_snprintf(servname, sizeof(servname), "%d", dns_params.port);
312 memset(&hints, 0, sizeof(hints));
314 /* This is only used to convert a service
315 * name to a port number. As we know we are
316 * passing a number already, we know this
317 * value will not be really used by the C
318 * library.
320 hints.ai_socktype = SOCK_STREAM;
321 #ifdef AI_ADDRCONFIG
322 hints.ai_flags |= AI_ADDRCONFIG;
323 #endif /* AI_ADDRCONFIG */
324 rc = getaddrinfo(hostname, servname, &hints, &res);
325 write_to_parent(child_out, &rc, sizeof(rc));
326 if (rc != 0) {
327 if (show_debug)
328 printf("dns[%d] Error: getaddrinfo returned %d\n",
329 getpid(), rc);
330 dns_params.hostname[0] = '\0';
331 g_free(hostname);
332 hostname = NULL;
333 break;
335 tmp = res;
336 while (res) {
337 size_t ai_addrlen = res->ai_addrlen;
338 write_to_parent(child_out, &ai_addrlen, sizeof(ai_addrlen));
339 write_to_parent(child_out, res->ai_addr, res->ai_addrlen);
340 res = res->ai_next;
342 freeaddrinfo(tmp);
343 #else
344 if (!inet_aton(hostname, &sin.sin_addr)) {
345 struct hostent *hp;
346 if (!(hp = gethostbyname(hostname))) {
347 write_to_parent(child_out, &h_errno, sizeof(int));
348 close(child_out);
349 if (show_debug)
350 printf("DNS Error: %d\n", h_errno);
351 _exit(0);
353 memset(&sin, 0, sizeof(struct sockaddr_in));
354 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
355 sin.sin_family = hp->h_addrtype;
356 } else
357 sin.sin_family = AF_INET;
359 sin.sin_port = htons(dns_params.port);
360 rc = 0;
361 write_to_parent(child_out, &rc, sizeof(rc));
362 write_to_parent(child_out, &addrlen, sizeof(addrlen));
363 write_to_parent(child_out, &sin, addrlen);
364 #endif
365 write_to_parent(child_out, &zero, sizeof(zero));
366 dns_params.hostname[0] = '\0';
368 g_free(hostname);
369 hostname = NULL;
372 close(child_out);
373 close(child_in);
375 _exit(0);
378 * End the DNS resolver child process functions.
382 * Begin the functions for dealing with the DNS child processes.
384 static void
385 cope_with_gdb_brokenness(void)
387 #ifdef __linux__
388 static gboolean already_done = FALSE;
389 char s[256], e[512];
390 int n;
391 pid_t ppid;
393 if(already_done)
394 return;
395 already_done = TRUE;
396 ppid = getppid();
397 g_snprintf(s, sizeof(s), "/proc/%d/exe", ppid);
398 n = readlink(s, e, sizeof(e));
399 if(n < 0)
400 return;
402 e[MIN(n,sizeof(e)-1)] = '\0';
404 if(strstr(e,"gdb")) {
405 purple_debug_info("dns",
406 "Debugger detected, performing useless query...\n");
407 gethostbyname("x.x.x.x.x");
409 #endif
412 static void
413 purple_dnsquery_resolver_destroy(PurpleDnsQueryResolverProcess *resolver)
415 g_return_if_fail(resolver != NULL);
417 /* Keep this before the kill() call below. */
418 if (resolver->inpa != 0) {
419 purple_input_remove(resolver->inpa);
420 resolver->inpa = 0;
424 * We might as well attempt to kill our child process. It really
425 * doesn't matter if this fails, because children will expire on
426 * their own after a few seconds.
428 if (resolver->dns_pid > 0)
429 kill(resolver->dns_pid, SIGKILL);
431 close(resolver->fd_in);
432 close(resolver->fd_out);
434 g_free(resolver);
436 number_of_dns_children--;
439 static PurpleDnsQueryResolverProcess *
440 purple_dnsquery_resolver_new(gboolean show_debug)
442 PurpleDnsQueryResolverProcess *resolver;
443 int child_out[2], child_in[2];
445 /* Create pipes for communicating with the child process */
446 if (pipe(child_out) || pipe(child_in)) {
447 purple_debug_error("dns",
448 "Could not create pipes: %s\n", g_strerror(errno));
449 return NULL;
452 resolver = g_new(PurpleDnsQueryResolverProcess, 1);
453 resolver->inpa = 0;
455 cope_with_gdb_brokenness();
457 /* "Go fork and multiply." --Tommy Caldwell (Emily's dad, not the climber) */
458 resolver->dns_pid = fork();
460 /* If we are the child process... */
461 if (resolver->dns_pid == 0) {
462 /* We should not access the parent's side of the pipes, so close them */
463 close(child_out[0]);
464 close(child_in[1]);
466 purple_dnsquery_resolver_run(child_out[1], child_in[0], show_debug);
467 /* The thread calls _exit() rather than returning, so we never get here */
470 /* We should not access the child's side of the pipes, so close them */
471 close(child_out[1]);
472 close(child_in[0]);
473 if (resolver->dns_pid == -1) {
474 purple_debug_error("dns",
475 "Could not create child process for DNS: %s\n",
476 g_strerror(errno));
477 purple_dnsquery_resolver_destroy(resolver);
478 return NULL;
481 resolver->fd_out = child_out[0];
482 resolver->fd_in = child_in[1];
483 number_of_dns_children++;
484 purple_debug_info("dns",
485 "Created new DNS child %d, there are now %d children.\n",
486 resolver->dns_pid, number_of_dns_children);
488 return resolver;
492 * @return TRUE if the request was sent succesfully. FALSE
493 * if the request could not be sent. This isn't
494 * necessarily an error. If the child has expired,
495 * for example, we won't be able to send the message.
497 static gboolean
498 send_dns_request_to_child(PurpleDnsQueryData *query_data,
499 PurpleDnsQueryResolverProcess *resolver)
501 pid_t pid;
502 dns_params_t dns_params;
503 ssize_t rc;
505 /* This waitpid might return the child's PID if it has recently
506 * exited, or it might return an error if it exited "long
507 * enough" ago that it has already been reaped; in either
508 * instance, we can't use it. */
509 pid = waitpid(resolver->dns_pid, NULL, WNOHANG);
510 if (pid > 0) {
511 purple_debug_warning("dns", "DNS child %d no longer exists\n",
512 resolver->dns_pid);
513 purple_dnsquery_resolver_destroy(resolver);
514 return FALSE;
515 } else if (pid < 0) {
516 purple_debug_warning("dns", "Wait for DNS child %d failed: %s\n",
517 resolver->dns_pid, g_strerror(errno));
518 purple_dnsquery_resolver_destroy(resolver);
519 return FALSE;
522 /* Copy the hostname and port into a single data structure */
523 strncpy(dns_params.hostname, query_data->hostname, sizeof(dns_params.hostname) - 1);
524 dns_params.hostname[sizeof(dns_params.hostname) - 1] = '\0';
525 dns_params.port = query_data->port;
527 /* Send the data structure to the child */
528 rc = write(resolver->fd_in, &dns_params, sizeof(dns_params));
529 if (rc < 0) {
530 purple_debug_error("dns", "Unable to write to DNS child %d: %s\n",
531 resolver->dns_pid, g_strerror(errno));
532 purple_dnsquery_resolver_destroy(resolver);
533 return FALSE;
535 if (rc < sizeof(dns_params)) {
536 purple_debug_error("dns", "Tried to write %" G_GSSIZE_FORMAT
537 " bytes to child but only wrote %" G_GSSIZE_FORMAT "\n",
538 sizeof(dns_params), rc);
539 purple_dnsquery_resolver_destroy(resolver);
540 return FALSE;
543 purple_debug_info("dns",
544 "Successfully sent DNS request to child %d\n",
545 resolver->dns_pid);
547 query_data->resolver = resolver;
549 return TRUE;
552 static void host_resolved(gpointer data, gint source, PurpleInputCondition cond);
554 static void
555 handle_next_queued_request(void)
557 PurpleDnsQueryData *query_data;
558 PurpleDnsQueryResolverProcess *resolver;
560 if (queued_requests == NULL)
561 /* No more DNS queries, yay! */
562 return;
564 query_data = queued_requests->data;
565 queued_requests = g_slist_delete_link(queued_requests, queued_requests);
568 * If we have any children, attempt to have them perform the DNS
569 * query. If we're able to send the query then resolver will be
570 * set to the PurpleDnsQueryResolverProcess. Otherwise, resolver
571 * will be NULL and we'll need to create a new DNS request child.
573 while (free_dns_children != NULL)
575 resolver = free_dns_children->data;
576 free_dns_children = g_slist_remove(free_dns_children, resolver);
578 if (send_dns_request_to_child(query_data, resolver))
579 /* We found an acceptable child, yay */
580 break;
583 /* We need to create a new DNS request child */
584 if (query_data->resolver == NULL)
586 if (number_of_dns_children >= MAX_DNS_CHILDREN)
588 /* Apparently all our children are busy */
589 queued_requests = g_slist_prepend(queued_requests, query_data);
590 return;
593 resolver = purple_dnsquery_resolver_new(purple_debug_is_enabled());
594 if (resolver == NULL)
596 purple_dnsquery_failed(query_data, _("Unable to create new resolver process\n"));
597 return;
599 if (!send_dns_request_to_child(query_data, resolver))
601 purple_dnsquery_failed(query_data, _("Unable to send request to resolver process\n"));
602 return;
606 query_data->resolver->inpa = purple_input_add(query_data->resolver->fd_out,
607 PURPLE_INPUT_READ, host_resolved, query_data);
611 * End the functions for dealing with the DNS child processes.
614 static void
615 host_resolved(gpointer data, gint source, PurpleInputCondition cond)
617 PurpleDnsQueryData *query_data;
618 int rc, err;
619 GSList *hosts = NULL;
620 struct sockaddr *addr = NULL;
621 size_t addrlen;
622 char message[1024];
624 query_data = data;
626 purple_debug_info("dns", "Got response for '%s'\n", query_data->hostname);
627 purple_input_remove(query_data->resolver->inpa);
628 query_data->resolver->inpa = 0;
630 rc = read(query_data->resolver->fd_out, &err, sizeof(err));
631 if ((rc == 4) && (err != 0))
633 #ifdef HAVE_GETADDRINFO
634 g_snprintf(message, sizeof(message), _("Error resolving %s:\n%s"),
635 query_data->hostname, purple_gai_strerror(err));
636 #else
637 g_snprintf(message, sizeof(message), _("Error resolving %s: %d"),
638 query_data->hostname, err);
639 #endif
640 /* Re-read resolv.conf and friends in case DNS servers have changed */
641 res_init();
643 purple_dnsquery_failed(query_data, message);
644 } else if (rc > 0) {
645 /* Success! */
646 while (rc > 0) {
647 rc = read(query_data->resolver->fd_out, &addrlen, sizeof(addrlen));
648 if (rc > 0 && addrlen > 0) {
649 addr = g_malloc(addrlen);
650 rc = read(query_data->resolver->fd_out, addr, addrlen);
651 hosts = g_slist_append(hosts, GINT_TO_POINTER(addrlen));
652 hosts = g_slist_append(hosts, addr);
653 } else {
654 break;
657 /* wait4(resolver->dns_pid, NULL, WNOHANG, NULL); */
658 purple_dnsquery_resolved(query_data, hosts);
660 } else if (rc == -1) {
661 g_snprintf(message, sizeof(message), _("Error reading from resolver process:\n%s"), g_strerror(errno));
662 purple_dnsquery_failed(query_data, message);
664 } else if (rc == 0) {
665 g_snprintf(message, sizeof(message), _("Resolver process exited without answering our request"));
666 purple_dnsquery_failed(query_data, message);
669 handle_next_queued_request();
672 static gboolean
673 resolve_host(gpointer data)
675 PurpleDnsQueryData *query_data;
677 query_data = data;
678 query_data->timeout = 0;
680 if (resolve_ip(query_data))
682 /* resolve_ip calls purple_dnsquery_resolved */
683 return FALSE;
686 if (purple_dnsquery_ui_resolve(query_data))
688 /* The UI is handling the resolve; we're done */
689 return FALSE;
692 queued_requests = g_slist_append(queued_requests, query_data);
694 handle_next_queued_request();
696 return FALSE;
699 PurpleDnsQueryData *
700 purple_dnsquery_a(const char *hostname, int port,
701 PurpleDnsQueryConnectFunction callback, gpointer data)
703 PurpleDnsQueryData *query_data;
705 g_return_val_if_fail(hostname != NULL, NULL);
706 g_return_val_if_fail(port != 0, NULL);
707 g_return_val_if_fail(callback != NULL, NULL);
709 query_data = g_new(PurpleDnsQueryData, 1);
710 query_data->hostname = g_strdup(hostname);
711 g_strstrip(query_data->hostname);
712 query_data->port = port;
713 query_data->callback = callback;
714 query_data->data = data;
715 query_data->resolver = NULL;
717 if (*query_data->hostname == '\0')
719 purple_dnsquery_destroy(query_data);
720 g_return_val_if_reached(NULL);
723 purple_debug_info("dns", "DNS query for '%s' queued\n", query_data->hostname);
725 query_data->timeout = purple_timeout_add(0, resolve_host, query_data);
727 return query_data;
730 #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */
733 * Windows!
736 static gboolean
737 dns_main_thread_cb(gpointer data)
739 PurpleDnsQueryData *query_data = data;
741 /* We're done, so purple_dnsquery_destroy() shouldn't think it is canceling an in-progress lookup */
742 query_data->resolver = NULL;
744 if (query_data->error_message != NULL)
745 purple_dnsquery_failed(query_data, query_data->error_message);
746 else
748 GSList *hosts;
750 /* We don't want purple_dns_query_resolved() to free(hosts) */
751 hosts = query_data->hosts;
752 query_data->hosts = NULL;
753 purple_dnsquery_resolved(query_data, hosts);
756 return FALSE;
759 static gpointer
760 dns_thread(gpointer data)
762 PurpleDnsQueryData *query_data;
763 #ifdef HAVE_GETADDRINFO
764 int rc;
765 struct addrinfo hints, *res, *tmp;
766 char servname[20];
767 #else
768 struct sockaddr_in sin;
769 struct hostent *hp;
770 #endif
771 char *hostname;
773 query_data = data;
775 #ifdef USE_IDN
776 if (!dns_str_is_ascii(query_data->hostname)) {
777 rc = purple_network_convert_idn_to_ascii(query_data->hostname, &hostname);
778 if (rc != 0) {
779 query_data->error_message = g_strdup_printf(_("Error converting %s "
780 "to punycode: %d"), query_data->hostname, rc);
781 /* back to main thread */
782 purple_timeout_add(0, dns_main_thread_cb, query_data);
783 return 0;
785 } else /* intentional fallthru */
786 #endif
787 hostname = g_strdup(query_data->hostname);
789 #ifdef HAVE_GETADDRINFO
790 g_snprintf(servname, sizeof(servname), "%d", query_data->port);
791 memset(&hints,0,sizeof(hints));
794 * This is only used to convert a service
795 * name to a port number. As we know we are
796 * passing a number already, we know this
797 * value will not be really used by the C
798 * library.
800 hints.ai_socktype = SOCK_STREAM;
801 #ifdef AI_ADDRCONFIG
802 hints.ai_flags |= AI_ADDRCONFIG;
803 #endif /* AI_ADDRCONFIG */
804 if ((rc = getaddrinfo(hostname, servname, &hints, &res)) == 0) {
805 tmp = res;
806 while(res) {
807 query_data->hosts = g_slist_append(query_data->hosts,
808 GSIZE_TO_POINTER(res->ai_addrlen));
809 query_data->hosts = g_slist_append(query_data->hosts,
810 g_memdup(res->ai_addr, res->ai_addrlen));
811 res = res->ai_next;
813 freeaddrinfo(tmp);
814 } else {
815 query_data->error_message = g_strdup_printf(_("Error resolving %s:\n%s"), query_data->hostname, purple_gai_strerror(rc));
817 #else
818 if ((hp = gethostbyname(hostname))) {
819 memset(&sin, 0, sizeof(struct sockaddr_in));
820 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
821 sin.sin_family = hp->h_addrtype;
822 sin.sin_port = htons(query_data->port);
824 query_data->hosts = g_slist_append(query_data->hosts,
825 GSIZE_TO_POINTER(sizeof(sin)));
826 query_data->hosts = g_slist_append(query_data->hosts,
827 g_memdup(&sin, sizeof(sin)));
828 } else {
829 query_data->error_message = g_strdup_printf(_("Error resolving %s: %d"), query_data->hostname, h_errno);
831 #endif
832 g_free(hostname);
834 /* back to main thread */
835 purple_timeout_add(0, dns_main_thread_cb, query_data);
837 return 0;
840 static gboolean
841 resolve_host(gpointer data)
843 PurpleDnsQueryData *query_data;
844 GError *err = NULL;
846 query_data = data;
847 query_data->timeout = 0;
849 if (purple_dnsquery_ui_resolve(query_data))
851 /* The UI is handling the resolve; we're done */
852 return FALSE;
855 if (!resolve_ip(query_data))
858 * Spin off a separate thread to perform the DNS lookup so
859 * that we don't block the UI.
861 query_data->resolver = g_thread_create(dns_thread,
862 query_data, FALSE, &err);
863 if (query_data->resolver == NULL)
865 char message[1024];
866 g_snprintf(message, sizeof(message), _("Thread creation failure: %s"),
867 (err && err->message) ? err->message : _("Unknown reason"));
868 g_error_free(err);
869 purple_dnsquery_failed(query_data, message);
873 return FALSE;
876 PurpleDnsQueryData *
877 purple_dnsquery_a(const char *hostname, int port,
878 PurpleDnsQueryConnectFunction callback, gpointer data)
880 PurpleDnsQueryData *query_data;
882 g_return_val_if_fail(hostname != NULL, NULL);
883 g_return_val_if_fail(port != 0, NULL);
884 g_return_val_if_fail(callback != NULL, NULL);
886 purple_debug_info("dnsquery", "Performing DNS lookup for %s\n", hostname);
888 query_data = g_new0(PurpleDnsQueryData, 1);
889 query_data->hostname = g_strdup(hostname);
890 g_strstrip(query_data->hostname);
891 query_data->port = port;
892 query_data->callback = callback;
893 query_data->data = data;
895 if (strlen(query_data->hostname) == 0)
897 purple_dnsquery_destroy(query_data);
898 g_return_val_if_reached(NULL);
901 /* Don't call the callback before returning */
902 query_data->timeout = purple_timeout_add(0, resolve_host, query_data);
904 return query_data;
907 #else /* not PURPLE_DNSQUERY_USE_FORK or _WIN32 */
910 * We weren't able to do anything fancier above, so use the
911 * fail-safe name resolution code, which is blocking.
914 static gboolean
915 resolve_host(gpointer data)
917 PurpleDnsQueryData *query_data;
918 struct sockaddr_in sin;
919 GSList *hosts = NULL;
921 query_data = data;
922 query_data->timeout = 0;
924 if (purple_dnsquery_ui_resolve(query_data))
926 /* The UI is handling the resolve; we're done */
927 return FALSE;
930 if (!inet_aton(query_data->hostname, &sin.sin_addr)) {
931 struct hostent *hp;
932 gchar *hostname;
933 #ifdef USE_IDN
934 if (!dns_str_is_ascii(query_data->hostname)) {
935 int ret = purple_network_convert_idn_to_ascii(query_data->hostname,
936 &hostname);
937 if (ret != 0) {
938 char message[1024];
939 g_snprintf(message, sizeof(message), _("Error resolving %s: %d"),
940 query_data->hostname, ret);
941 purple_dnsquery_failed(query_data, message);
942 return FALSE;
944 } else /* fallthrough is intentional to the g_strdup */
945 #endif
946 hostname = g_strdup(query_data->hostname);
948 if(!(hp = gethostbyname(hostname))) {
949 char message[1024];
950 g_snprintf(message, sizeof(message), _("Error resolving %s: %d"),
951 query_data->hostname, h_errno);
952 purple_dnsquery_failed(query_data, message);
953 g_free(hostname);
954 return FALSE;
956 memset(&sin, 0, sizeof(struct sockaddr_in));
957 memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length);
958 sin.sin_family = hp->h_addrtype;
959 g_free(hostname);
960 } else
961 sin.sin_family = AF_INET;
962 sin.sin_port = htons(query_data->port);
964 hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin)));
965 hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin)));
967 purple_dnsquery_resolved(query_data, hosts);
969 return FALSE;
972 PurpleDnsQueryData *
973 purple_dnsquery_a(const char *hostname, int port,
974 PurpleDnsQueryConnectFunction callback, gpointer data)
976 PurpleDnsQueryData *query_data;
978 g_return_val_if_fail(hostname != NULL, NULL);
979 g_return_val_if_fail(port != 0, NULL);
980 g_return_val_if_fail(callback != NULL, NULL);
982 query_data = g_new(PurpleDnsQueryData, 1);
983 query_data->hostname = g_strdup(hostname);
984 g_strstrip(query_data->hostname);
985 query_data->port = port;
986 query_data->callback = callback;
987 query_data->data = data;
989 if (strlen(query_data->hostname) == 0)
991 purple_dnsquery_destroy(query_data);
992 g_return_val_if_reached(NULL);
995 /* Don't call the callback before returning */
996 query_data->timeout = purple_timeout_add(0, resolve_host, query_data);
998 return query_data;
1001 #endif /* not PURPLE_DNSQUERY_USE_FORK or _WIN32 */
1003 void
1004 purple_dnsquery_destroy(PurpleDnsQueryData *query_data)
1006 PurpleDnsQueryUiOps *ops = purple_dnsquery_get_ui_ops();
1008 if (ops && ops->destroy)
1009 ops->destroy(query_data);
1011 #if defined(PURPLE_DNSQUERY_USE_FORK)
1012 queued_requests = g_slist_remove(queued_requests, query_data);
1014 if (query_data->resolver != NULL)
1016 * This is only non-NULL when we're cancelling an in-progress
1017 * query. Ideally we would tell our resolver child to stop
1018 * resolving shit and then we would add it back to the
1019 * free_dns_children linked list. However, it's hard to tell
1020 * children stuff, they just don't listen. So we'll just
1021 * kill the process and allow a new child to be started if we
1022 * have more stuff to resolve.
1024 purple_dnsquery_resolver_destroy(query_data->resolver);
1025 #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */
1026 if (query_data->resolver != NULL)
1029 * It's not really possible to kill a thread. So instead we
1030 * just set the callback to NULL and let the DNS lookup
1031 * finish.
1033 query_data->callback = NULL;
1034 return;
1037 while (query_data->hosts != NULL)
1039 /* Discard the length... */
1040 query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data);
1041 /* Free the address... */
1042 g_free(query_data->hosts->data);
1043 query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data);
1045 g_free(query_data->error_message);
1046 #endif /* end _WIN32 */
1048 if (query_data->timeout > 0)
1049 purple_timeout_remove(query_data->timeout);
1051 g_free(query_data->hostname);
1052 g_free(query_data);
1055 char *
1056 purple_dnsquery_get_host(PurpleDnsQueryData *query_data)
1058 g_return_val_if_fail(query_data != NULL, NULL);
1060 return query_data->hostname;
1063 unsigned short
1064 purple_dnsquery_get_port(PurpleDnsQueryData *query_data)
1066 g_return_val_if_fail(query_data != NULL, 0);
1068 return query_data->port;
1071 void
1072 purple_dnsquery_set_ui_ops(PurpleDnsQueryUiOps *ops)
1074 dns_query_ui_ops = ops;
1077 PurpleDnsQueryUiOps *
1078 purple_dnsquery_get_ui_ops(void)
1080 /* It is perfectly acceptable for dns_query_ui_ops to be NULL; this just
1081 * means that the default platform-specific implementation will be used.
1083 return dns_query_ui_ops;
1086 void
1087 purple_dnsquery_init(void)
1091 void
1092 purple_dnsquery_uninit(void)
1094 #if defined(PURPLE_DNSQUERY_USE_FORK)
1095 while (free_dns_children != NULL)
1097 purple_dnsquery_resolver_destroy(free_dns_children->data);
1098 free_dns_children = g_slist_remove(free_dns_children, free_dns_children->data);
1100 #endif /* end PURPLE_DNSQUERY_USE_FORK */