# HG changeset patch # User Paul Aurich # Date 1270094945 0 # Node ID 9bfa52f8ee877a5cfbd7bdfb611e68e0c99f6298 # Parent 852f9df1d7351fef240d93b9637745bdf3355af3 gnutls: Allow overriding (per-host) of GnuTLS priorities via env. Fixes #11616 This allows a site to add workarounds for connecting to specific servers (like the reporter's) which are horribly broken when it comes to TLS 1.0+. The format (to be documented in the ChangeLog) is host=priority pairs delimited by semicolons (also pending a confirmation that gnutls_priority_init's documentation is wrong about semicolon vs. colon). diff -r 852f9df1d735 -r 9bfa52f8ee87 libpurple/plugins/ssl/ssl-gnutls.c --- a/libpurple/plugins/ssl/ssl-gnutls.c Wed Mar 31 07:28:34 2010 +0000 +++ b/libpurple/plugins/ssl/ssl-gnutls.c Thu Apr 01 04:09:05 2010 +0000 @@ -40,7 +40,15 @@ #define PURPLE_SSL_GNUTLS_DATA(gsc) ((PurpleSslGnutlsData *)gsc->private_data) -static gnutls_certificate_client_credentials xcred; +static gnutls_certificate_client_credentials xcred = NULL; + +#ifdef HAVE_GNUTLS_PRIORITY_FUNCS +/* Priority strings. The default one is, well, the default (and is always set). + * The hash table is of the form hostname => priority (both char *) + */ +static char *default_priority = NULL; +static GHashTable *host_priorities = NULL; +#endif static void ssl_gnutls_log(int level, const char *str) @@ -53,6 +61,7 @@ ssl_gnutls_init_gnutls(void) { const char *debug_level; + const char *host_priorities_str; /* Configure GnuTLS to use glib memory management */ /* I expect that this isn't really necessary, but it may prevent @@ -82,6 +91,60 @@ gnutls_global_set_log_function(ssl_gnutls_log); } + /* Expected format: host=priority;host2=priority;*=priority + * where "*" is used to override the default priority string for + * libpurple. + */ + host_priorities_str = g_getenv("PURPLE_GNUTLS_PRIORITIES"); + if (host_priorities_str) { +#ifndef HAVE_GNUTLS_PRIORITY_FUNCS + purple_debug_warning("gnutls", "Warning, PURPLE_GNUTLS_PRIORITIES " + "environment variable set, but we were built " + "against an older GnuTLS that doesn't support " + "this. :-("); +#else /* HAVE_GNUTLS_PRIORITY_FUNCS */ + char **entries = g_strsplit(host_priorities_str, ";", -1); + guint i; + + host_priorities = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + + for (i = 0; entries[i]; ++i) { + char *host = entries[i]; + char *equals = strchr(host, '='); + char *prio_str; + + if (equals) { + *equals = '\0'; + prio_str = equals + 1; + + /* Empty? */ + if (*prio_str == '\0') { + purple_debug_warning("gnutls", "Ignoring empty priority " + "string for %s\n", host); + } else { + /* TODO: Validate each of these and complain */ + if (g_str_equal(host, "*")) { + /* Override the default priority */ + g_free(default_priority); + default_priority = g_strdup(prio_str); + } else + g_hash_table_insert(host_priorities, g_strdup(host), + g_strdup(prio_str)); + } + } + } + + g_strfreev(entries); +#endif /* HAVE_GNUTLS_PRIORITY_FUNCS */ + } + +#ifdef HAVE_GNUTLS_PRIORITY_FUNCS + /* Make sure we set have a default priority! */ + if (!default_priority) + default_priority = g_strdup("NORMAL:%SSL3_RECORD_VERSION"); +#endif /* HAVE_GNUTLS_PRIORITY_FUNCS */ + gnutls_global_init(); gnutls_certificate_allocate_credentials(&xcred); @@ -103,6 +166,17 @@ gnutls_global_deinit(); gnutls_certificate_free_credentials(xcred); + xcred = NULL; + +#ifdef HAVE_GNUTLS_PRIORITY_FUNCS + if (host_priorities) { + g_hash_table_destroy(host_priorities); + host_priorities = NULL; + } + + g_free(default_priority); + default_priority = NULL; +#endif } static void @@ -280,9 +354,27 @@ gnutls_init(&gnutls_data->session, GNUTLS_CLIENT); #ifdef HAVE_GNUTLS_PRIORITY_FUNCS - if (gnutls_priority_set_direct(gnutls_data->session, - "NORMAL:%SSL3_RECORD_VERSION", NULL)) - gnutls_priority_set_direct(gnutls_data->session, "NORMAL", NULL); + { + const char *prio_str = NULL; + + /* Let's see if someone has specified a specific priority */ + if (gsc->host && host_priorities) + prio_str = g_hash_table_lookup(host_priorities, gsc->host); + + /* If not, let's use the default! */ + if (!prio_str) + prio_str = default_priority; + + /* TODO: Use a gnutls_priority_t cache, so this doesn't require three levels! */ + /* The logic here is to try the specified string, fall back to the default + * (which may also be user-specified), and if *that* doesn't work, fall back + * to the default default (which I'm not sure is necessary, but whatever). + */ + if (gnutls_priority_set_direct(gnutls_data->session, + prio_str, NULL)) + if (gnutls_priority_set_direct(gnutls_data->session, default_priority, NULL)) + gnutls_priority_set_direct(gnutls_data->session, "NORMAL", NULL); + } #else gnutls_set_default_priority(gnutls_data->session); #endif