* @file dnsquery.c DNS query API * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA #define _PURPLE_DNSQUERY_C_ #if (defined(__APPLE__) || defined (__unix__)) && !defined(__osf__) #define PURPLE_DNSQUERY_USE_FORK /************************************************************************** **************************************************************************/ static PurpleDnsQueryUiOps *dns_query_ui_ops = NULL; typedef struct _PurpleDnsQueryResolverProcess PurpleDnsQueryResolverProcess; struct _PurpleDnsQueryData { PurpleDnsQueryConnectFunction callback; #if defined(PURPLE_DNSQUERY_USE_FORK) PurpleDnsQueryResolverProcess *resolver; #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */ #if defined(PURPLE_DNSQUERY_USE_FORK) #define MAX_DNS_CHILDREN 4 * This structure keeps a reference to a child resolver process. struct _PurpleDnsQueryResolverProcess { static GSList *free_dns_children = NULL; /* TODO: Make me a GQueue when we require >= glib 2.4 */ static GSList *queued_requests = NULL; static int number_of_dns_children = 0; * This is a convenience struct used to pass data to * the child resolver process. #endif /* end PURPLE_DNSQUERY_USE_FORK */ purple_dnsquery_resolved(PurpleDnsQueryData *query_data, GSList *hosts) purple_debug_info("dnsquery", "IP resolved for %s\n", query_data->hostname); if (query_data->callback != NULL) query_data->callback(hosts, query_data->data, NULL); * Callback is a required parameter, but it can get set to * NULL if we cancel a thread-based DNS lookup. So we need hosts = g_slist_remove(hosts, hosts->data); hosts = g_slist_remove(hosts, hosts->data); #ifdef PURPLE_DNSQUERY_USE_FORK * Add the resolver to the list of available resolvers, and set it * to NULL so that it doesn't get destroyed along with the query_data if (query_data->resolver) free_dns_children = g_slist_prepend(free_dns_children, query_data->resolver); query_data->resolver = NULL; #endif /* PURPLE_DNSQUERY_USE_FORK */ purple_dnsquery_destroy(query_data); purple_dnsquery_failed(PurpleDnsQueryData *query_data, const gchar *error_message) purple_debug_error("dnsquery", "%s\n", error_message); if (query_data->callback != NULL) query_data->callback(NULL, query_data->data, error_message); purple_dnsquery_destroy(query_data); purple_dnsquery_ui_resolve(PurpleDnsQueryData *query_data) PurpleDnsQueryUiOps *ops = purple_dnsquery_get_ui_ops(); if (ops && ops->resolve_host) return ops->resolve_host(query_data, purple_dnsquery_resolved, purple_dnsquery_failed); resolve_ip(PurpleDnsQueryData *query_data) #if defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST) struct addrinfo hints, *res; g_snprintf(servname, sizeof(servname), "%d", query_data->port); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_flags |= AI_NUMERICHOST; if (0 == getaddrinfo(query_data->hostname, servname, &hints, &res)) hosts = g_slist_append(hosts, GINT_TO_POINTER(res->ai_addrlen)); hosts = g_slist_append(hosts, g_memdup(res->ai_addr, res->ai_addrlen)); purple_dnsquery_resolved(query_data, hosts); #else /* defined(HAVE_GETADDRINFO) && defined(AI_NUMERICHOST) */ if (inet_aton(query_data->hostname, &sin.sin_addr)) * The given "hostname" is actually an IP address, so we * don't need to do anything. sin.sin_family = AF_INET; sin.sin_port = htons(query_data->port); hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin))); hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin))); purple_dnsquery_resolved(query_data, hosts); dns_str_is_ascii(const char *name) for (c = (guchar *)name; c && *c; ++c) { #if defined(PURPLE_DNSQUERY_USE_FORK) * Begin the DNS resolver child process functions. G_GNUC_NORETURN static void "Purple's DNS child got a SIGTRAP signal.\n" "This can be caused by trying to run purple inside gdb.\n" "There is a known gdb bug which prevents this. Supposedly purple\n" "should have detected you were using gdb and used an ugly hack,\n" "check cope_with_gdb_brokenness() in dnsquery.c.\n\n" "For more info about this bug, see http://sources.redhat.com/ml/gdb/2001-07/msg00349.html\n"; fputs("\n* * *\n",stderr); fputs("* * *\n\n",stderr); execlp("xmessage","xmessage","-center", message, NULL); write_to_parent(int fd, const void *buf, size_t count) written = write(fd, buf, count); fprintf(stderr, "dns[%d]: Error writing data to " "parent: %s\n", getpid(), strerror(errno)); fprintf(stderr, "dns[%d]: Error: Tried to write %" G_GSIZE_FORMAT " bytes to parent but instead " "wrote %" G_GSIZE_FORMAT " bytes\n", getpid(), count, written); G_GNUC_NORETURN static void purple_dnsquery_resolver_run(int child_out, int child_in, gboolean show_debug) struct addrinfo hints, *res, *tmp; const size_t addrlen = sizeof(sin); purple_restore_default_signal_handlers(); signal(SIGTRAP, trap_gdb_bug); * We resolve 1 host name for each iteration of this * The top half of this reads in the hostname and port * number from the socket with our parent. The bottom * half of this resolves the IP (blocking) and sends * the result back to our parent, when finished. struct timeval tv = { .tv_sec = 20, .tv_usec = 0 }; rc = select(child_in + 1, &fds, NULL, NULL, &tv); printf("dns[%d]: nobody needs me... =(\n", getpid()); rc = read(child_in, &dns_params, sizeof(dns_params_t)); fprintf(stderr, "dns[%d]: Error: Could not read dns_params: " "%s\n", getpid(), strerror(errno)); printf("dns[%d]: Oops, father has gone, wait for me, wait...!\n", getpid()); if (dns_params.hostname[0] == '\0') { fprintf(stderr, "dns[%d]: Error: Parent requested resolution " "of an empty hostname (port = %d)!!!\n", getpid(), if (!dns_str_is_ascii(dns_params.hostname)) { rc = purple_network_convert_idn_to_ascii(dns_params.hostname, &hostname); write_to_parent(child_out, &rc, sizeof(rc)); fprintf(stderr, "dns[%d] Error: IDN conversion returned " dns_params.hostname[0] = '\0'; } else /* intentional to execute the g_strdup */ hostname = g_strdup(dns_params.hostname); /* We have the hostname and port, now resolve the IP */ g_snprintf(servname, sizeof(servname), "%d", dns_params.port); memset(&hints, 0, sizeof(hints)); /* This is only used to convert a service * name to a port number. As we know we are * passing a number already, we know this * value will not be really used by the C hints.ai_socktype = SOCK_STREAM; hints.ai_flags |= AI_ADDRCONFIG; #endif /* AI_ADDRCONFIG */ rc = getaddrinfo(hostname, servname, &hints, &res); write_to_parent(child_out, &rc, sizeof(rc)); printf("dns[%d] Error: getaddrinfo returned %d\n", dns_params.hostname[0] = '\0'; size_t ai_addrlen = res->ai_addrlen; write_to_parent(child_out, &ai_addrlen, sizeof(ai_addrlen)); write_to_parent(child_out, res->ai_addr, res->ai_addrlen); if (!(hp = gethostbyname(hostname))) { write_to_parent(child_out, &h_errno, sizeof(int)); printf("DNS Error: %d\n", h_errno); memset(&sin, 0, sizeof(struct sockaddr_in)); memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); sin.sin_family = hp->h_addrtype; sin.sin_port = htons(dns_params.port); write_to_parent(child_out, &rc, sizeof(rc)); write_to_parent(child_out, &addrlen, sizeof(addrlen)); write_to_parent(child_out, &sin, addrlen); write_to_parent(child_out, &zero, sizeof(zero)); dns_params.hostname[0] = '\0'; * End the DNS resolver child process functions. * Begin the functions for dealing with the DNS child processes. cope_with_gdb_brokenness(void) static gboolean already_done = FALSE; g_snprintf(s, sizeof(s), "/proc/%d/exe", ppid); n = readlink(s, e, sizeof(e)); e[MIN(n,sizeof(e)-1)] = '\0'; "Debugger detected, performing useless query...\n"); gethostbyname("x.x.x.x.x"); purple_dnsquery_resolver_destroy(PurpleDnsQueryResolverProcess *resolver) g_return_if_fail(resolver != NULL); /* Keep this before the kill() call below. */ if (resolver->inpa != 0) { purple_input_remove(resolver->inpa); * We might as well attempt to kill our child process. It really * doesn't matter if this fails, because children will expire on * their own after a few seconds. if (resolver->dns_pid > 0) kill(resolver->dns_pid, SIGKILL); number_of_dns_children--; static PurpleDnsQueryResolverProcess * purple_dnsquery_resolver_new(gboolean show_debug) PurpleDnsQueryResolverProcess *resolver; int child_out[2], child_in[2]; /* Create pipes for communicating with the child process */ if (pipe(child_out) || pipe(child_in)) { purple_debug_error("dns", "Could not create pipes: %s\n", g_strerror(errno)); resolver = g_new(PurpleDnsQueryResolverProcess, 1); cope_with_gdb_brokenness(); /* "Go fork and multiply." --Tommy Caldwell (Emily's dad, not the climber) */ resolver->dns_pid = fork(); /* If we are the child process... */ if (resolver->dns_pid == 0) { /* We should not access the parent's side of the pipes, so close them */ purple_dnsquery_resolver_run(child_out[1], child_in[0], show_debug); /* The thread calls _exit() rather than returning, so we never get here */ /* We should not access the child's side of the pipes, so close them */ if (resolver->dns_pid == -1) { purple_debug_error("dns", "Could not create child process for DNS: %s\n", purple_dnsquery_resolver_destroy(resolver); resolver->fd_out = child_out[0]; resolver->fd_in = child_in[1]; number_of_dns_children++; "Created new DNS child %d, there are now %d children.\n", resolver->dns_pid, number_of_dns_children); * @return TRUE if the request was sent succesfully. FALSE * if the request could not be sent. This isn't * necessarily an error. If the child has expired, * for example, we won't be able to send the message. send_dns_request_to_child(PurpleDnsQueryData *query_data, PurpleDnsQueryResolverProcess *resolver) /* This waitpid might return the child's PID if it has recently * exited, or it might return an error if it exited "long * enough" ago that it has already been reaped; in either * instance, we can't use it. */ pid = waitpid(resolver->dns_pid, NULL, WNOHANG); purple_debug_warning("dns", "DNS child %d no longer exists\n", purple_dnsquery_resolver_destroy(resolver); purple_debug_warning("dns", "Wait for DNS child %d failed: %s\n", resolver->dns_pid, g_strerror(errno)); purple_dnsquery_resolver_destroy(resolver); /* Copy the hostname and port into a single data structure */ strncpy(dns_params.hostname, query_data->hostname, sizeof(dns_params.hostname) - 1); dns_params.hostname[sizeof(dns_params.hostname) - 1] = '\0'; dns_params.port = query_data->port; /* Send the data structure to the child */ rc = write(resolver->fd_in, &dns_params, sizeof(dns_params)); purple_debug_error("dns", "Unable to write to DNS child %d: %s\n", resolver->dns_pid, g_strerror(errno)); purple_dnsquery_resolver_destroy(resolver); if (rc < sizeof(dns_params)) { purple_debug_error("dns", "Tried to write %" G_GSSIZE_FORMAT " bytes to child but only wrote %" G_GSSIZE_FORMAT "\n", purple_dnsquery_resolver_destroy(resolver); "Successfully sent DNS request to child %d\n", query_data->resolver = resolver; static void host_resolved(gpointer data, gint source, PurpleInputCondition cond); handle_next_queued_request(void) PurpleDnsQueryData *query_data; PurpleDnsQueryResolverProcess *resolver; if (queued_requests == NULL) /* No more DNS queries, yay! */ query_data = queued_requests->data; queued_requests = g_slist_delete_link(queued_requests, queued_requests); * If we have any children, attempt to have them perform the DNS * query. If we're able to send the query then resolver will be * set to the PurpleDnsQueryResolverProcess. Otherwise, resolver * will be NULL and we'll need to create a new DNS request child. while (free_dns_children != NULL) resolver = free_dns_children->data; free_dns_children = g_slist_remove(free_dns_children, resolver); if (send_dns_request_to_child(query_data, resolver)) /* We found an acceptable child, yay */ /* We need to create a new DNS request child */ if (query_data->resolver == NULL) if (number_of_dns_children >= MAX_DNS_CHILDREN) /* Apparently all our children are busy */ queued_requests = g_slist_prepend(queued_requests, query_data); resolver = purple_dnsquery_resolver_new(purple_debug_is_enabled()); purple_dnsquery_failed(query_data, _("Unable to create new resolver process\n")); if (!send_dns_request_to_child(query_data, resolver)) purple_dnsquery_failed(query_data, _("Unable to send request to resolver process\n")); query_data->resolver->inpa = purple_input_add(query_data->resolver->fd_out, PURPLE_INPUT_READ, host_resolved, query_data); * End the functions for dealing with the DNS child processes. host_resolved(gpointer data, gint source, PurpleInputCondition cond) PurpleDnsQueryData *query_data; struct sockaddr *addr = NULL; purple_debug_info("dns", "Got response for '%s'\n", query_data->hostname); purple_input_remove(query_data->resolver->inpa); query_data->resolver->inpa = 0; rc = read(query_data->resolver->fd_out, &err, sizeof(err)); if ((rc == 4) && (err != 0)) g_snprintf(message, sizeof(message), _("Error resolving %s:\n%s"), query_data->hostname, purple_gai_strerror(err)); g_snprintf(message, sizeof(message), _("Error resolving %s: %d"), query_data->hostname, err); /* Re-read resolv.conf and friends in case DNS servers have changed */ purple_dnsquery_failed(query_data, message); rc = read(query_data->resolver->fd_out, &addrlen, sizeof(addrlen)); if (rc > 0 && addrlen > 0) { addr = g_malloc(addrlen); rc = read(query_data->resolver->fd_out, addr, addrlen); hosts = g_slist_append(hosts, GINT_TO_POINTER(addrlen)); hosts = g_slist_append(hosts, addr); /* wait4(resolver->dns_pid, NULL, WNOHANG, NULL); */ purple_dnsquery_resolved(query_data, hosts); g_snprintf(message, sizeof(message), _("Error reading from resolver process:\n%s"), g_strerror(errno)); purple_dnsquery_failed(query_data, message); g_snprintf(message, sizeof(message), _("Resolver process exited without answering our request")); purple_dnsquery_failed(query_data, message); handle_next_queued_request(); resolve_host(PurpleDnsQueryData *query_data) queued_requests = g_slist_append(queued_requests, query_data); handle_next_queued_request(); #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */ dns_main_thread_cb(gpointer data) PurpleDnsQueryData *query_data = data; /* We're done, so purple_dnsquery_destroy() shouldn't think it is canceling an in-progress lookup */ query_data->resolver = NULL; if (query_data->error_message != NULL) purple_dnsquery_failed(query_data, query_data->error_message); /* We don't want purple_dns_query_resolved() to free(hosts) */ hosts = query_data->hosts; query_data->hosts = NULL; purple_dnsquery_resolved(query_data, hosts); dns_thread(gpointer data) PurpleDnsQueryData *query_data; struct addrinfo hints, *res, *tmp; if (!dns_str_is_ascii(query_data->hostname)) { rc = purple_network_convert_idn_to_ascii(query_data->hostname, &hostname); query_data->error_message = g_strdup_printf(_("Error converting %s " "to punycode: %d"), query_data->hostname, rc); /* back to main thread */ purple_timeout_add(0, dns_main_thread_cb, query_data); } else /* intentional fallthru */ hostname = g_strdup(query_data->hostname); g_snprintf(servname, sizeof(servname), "%d", query_data->port); memset(&hints,0,sizeof(hints)); * This is only used to convert a service * name to a port number. As we know we are * passing a number already, we know this * value will not be really used by the C hints.ai_socktype = SOCK_STREAM; hints.ai_flags |= AI_ADDRCONFIG; #endif /* AI_ADDRCONFIG */ if ((rc = getaddrinfo(hostname, servname, &hints, &res)) == 0) { query_data->hosts = g_slist_append(query_data->hosts, GSIZE_TO_POINTER(res->ai_addrlen)); query_data->hosts = g_slist_append(query_data->hosts, g_memdup(res->ai_addr, res->ai_addrlen)); query_data->error_message = g_strdup_printf(_("Error resolving %s:\n%s"), query_data->hostname, purple_gai_strerror(rc)); if ((hp = gethostbyname(hostname))) { memset(&sin, 0, sizeof(struct sockaddr_in)); memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); sin.sin_family = hp->h_addrtype; sin.sin_port = htons(query_data->port); query_data->hosts = g_slist_append(query_data->hosts, GSIZE_TO_POINTER(sizeof(sin))); query_data->hosts = g_slist_append(query_data->hosts, g_memdup(&sin, sizeof(sin))); query_data->error_message = g_strdup_printf(_("Error resolving %s: %d"), query_data->hostname, h_errno); /* back to main thread */ purple_timeout_add(0, dns_main_thread_cb, query_data); resolve_host(PurpleDnsQueryData *query_data) * Spin off a separate thread to perform the DNS lookup so * that we don't block the UI. query_data->resolver = g_thread_create(dns_thread, query_data, FALSE, &err); if (query_data->resolver == NULL) g_snprintf(message, sizeof(message), _("Thread creation failure: %s"), (err && err->message) ? err->message : _("Unknown reason")); purple_dnsquery_failed(query_data, message); #else /* not PURPLE_DNSQUERY_USE_FORK or _WIN32 */ * We weren't able to do anything fancier above, so use the * fail-safe name resolution code, which is blocking. resolve_host(PurpleDnsQueryData *query_data) if (!dns_str_is_ascii(query_data->hostname)) { int ret = purple_network_convert_idn_to_ascii(query_data->hostname, g_snprintf(message, sizeof(message), _("Error resolving %s: %d"), query_data->hostname, ret); purple_dnsquery_failed(query_data, message); } else /* fallthrough is intentional to the g_strdup */ hostname = g_strdup(query_data->hostname); if(!(hp = gethostbyname(hostname))) { g_snprintf(message, sizeof(message), _("Error resolving %s: %d"), query_data->hostname, h_errno); purple_dnsquery_failed(query_data, message); memset(&sin, 0, sizeof(struct sockaddr_in)); memcpy(&sin.sin_addr.s_addr, hp->h_addr, hp->h_length); sin.sin_family = hp->h_addrtype; sin.sin_port = htons(query_data->port); hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin))); hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin))); purple_dnsquery_resolved(query_data, hosts); #endif /* not PURPLE_DNSQUERY_USE_FORK or _WIN32 */ initiate_resolving(gpointer data) PurpleDnsQueryData *query_data; PurpleProxyType proxy_type; if (resolve_ip(query_data)) /* resolve_ip calls purple_dnsquery_resolved */ proxy_type = purple_proxy_info_get_type( purple_proxy_get_setup(query_data->account)); if (proxy_type == PURPLE_PROXY_TOR) { purple_dnsquery_failed(query_data, _("Aborting DNS lookup in Tor Proxy mode.")); if (purple_dnsquery_ui_resolve(query_data)) /* The UI is handling the resolve; we're done */ resolve_host(query_data); purple_dnsquery_a_account(PurpleAccount *account, const char *hostname, int port, PurpleDnsQueryConnectFunction callback, gpointer data) PurpleDnsQueryData *query_data; g_return_val_if_fail(hostname != NULL, NULL); g_return_val_if_fail(port != 0, NULL); g_return_val_if_fail(callback != NULL, NULL); purple_debug_info("dnsquery", "Performing DNS lookup for %s\n", hostname); query_data = g_new0(PurpleDnsQueryData, 1); query_data->hostname = g_strdup(hostname); g_strstrip(query_data->hostname); query_data->callback = callback; query_data->account = account; if (*query_data->hostname == '\0') purple_dnsquery_destroy(query_data); g_return_val_if_reached(NULL); query_data->timeout = purple_timeout_add(0, initiate_resolving, query_data); purple_dnsquery_a(const char *hostname, int port, PurpleDnsQueryConnectFunction callback, gpointer data) return purple_dnsquery_a_account(NULL, hostname, port, callback, data); purple_dnsquery_destroy(PurpleDnsQueryData *query_data) PurpleDnsQueryUiOps *ops = purple_dnsquery_get_ui_ops(); ops->destroy(query_data); #if defined(PURPLE_DNSQUERY_USE_FORK) queued_requests = g_slist_remove(queued_requests, query_data); if (query_data->resolver != NULL) * This is only non-NULL when we're cancelling an in-progress * query. Ideally we would tell our resolver child to stop * resolving shit and then we would add it back to the * free_dns_children linked list. However, it's hard to tell * children stuff, they just don't listen. So we'll just * kill the process and allow a new child to be started if we * have more stuff to resolve. purple_dnsquery_resolver_destroy(query_data->resolver); #elif defined _WIN32 /* end PURPLE_DNSQUERY_USE_FORK */ if (query_data->resolver != NULL) * It's not really possible to kill a thread. So instead we * just set the callback to NULL and let the DNS lookup query_data->callback = NULL; while (query_data->hosts != NULL) /* Discard the length... */ query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data); /* Free the address... */ g_free(query_data->hosts->data); query_data->hosts = g_slist_remove(query_data->hosts, query_data->hosts->data); g_free(query_data->error_message); if (query_data->timeout > 0) purple_timeout_remove(query_data->timeout); g_free(query_data->hostname); purple_dnsquery_get_host(PurpleDnsQueryData *query_data) g_return_val_if_fail(query_data != NULL, NULL); return query_data->hostname; purple_dnsquery_get_port(PurpleDnsQueryData *query_data) g_return_val_if_fail(query_data != NULL, 0); purple_dnsquery_set_ui_ops(PurpleDnsQueryUiOps *ops) purple_dnsquery_get_ui_ops(void) /* It is perfectly acceptable for dns_query_ui_ops to be NULL; this just * means that the default platform-specific implementation will be used. purple_dnsquery_init(void) purple_dnsquery_uninit(void) #if defined(PURPLE_DNSQUERY_USE_FORK) while (free_dns_children != NULL) purple_dnsquery_resolver_destroy(free_dns_children->data); free_dns_children = g_slist_remove(free_dns_children, free_dns_children->data); #endif /* end PURPLE_DNSQUERY_USE_FORK */