# HG changeset patch # User Paul Aurich # Date 1248047113 0 # Node ID 7fbf964c6c6c2c83a1e94906b39021c937c73524 # Parent 91333c6c16ef536b9528d919925fd7ab97dd0b5e Move the IDN support into the DNS routines. This turned out to be cleaner than I first thought, so here we go. All libpurple DNS routines support IDN when built against GNU libidn. diff -r 91333c6c16ef -r 7fbf964c6c6c ChangeLog --- a/ChangeLog Sun Jul 19 22:40:10 2009 +0000 +++ b/ChangeLog Sun Jul 19 23:45:13 2009 +0000 @@ -38,6 +38,8 @@ * Better handling of corrupt certificates in the TLS Peers cache. * More efficient purple_find_buddies() and purple_find_group() functions. (Jan Kaluza and Aman Gupta) + * Internationalized Domain Names are supported when libpurple is compiled + against the GNU IDN library. AIM and ICQ: * Preliminary support for a new authentication scheme called @@ -106,9 +108,8 @@ * Removed support for obsoleted XEP-0022 (Message Events) and XEP-0091 (Legacy Entity Time). * When the GNU IDN library (libidn) is available, it is used for - normalization of Jabber IDs and support for internationalized domain - names (IDNA). When unavailable, internal routines are used (as in - previous versions) for normalization and IDNA is unavailable. + normalization of Jabber IDs. When unavailable, internal routines are + used (as in previous versions). Yahoo!/Yahoo! JAPAN: * P2P file transfers. (Sulabh Mahajan) diff -r 91333c6c16ef -r 7fbf964c6c6c ChangeLog.API --- a/ChangeLog.API Sun Jul 19 22:40:10 2009 +0000 +++ b/ChangeLog.API Sun Jul 19 23:45:13 2009 +0000 @@ -42,6 +42,7 @@ * purple_log_get_activity_score * purple_markup_is_rtl * purple_markup_escape_text + * purple_network_convert_idn_to_ascii * purple_network_force_online * purple_network_set_stun_server * purple_network_set_turn_server @@ -83,6 +84,8 @@ * purple_find_buddies is now more efficient in the case where it is enumerating all the buddies for an account. * purple_find_group is now more efficient for large numbers of groups. + * All DNS routines support internationalized domain names (IDNs) when + libpurple is compiled with GNU libidn. Deprecated: * buddy-added and buddy-removed blist signals diff -r 91333c6c16ef -r 7fbf964c6c6c libpurple/Makefile.am --- a/libpurple/Makefile.am Sun Jul 19 22:40:10 2009 +0000 +++ b/libpurple/Makefile.am Sun Jul 19 23:45:13 2009 +0000 @@ -293,6 +293,7 @@ $(FARSIGHT_LIBS) \ $(GSTREAMER_LIBS) \ $(GSTINTERFACES_LIBS) \ + $(IDN_LIBS) \ -lm AM_CPPFLAGS = \ @@ -308,6 +309,7 @@ $(FARSIGHT_CFLAGS) \ $(GSTREAMER_CFLAGS) \ $(GSTINTERFACES_CFLAGS) \ + $(IDN_CFLAGS) \ $(NETWORKMANAGER_CFLAGS) # INSTALL_SSL_CERTIFICATES is true when SSL_CERTIFICATES_DIR is empty. diff -r 91333c6c16ef -r 7fbf964c6c6c libpurple/dnsquery.c --- a/libpurple/dnsquery.c Sun Jul 19 22:40:10 2009 +0000 +++ b/libpurple/dnsquery.c Sun Jul 19 23:45:13 2009 +0000 @@ -28,6 +28,7 @@ #include "internal.h" #include "debug.h" #include "dnsquery.h" +#include "network.h" #include "notify.h" #include "prefs.h" #include "util.h" @@ -171,6 +172,18 @@ return FALSE; } +static gboolean +dns_str_is_ascii(const char *name) +{ + guchar *c; + for (c = (guchar *)name; c && *c; ++c) { + if (*c > 0x7f) + return FALSE; + } + + return TRUE; +} + #if defined(PURPLE_DNSQUERY_USE_FORK) /* @@ -230,6 +243,7 @@ struct sockaddr_in sin; const size_t addrlen = sizeof(sin); #endif + char *hostname; #ifdef HAVE_SIGNAL_H purple_restore_default_signal_handlers(); @@ -274,6 +288,22 @@ _exit(1); } +#ifdef USE_IDN + if (!dns_str_is_ascii(dns_params.hostname)) { + rc = purple_network_convert_idn_to_ascii(dns_params.hostname, &hostname); + if (rc != 0) { + write_to_parent(child_out, &rc, sizeof(rc)); + close(child_out); + if (show_debug) + fprintf(stderr, "dns[%d] Error: IDN conversion returned " + "%d\n", getpid(), rc); + dns_params.hostname[0] = '\0'; + continue; + } + } else /* intentional to execute the g_strdup */ +#endif + hostname = g_strdup(dns_params.hostname); + /* We have the hostname and port, now resolve the IP */ #ifdef HAVE_GETADDRINFO @@ -290,7 +320,7 @@ #ifdef AI_ADDRCONFIG hints.ai_flags |= AI_ADDRCONFIG; #endif /* AI_ADDRCONFIG */ - rc = getaddrinfo(dns_params.hostname, servname, &hints, &res); + rc = getaddrinfo(hostname, servname, &hints, &res); write_to_parent(child_out, &rc, sizeof(rc)); if (rc != 0) { close(child_out); @@ -309,9 +339,9 @@ } freeaddrinfo(tmp); #else - if (!inet_aton(dns_params.hostname, &sin.sin_addr)) { + if (!inet_aton(hostname, &sin.sin_addr)) { struct hostent *hp; - if (!(hp = gethostbyname(dns_params.hostname))) { + if (!(hp = gethostbyname(hostname))) { write_to_parent(child_out, &h_errno, sizeof(int)); close(child_out); if (show_debug) @@ -332,6 +362,9 @@ #endif write_to_parent(child_out, &zero, sizeof(zero)); dns_params.hostname[0] = '\0'; + + g_free(hostname); + hostname = NULL; } close(child_out); @@ -733,9 +766,27 @@ struct sockaddr_in sin; struct hostent *hp; #endif + char *hostname; query_data = data; +#ifdef USE_IDN + if (!dns_str_is_ascii(dns_params.hostname)) { + rc = purple_network_convert_idn_to_ascii(dns_params.hostname, &hostname); + if (rc != 0) { + /* FIXME: Dirty 2.6.0 string freeze hack */ + char tmp[8]; + g_snprintf(tmp, sizeof(tmp), "%d", rc); + query_data->error_message = g_strdup_printf(_("Error resolving %s:\n%s"), + query_data->hostname, tmp); + /* back to main thread */ + purple_timeout_add(0, dns_main_thread_cb, query_data); + return 0; + } + } else /* intentional fallthru */ +#endif + hostname = g_strdup(dns_params.hostname); + #ifdef HAVE_GETADDRINFO g_snprintf(servname, sizeof(servname), "%d", query_data->port); memset(&hints,0,sizeof(hints)); @@ -751,7 +802,7 @@ #ifdef AI_ADDRCONFIG hints.ai_flags |= AI_ADDRCONFIG; #endif /* AI_ADDRCONFIG */ - if ((rc = getaddrinfo(query_data->hostname, servname, &hints, &res)) == 0) { + if ((rc = getaddrinfo(hostname, servname, &hints, &res)) == 0) { tmp = res; while(res) { query_data->hosts = g_slist_append(query_data->hosts, @@ -765,7 +816,7 @@ query_data->error_message = g_strdup_printf(_("Error resolving %s:\n%s"), query_data->hostname, purple_gai_strerror(rc)); } #else - if ((hp = gethostbyname(query_data->hostname))) { + 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; @@ -779,6 +830,7 @@ query_data->error_message = g_strdup_printf(_("Error resolving %s: %d"), query_data->hostname, h_errno); } #endif + g_free(hostname); /* back to main thread */ purple_timeout_add(0, dns_main_thread_cb, query_data); @@ -866,6 +918,7 @@ PurpleDnsQueryData *query_data; struct sockaddr_in sin; GSList *hosts = NULL; + char *hostname; query_data = data; query_data->timeout = 0; @@ -878,7 +931,22 @@ if (!inet_aton(query_data->hostname, &sin.sin_addr)) { struct hostent *hp; - if(!(hp = gethostbyname(query_data->hostname))) { +#ifdef USE_IDN + if (!dns_str_is_ascii(query_data->hostname)) { + int ret = purple_network_convert_idn_to_ascii(query_data->hostname, + &hostname); + if (ret != 0) { + char message[1024]; + g_snprintf(message, sizeof(message), _("Error resolving %s: %d"), + query_data->hostname, ret); + purple_dnsquery_failed(query_data, message); + return FALSE; + } + } else /* fallthrough is intentional to the g_strdup */ +#endif + hostname = g_strdup(query_data->hostname); + + if(!(hp = gethostbyname(hostname))) { char message[1024]; g_snprintf(message, sizeof(message), _("Error resolving %s: %d"), query_data->hostname, h_errno); @@ -892,6 +960,7 @@ sin.sin_family = AF_INET; sin.sin_port = htons(query_data->port); + g_free(hostname); hosts = g_slist_append(hosts, GINT_TO_POINTER(sizeof(sin))); hosts = g_slist_append(hosts, g_memdup(&sin, sizeof(sin))); diff -r 91333c6c16ef -r 7fbf964c6c6c libpurple/dnssrv.c --- a/libpurple/dnssrv.c Sun Jul 19 22:40:10 2009 +0000 +++ b/libpurple/dnssrv.c Sun Jul 19 23:45:13 2009 +0000 @@ -47,9 +47,10 @@ #endif #endif +#include "debug.h" #include "dnssrv.h" #include "eventloop.h" -#include "debug.h" +#include "network.h" #ifndef _WIN32 typedef union { @@ -247,6 +248,18 @@ return list; } +static gboolean +dns_str_is_ascii(const char *name) +{ + guchar *c; + for (c = (guchar *)name; c && *c; ++c) { + if (*c > 0x7f) + return FALSE; + } + + return TRUE; +} + #ifndef _WIN32 G_GNUC_NORETURN static void @@ -588,6 +601,7 @@ purple_srv_resolve(const char *protocol, const char *transport, const char *domain, PurpleSrvCallback cb, gpointer extradata) { char *query; + char *hostname; PurpleSrvQueryData *query_data; #ifndef _WIN32 PurpleSrvInternalQuery internal_query; @@ -604,8 +618,22 @@ g_return_val_if_reached(NULL); } - query = g_strdup_printf("_%s._%s.%s", protocol, transport, domain); - purple_debug_info("dnssrv","querying SRV record for %s\n", query); +#ifdef USE_IDN + if (!dns_str_is_ascii(domain)) { + int ret = purple_network_convert_idn_to_ascii(domain, &hostname); + if (ret != 0) { + purple_debug_error("dnssrv", "IDNA ToASCII failed\n"); + cb(NULL, 0, extradata); + return NULL; + } + } else /* Fallthru is intentional */ +#endif + hostname = g_strdup(domain); + + query = g_strdup_printf("_%s._%s.%s", protocol, transport, hostname); + purple_debug_info("dnssrv","querying SRV record for %s: %s\n", domain, + query); + g_free(hostname); #ifndef _WIN32 if(pipe(in) || pipe(out)) { @@ -692,6 +720,7 @@ PurpleSrvQueryData *purple_txt_resolve(const char *owner, const char *domain, PurpleTxtCallback cb, gpointer extradata) { char *query; + char *hostname; PurpleSrvQueryData *query_data; #ifndef _WIN32 PurpleSrvInternalQuery internal_query; @@ -702,8 +731,22 @@ static gboolean initialized = FALSE; #endif - query = g_strdup_printf("%s.%s", owner, domain); - purple_debug_info("dnssrv","querying TXT record for %s\n", query); +#ifdef USE_IDN + if (!dns_str_is_ascii(domain)) { + int ret = purple_network_convert_idn_to_ascii(domain, &hostname); + if (ret != 0) { + purple_debug_error("dnssrv", "IDNA ToASCII failed\n"); + cb(NULL, extradata); + return NULL; + } + } else /* fallthru is intentional */ +#endif + hostname = g_strdup(domain); + + query = g_strdup_printf("%s.%s", owner, hostname); + purple_debug_info("dnssrv","querying TXT record for %s: %s\n", domain, + query); + g_free(hostname); #ifndef _WIN32 if(pipe(in) || pipe(out)) { diff -r 91333c6c16ef -r 7fbf964c6c6c libpurple/network.c --- a/libpurple/network.c Sun Jul 19 22:40:10 2009 +0000 +++ b/libpurple/network.c Sun Jul 19 23:45:13 2009 +0000 @@ -50,6 +50,10 @@ #include "upnp.h" #include "dnsquery.h" +#ifdef USE_IDN +#include +#endif + /* * Calling sizeof(struct ifreq) isn't always correct on * Mac OS X (and maybe others). @@ -966,7 +970,33 @@ } } } - + +int purple_network_convert_idn_to_ascii(const gchar *in, gchar **out) +{ +#ifdef USE_IDN + char *tmp; + int ret; + + g_return_val_if_fail(out != NULL, -1); + + ret = idna_to_ascii_8z(in, &tmp, IDNA_USE_STD3_ASCII_RULES); + if (ret != IDNA_SUCCESS) { + *out = NULL; + return ret; + } + + *out = g_strdup(tmp); + /* This *MUST* be freed with free, not g_free */ + free(tmp); + return 0; +#else + g_return_val_if_fail(out != NULL, -1); + + *out = g_strdup(in); + return 0; +#endif +} + void purple_network_init(void) { diff -r 91333c6c16ef -r 7fbf964c6c6c libpurple/network.h --- a/libpurple/network.h Sun Jul 19 22:40:10 2009 +0000 +++ b/libpurple/network.h Sun Jul 19 23:45:13 2009 +0000 @@ -268,6 +268,25 @@ void purple_network_remove_port_mapping(gint fd); /** + * Convert a UTF-8 domain name to ASCII in accordance with the IDNA + * specification. If libpurple is compiled without IDN support, this function + * copies the input into the output buffer. + * + * Because this function is used by DNS resolver child/threads, it uses no + * other libpurple API and is threadsafe. + * + * In general, a buffer of about 512 bytes is the appropriate size to use. + * + * @param in The hostname to be converted. + * @param out The output buffer where an allocated string will be returned. + * The caller is responsible for freeing this. + * @returns 0 on success, -1 if the out is NULL, or an error code + * that currently corresponds to the Idna_rc enum in libidn. + * @since 2.6.0 + */ +int purple_network_convert_idn_to_ascii(const gchar *in, gchar **out); + +/** * Initializes the network subsystem. */ void purple_network_init(void); diff -r 91333c6c16ef -r 7fbf964c6c6c libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Sun Jul 19 22:40:10 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.c Sun Jul 19 23:45:13 2009 +0000 @@ -657,11 +657,9 @@ purple_debug_error("jabber", "Unable to connect to server: %s. Trying next SRV record.\n", error); try_srv_connect(js); } else { - char *ascii_domain = jabber_try_idna_to_ascii(js->user->domain); purple_debug_info("jabber","Couldn't connect directly to %s. Trying to find alternative connection methods, like BOSH.\n", js->user->domain); js->srv_query_data = purple_txt_resolve("_xmppconnect", - ascii_domain, txt_resolved_cb, js); - g_free(ascii_domain); + js->user->domain, txt_resolved_cb, js); } return; } @@ -704,17 +702,14 @@ } static gboolean jabber_login_connect(JabberStream *js, const char *domain, const char *host, int port, - gboolean fatal_failure, gboolean use_domain) + gboolean fatal_failure) { /* host should be used in preference to domain to * allow SASL authentication to work with FQDN of the server, * but we use domain as fallback for when users enter IP address - * in connect server. - * We also want to use the domain if the hostname is the result of the - * IDNA ToASCII operation. - */ + * in connect server */ g_free(js->serverFQDN); - if (use_domain || purple_ip_address_is_valid(host)) + if (purple_ip_address_is_valid(host)) js->serverFQDN = g_strdup(domain); else js->serverFQDN = g_strdup(host); @@ -735,23 +730,19 @@ static void try_srv_connect(JabberStream *js) { - char *ascii_domain; - while (js->srv_rec != NULL && js->srv_rec_idx < js->max_srv_rec_idx) { PurpleSrvResponse *tmp_resp = js->srv_rec + (js->srv_rec_idx++); - if (jabber_login_connect(js, tmp_resp->hostname, tmp_resp->hostname, tmp_resp->port, FALSE, FALSE)) + if (jabber_login_connect(js, tmp_resp->hostname, tmp_resp->hostname, tmp_resp->port, FALSE)) return; } g_free(js->srv_rec); js->srv_rec = NULL; - ascii_domain = jabber_try_idna_to_ascii(js->user->domain); /* Fall back to the defaults (I'm not sure if we should actually do this) */ - jabber_login_connect(js, js->user->domain, ascii_domain, + jabber_login_connect(js, js->user->domain, js->user->domain, purple_account_get_int(purple_connection_get_account(js->gc), "port", 5222), - TRUE, TRUE); - g_free(ascii_domain); + TRUE); } static void srv_resolved_cb(PurpleSrvResponse *resp, int results, gpointer data) @@ -765,11 +756,9 @@ js->max_srv_rec_idx = results; try_srv_connect(js); } else { - char *ascii_domain = jabber_try_idna_to_ascii(js->user->domain); - jabber_login_connect(js, js->user->domain, ascii_domain, + jabber_login_connect(js, js->user->domain, js->user->domain, purple_account_get_int(purple_connection_get_account(js->gc), "port", 5222), - TRUE, TRUE); - g_free(ascii_domain); + TRUE); } } @@ -860,7 +849,6 @@ "connect_server", ""); const char *bosh_url = purple_account_get_string(account, "bosh_url", ""); - char *ascii_domain; jabber_stream_set_state(js, JABBER_STREAM_CONNECTING); @@ -883,22 +871,12 @@ js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user->domain); - ascii_domain = jabber_try_idna_to_ascii(js->certificate_CN); - if (ascii_domain == NULL) { - purple_connection_error_reason(gc, - PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, - _("Invalid XMPP ID")); - return; - } - /* if they've got old-ssl mode going, we probably want to ignore SRV lookups */ if(purple_account_get_bool(account, "old_ssl", FALSE)) { if(purple_ssl_is_supported()) { - js->gsc = purple_ssl_connect_with_ssl_cn(account, ascii_domain, + js->gsc = purple_ssl_connect(account, js->certificate_CN, purple_account_get_int(account, "port", 5223), - jabber_login_callback_ssl, jabber_ssl_connect_failure, - js->certificate_CN, gc); - g_free(ascii_domain); + jabber_login_callback_ssl, jabber_ssl_connect_failure, gc); if (!js->gsc) { purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, @@ -910,22 +888,18 @@ _("SSL support unavailable")); } - g_free(ascii_domain); return; } /* no old-ssl, so if they've specified a connect server, we'll use that, otherwise we'll * invoke the magic of SRV lookups, to figure out host and port */ if(connect_server[0]) { - jabber_login_connect(js, js->user->domain, ascii_domain, - purple_account_get_int(account, "port", 5222), - TRUE, TRUE); + jabber_login_connect(js, js->user->domain, connect_server, + purple_account_get_int(account, "port", 5222), TRUE); } else { js->srv_query_data = purple_srv_resolve("xmpp-client", - "tcp", ascii_domain, srv_resolved_cb, js); + "tcp", js->user->domain, srv_resolved_cb, js); } - - g_free(ascii_domain); } void diff -r 91333c6c16ef -r 7fbf964c6c6c libpurple/protocols/jabber/jutil.c --- a/libpurple/protocols/jabber/jutil.c Sun Jul 19 22:40:10 2009 +0000 +++ b/libpurple/protocols/jabber/jutil.c Sun Jul 19 23:45:13 2009 +0000 @@ -37,28 +37,6 @@ static char idn_buffer[1024]; #endif -gchar *jabber_try_idna_to_ascii(const char *input) -{ -#ifndef USE_IDN - return g_strdup(input); -#else - gchar *out; - char *tmp; - - g_return_val_if_fail(input != NULL, NULL); - g_return_val_if_fail(*input != '\0', NULL); - - if (idna_to_ascii_8z(input, &tmp, IDNA_USE_STD3_ASCII_RULES) != IDNA_SUCCESS) { - return NULL; - } - - out = g_strdup(tmp); - /* This *MUST* be freed with free, not g_free */ - free(tmp); - return out; -#endif -} - #ifdef USE_IDN static gboolean jabber_nodeprep(char *str, size_t buflen) { diff -r 91333c6c16ef -r 7fbf964c6c6c libpurple/protocols/jabber/jutil.h --- a/libpurple/protocols/jabber/jutil.h Sun Jul 19 22:40:10 2009 +0000 +++ b/libpurple/protocols/jabber/jutil.h Sun Jul 19 23:45:13 2009 +0000 @@ -44,11 +44,6 @@ /* Returns true if JID is the bare JID of our account. */ gboolean jabber_is_own_account(JabberStream *js, const char *jid); -/* Try to convert an IDNA domain name to something we can pass to a DNS lookup. - * If IDN support is not available, returns a copy of the input string. - */ -gchar *jabber_try_idna_to_ascii(const gchar *input); - gboolean jabber_nodeprep_validate(const char *); gboolean jabber_domain_validate(const char *); gboolean jabber_resourceprep_validate(const char *);