changeset 29629:816223a49038

merge of '584de5e77d7a29a10037eba7c064d791f2814821' and '65542da15a8b1f08f18238f3d7223dc65f983822'
author John Bailey <rekkanoryo@rekkanoryo.org>
date Sun, 08 Nov 2009 23:38:08 +0000
parents 6652fdb8baf5 (current diff) e77894113a14 (diff)
children be31e811cb26
files
diffstat 33 files changed, 533 insertions(+), 247 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Sun Nov 08 23:36:28 2009 +0000
+++ b/COPYRIGHT	Sun Nov 08 23:38:08 2009 +0000
@@ -268,6 +268,7 @@
 Ambrose C. Li
 Nicolas Lichtmaier
 Wesley Lin
+Shaun Lindsay
 Artem Litvinovich
 Josh Littlefield
 Daniel Ljungborg
--- a/ChangeLog	Sun Nov 08 23:36:28 2009 +0000
+++ b/ChangeLog	Sun Nov 08 23:38:08 2009 +0000
@@ -1,3 +1,4 @@
+
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
 version 2.7.0 (??/??/????):
@@ -26,12 +27,23 @@
 	* Fix DNS TXT query resolution.
 	* Always rejoin open chats after an account reconnects.
 
+	AIM and ICQ:
+	* Better rate limit calculations and other improvements.  (Aman Gupta)
+	* More detailed error messages when messages fail to send.  (Aman Gupta)
+	* The simultaneous login account option is respected when using
+	  the clientLogin authentication method.
+	* Fix offline message retrieval (broken in 2.6.3)
+	* Fix SSL when clientLogin is enabled.
+
 	MSN:
 	* Don't forget display names for buddies.
 	* Fix a random crash that might occur when idle.
 	* Fix more FQY 240 connection errors.
 	* Fix a crash that could occur when adding a buddy.
 	* Fix an occasional crash when sending message to an offline user.
+	* Fix a random crash that might occur when idle.
+	* Fix a crash when logging in with some long non-ASCII passwords.
+	  (Shaun Lindsay)
 
 	XMPP:
 	* Users connecting to Google Talk now have an "Initiate Chat" context menu
@@ -39,6 +51,9 @@
 	* Fix a crash when attempting to validate an invalid JID.
 	* Resolve an issue when connecting to iChat Server when no resource
 	  is specified.
+	* Try to automatically find a STUN server by using an SRV lookup on the
+	  account's domain, and use that for voice and video if found and the user 
+	  didn't set one manually in prefs.
 	* Fix a crash when adding a buddy without an '@'.
 
 	Yahoo:
@@ -58,12 +73,6 @@
 	* The userlist in a multiuser chat can be styled via gtkrc by using the
 	  widget name "pidgin_conv_userlist". (Heiko Schmitt)
 	* Add a hold button to the media window.
-	* Tooltips for custom smileys should work now.
-	* Users with unread messages are again bolded in the Buddy List.
-	* Minor reworking of the Preferences window's Network tab to make it need
-	  less vertical space.
-	* The global "Use remote DNS with SOCKS4 proxies" preference no longer
-	  disappears when the preference value changes in certain ways.
 
 version 2.6.3 (10/16/2009):
 	General:
--- a/finch/libgnt/gnttree.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/finch/libgnt/gnttree.c	Sun Nov 08 23:38:08 2009 +0000
@@ -815,7 +815,7 @@
 		gnt_widget_activate(widget);
 	} else if (tree->priv->search) {
 		gboolean changed = TRUE;
-		if (isalnum(*text)) {
+		if (g_unichar_isprint(*text)) {
 			tree->priv->search = g_string_append_c(tree->priv->search, *text);
 		} else if (g_utf8_collate(text, GNT_KEY_BACKSPACE) == 0) {
 			if (tree->priv->search->len)
--- a/libpurple/protocols/bonjour/mdns_avahi.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Sun Nov 08 23:38:08 2009 +0000
@@ -150,6 +150,10 @@
 			}
 			break;
 		case AVAHI_RESOLVER_FOUND:
+
+			purple_debug_info("bonjour", "_resolve_callback - name:%s account:%p bb:%p\n",
+				name, account, bb);
+
 			/* create a buddy record */
 			if (bb == NULL)
 				bb = bonjour_buddy_new(name, account);
@@ -173,8 +177,12 @@
 
 
 			/* Get the ip as a string */
+			ip[0] = '\0';
 			avahi_address_snprint(ip, AVAHI_ADDRESS_STR_MAX, a);
 
+			purple_debug_info("bonjour", "_resolve_callback - name:%s ip:%s prev_ip:%s\n",
+				name, ip, rd->ip);
+
 			if (rd->ip == NULL || strcmp(rd->ip, ip) != 0) {
 				/* We store duplicates in bb->ips, so we always remove the one */
 				if (rd->ip != NULL) {
--- a/libpurple/protocols/jabber/auth.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/auth.c	Sun Nov 08 23:38:08 2009 +0000
@@ -58,7 +58,7 @@
 				PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
 				_("Server requires TLS/SSL, but no TLS/SSL support was found."));
 			return TRUE;
-		} else if(purple_account_get_bool(js->gc->account, "require_tls", FALSE)) {
+		} else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
 			purple_connection_error_reason(js->gc,
 				 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
 				_("You require encryption, but no TLS/SSL support was found."));
@@ -381,13 +381,13 @@
 				 * due to mechanism specific issues, so we want to try one of the other
 				 * supported mechanisms. This code handles that case
 				 */
-				if (js->current_mech && strlen(js->current_mech) > 0) {
+				if (js->current_mech && *js->current_mech) {
 					char *pos;
 					if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) {
 						g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech));
 					}
 					/* Remove space which separated this mech from the next */
-					if (strlen(js->sasl_mechs->str) > 0 && ((js->sasl_mechs->str)[0] == ' ')) {
+					if ((js->sasl_mechs->str)[0] == ' ') {
 						g_string_erase(js->sasl_mechs, 0, 1);
 					}
 					again = TRUE;
@@ -511,7 +511,7 @@
 		 * support it and including it gives a false fall-back to other mechs offerred,
 		 * leading to incorrect error handling.
 		 */
-		if (mech_name && !strcmp(mech_name, "X-GOOGLE-TOKEN")) {
+		if (purple_strequal(mech_name, "X-GOOGLE-TOKEN")) {
 			g_free(mech_name);
 			continue;
 		}
@@ -519,9 +519,9 @@
 		g_string_append(js->sasl_mechs, mech_name);
 		g_string_append_c(js->sasl_mechs, ' ');
 #else
-		if(mech_name && !strcmp(mech_name, "DIGEST-MD5"))
+		if (purple_strequal(mech_name, "DIGEST-MD5"))
 			digest_md5 = TRUE;
-		else if(mech_name && !strcmp(mech_name, "PLAIN"))
+		else if (purple_strequal(mech_name, "PLAIN"))
 			plain = TRUE;
 #endif
 		g_free(mech_name);
@@ -586,7 +586,7 @@
 		/* FIXME: Why is this not in jabber_parse_error? */
 		if((error = xmlnode_get_child(packet, "error")) &&
 					(err_code = xmlnode_get_attrib(error, "code")) &&
-					!strcmp(err_code, "401")) {
+					g_str_equal(err_code, "401")) {
 			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
 			/* Clear the pasword if it isn't being saved */
 			if (!purple_account_get_remember_password(js->gc->account))
@@ -698,7 +698,7 @@
 	 * is requiring SSL/TLS, we need to enforce it.
 	 */
 	if (!jabber_stream_is_ssl(js) &&
-			purple_account_get_bool(purple_connection_get_account(js->gc), "require_tls", FALSE)) {
+			purple_account_get_bool(purple_connection_get_account(js->gc), "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
 		purple_connection_error_reason(js->gc,
 			PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
 			_("You require encryption, but it is not available on this server."));
@@ -877,7 +877,7 @@
 		}
 
 		dec_in = (char *)purple_base64_decode(enc_in, NULL);
-		purple_debug(PURPLE_DEBUG_MISC, "jabber", "decoded challenge (%"
+		purple_debug_misc("jabber", "decoded challenge (%"
 				G_GSIZE_FORMAT "): %s\n", strlen(dec_in), dec_in);
 
 		parts = parse_challenge(dec_in);
@@ -887,8 +887,7 @@
 			char *rspauth = g_hash_table_lookup(parts, "rspauth");
 
 
-			if(rspauth && js->expected_rspauth &&
-					!strcmp(rspauth, js->expected_rspauth)) {
+			if (rspauth && purple_strequal(rspauth, js->expected_rspauth)) {
 				jabber_send_raw(js,
 						"<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />",
 						-1);
@@ -1014,7 +1013,7 @@
 				 * realm are always encoded in UTF-8 (they seem to be the values
 				 * we pass in), so we need to ensure charset=utf-8 is set.
 				 */
-				if (!js->current_mech || !g_str_equal(js->current_mech, "DIGEST-MD5") ||
+				if (!purple_strequal(js->current_mech, "DIGEST-MD5") ||
 						strstr(c_out, ",charset="))
 					/* If we're not using DIGEST-MD5 or Cyrus SASL is fixed */
 					enc_out = purple_base64_encode((unsigned char*)c_out, clen);
@@ -1041,7 +1040,7 @@
 	const void *x;
 #endif
 
-	if(!ns || strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) {
+	if (!purple_strequal(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) {
 		purple_connection_error_reason(js->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Invalid response from server"));
@@ -1072,6 +1071,7 @@
 			purple_connection_error_reason(js->gc,
 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Invalid response from server"));
+			g_return_if_reached();
 		}
 	}
 	/* If we've negotiated a security layer, we need to enable it */
@@ -1099,17 +1099,17 @@
 
 #ifdef HAVE_CYRUS_SASL
 	if(js->auth_fail_count++ < 5) {
-		if (js->current_mech && strlen(js->current_mech) > 0) {
+		if (js->current_mech && *js->current_mech) {
 			char *pos;
 			if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) {
 				g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech));
 			}
 			/* Remove space which separated this mech from the next */
-			if (strlen(js->sasl_mechs->str) > 0 && ((js->sasl_mechs->str)[0] == ' ')) {
+			if ((js->sasl_mechs->str)[0] == ' ') {
 				g_string_erase(js->sasl_mechs, 0, 1);
 			}
 		}
-		if (strlen(js->sasl_mechs->str)) {
+		if (*js->sasl_mechs->str) {
 			/* If we have remaining mechs to try, do so */
 			sasl_dispose(&js->sasl);
 
--- a/libpurple/protocols/jabber/buddy.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Sun Nov 08 23:38:08 2009 +0000
@@ -581,8 +581,7 @@
 		if (text != NULL && *text != '\0') {
 			xmlnode *xp;
 
-			purple_debug(PURPLE_DEBUG_INFO, "jabber",
-					"Setting %s to '%s'\n", vc_tp->tag, text);
+			purple_debug_info("jabber", "Setting %s to '%s'\n", vc_tp->tag, text);
 
 			if ((xp = insert_tag_to_parent_tag(vc_node,
 											   NULL, vc_tp->tag)) != NULL) {
--- a/libpurple/protocols/jabber/disco.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/disco.c	Sun Nov 08 23:38:08 2009 +0000
@@ -421,6 +421,76 @@
 
 }
 
+/* should probably share this code with google.c, or maybe from 2.7.0
+ introduce an abstracted hostname -> IP function in dns.c */
+static void
+jabber_disco_stun_lookup_cb(GSList *hosts, gpointer data,
+	const char *error_message)
+{
+	JabberStream *js = (JabberStream *) data;
+
+	if (error_message) {
+		purple_debug_error("jabber", "STUN lookup failed: %s\n",
+			error_message);
+		g_slist_free(hosts);
+		js->stun_query = NULL;
+		return;
+	}
+
+	if (hosts && g_slist_next(hosts)) {
+		struct sockaddr *addr = g_slist_next(hosts)->data;
+		char dst[INET6_ADDRSTRLEN];
+		int port;
+
+		if (addr->sa_family == AF_INET6) {
+			inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr,
+				dst, sizeof(dst));
+			port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port);
+		} else {
+			inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr,
+				dst, sizeof(dst));
+			port = ntohs(((struct sockaddr_in *) addr)->sin_port);
+		}
+
+		if (js->stun_ip)
+			g_free(js->stun_ip);
+		js->stun_ip = g_strdup(dst);
+		js->stun_port = port;
+
+		purple_debug_info("jabber", "set STUN IP/port address: "
+		                  "%s:%d\n", dst, port);
+
+		/* unmark ongoing query */
+		js->stun_query = NULL;
+	}
+
+	while (hosts != NULL) {
+		hosts = g_slist_delete_link(hosts, hosts);
+		/* Free the address */
+		g_free(hosts->data);
+		hosts = g_slist_delete_link(hosts, hosts);
+	}
+}
+
+
+static void
+jabber_disco_stun_srv_resolve_cb(PurpleSrvResponse *resp, int results, gpointer data)
+{
+	JabberStream *js = (JabberStream *) data;
+
+	purple_debug_info("jabber", "got %d SRV responses for STUN.\n", results);
+	js->srv_query_data = NULL;
+
+	if (results > 0) {
+		purple_debug_info("jabber", "looking up IP for %s:%d\n", 
+			resp[0].hostname, resp[0].port);
+		js->stun_query = 
+			purple_dnsquery_a(resp[0].hostname, resp[0].port, 
+				jabber_disco_stun_lookup_cb, js);
+	}
+}
+
+
 static void
 jabber_disco_server_info_result_cb(JabberStream *js, const char *from,
                                    JabberIqType type, const char *id,
@@ -471,7 +541,10 @@
 			/* autodiscover stun and relays */
 			jabber_google_send_jingle_info(js);
 		} else {
-			/* TODO: add external service discovery here... */
+			js->srv_query_data = 
+				purple_srv_resolve("stun", "udp", js->user->domain,
+					jabber_disco_stun_srv_resolve_cb, js);
+			/* TODO: add TURN support later... */
 		}
 	}
 
--- a/libpurple/protocols/jabber/google.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/google.c	Sun Nov 08 23:38:08 2009 +0000
@@ -910,7 +910,7 @@
 	xmlnode_set_attrib(iq->node, "id", id);
 	jabber_iq_send(iq);
 
-	purple_debug(PURPLE_DEBUG_MISC, "jabber",
+	purple_debug_misc("jabber",
 		   "Got new mail notification. Sending request for more info\n");
 
 	iq = jabber_iq_new_query(js, JABBER_IQ_GET, "google:mail:notify");
@@ -1094,12 +1094,13 @@
 			jbr = l->data;
 			if (jbr && jbr->name)
 			{
-				purple_debug(PURPLE_DEBUG_MISC, "jabber", "Removing resource %s\n", jbr->name);
+				purple_debug_misc("jabber", "Removing resource %s\n", jbr->name);
 				jabber_buddy_remove_resource(jb, jbr->name);
 			}
 			l = l->next;
 		}
 	}
+
 	purple_prpl_got_user_status(purple_connection_get_account(gc), who, "offline", NULL);
 }
 
--- a/libpurple/protocols/jabber/iq.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/iq.c	Sun Nov 08 23:38:08 2009 +0000
@@ -342,7 +342,7 @@
 		return;
 	}
 
-	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
+	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc),
 			"jabber-receiving-iq", js->gc, iq_type, id, from, packet));
 	if (signal_return)
 		return;
@@ -367,7 +367,7 @@
 		g_free(key);
 
 		if (signal_ref > 0) {
-			signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, "jabber-watched-iq",
+			signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), "jabber-watched-iq",
 					js->gc, iq_type, id, from, child));
 			if (signal_return)
 				return;
--- a/libpurple/protocols/jabber/jabber.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sun Nov 08 23:38:08 2009 +0000
@@ -68,10 +68,9 @@
 #include "jingle/jingle.h"
 #include "jingle/rtp.h"
 
-PurplePlugin *jabber_plugin = NULL;
 GList *jabber_features = NULL;
 GList *jabber_identities = NULL;
-GSList *jabber_cmds = NULL;
+static GSList *jabber_cmds = NULL;
 
 static void jabber_unregister_account_cb(JabberStream *js);
 static void try_srv_connect(JabberStream *js);
@@ -200,7 +199,7 @@
 			jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
 			return;
 		}
-	} else if(purple_account_get_bool(js->gc->account, "require_tls", FALSE) && !jabber_stream_is_ssl(js)) {
+	} else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS) && !jabber_stream_is_ssl(js)) {
 		purple_connection_error_reason(js->gc,
 			 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
 			_("You require encryption, but it is not available on this server."));
@@ -255,7 +254,7 @@
 {
 	const char *xmlns;
 
-	purple_signal_emit(jabber_plugin, "jabber-receiving-xmlnode", js->gc, packet);
+	purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-receiving-xmlnode", js->gc, packet);
 
 	/* if the signal leaves us with a null packet, we're done */
 	if(NULL == *packet)
@@ -294,8 +293,7 @@
 		else
 			purple_debug_warning("jabber", "Ignoring spurious <proceed/>\n");
 	} else {
-		purple_debug(PURPLE_DEBUG_WARNING, "jabber", "Unknown packet: %s\n",
-				(*packet)->name);
+		purple_debug_warning("jabber", "Unknown packet: %s\n", (*packet)->name);
 	}
 }
 
@@ -377,9 +375,9 @@
 
 void jabber_send_raw(JabberStream *js, const char *data, int len)
 {
-
 	/* because printing a tab to debug every minute gets old */
 	if(strcmp(data, "\t")) {
+		const char *username;
 		char *text = NULL, *last_part = NULL, *tag_start = NULL;
 
 		/* Because debug logs with plaintext passwords make me sad */
@@ -404,8 +402,13 @@
 			*data_start = '\0';
 		}
 
-		purple_debug(PURPLE_DEBUG_MISC, "jabber", "Sending%s: %s%s%s\n",
-				jabber_stream_is_ssl(js) ? " (ssl)" : "", text ? text : data,
+		username = purple_connection_get_display_name(js->gc);
+		if (!username)
+			username = purple_account_get_username(purple_connection_get_account(js->gc));
+
+		purple_debug_misc("jabber", "Sending%s (%s): %s%s%s\n",
+				jabber_stream_is_ssl(js) ? " (ssl)" : "", username,
+				text ? text : data,
 				last_part ? "password removed" : "",
 				last_part ? last_part : "");
 
@@ -415,7 +418,7 @@
 	/* If we've got a security layer, we need to encode the data,
 	 * splitting it on the maximum buffer length negotiated */
 
-	purple_signal_emit(jabber_plugin, "jabber-sending-text", js->gc, &data);
+	purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-sending-text", js->gc, &data);
 	if (data == NULL)
 		return;
 
@@ -485,7 +488,7 @@
 
 void jabber_send(JabberStream *js, xmlnode *packet)
 {
-	purple_signal_emit(jabber_plugin, "jabber-sending-xmlnode", js->gc, &packet);
+	purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-sending-xmlnode", js->gc, &packet);
 }
 
 static gboolean jabber_keepalive_timeout(PurpleConnection *gc)
@@ -526,7 +529,7 @@
 	while((len = purple_ssl_read(gsc, buf, sizeof(buf) - 1)) > 0) {
 		gc->last_received = time(NULL);
 		buf[len] = '\0';
-		purple_debug(PURPLE_DEBUG_INFO, "jabber", "Recv (ssl)(%d): %s\n", len, buf);
+		purple_debug_info("jabber", "Recv (ssl)(%d): %s\n", len, buf);
 		jabber_parser_process(js, buf, len);
 		if(js->reinit)
 			jabber_stream_init(js);
@@ -566,7 +569,7 @@
 			unsigned int olen;
 			sasl_decode(js->sasl, buf, len, &out, &olen);
 			if (olen>0) {
-				purple_debug(PURPLE_DEBUG_INFO, "jabber", "RecvSASL (%u): %s\n", olen, out);
+				purple_debug_info("jabber", "RecvSASL (%u): %s\n", olen, out);
 				jabber_parser_process(js,out,olen);
 				if(js->reinit)
 					jabber_stream_init(js);
@@ -575,7 +578,7 @@
 		}
 #endif
 		buf[len] = '\0';
-		purple_debug(PURPLE_DEBUG_INFO, "jabber", "Recv (%d): %s\n", len, buf);
+		purple_debug_info("jabber", "Recv (%d): %s\n", len, buf);
 		jabber_parser_process(js, buf, len);
 		if(js->reinit)
 			jabber_stream_init(js);
@@ -3401,8 +3404,6 @@
 								unspecified */
 	const gchar *ui_name = NULL;
 
-	jabber_plugin = plugin;
-
 	ui_type = ui_info ? g_hash_table_lookup(ui_info, "client_type") : NULL;
 	if (ui_type) {
 		if (strcmp(ui_type, "pc") == 0 ||
@@ -3495,9 +3496,9 @@
 }
 
 void
-jabber_uninit_plugin(void)
+jabber_uninit_plugin(PurplePlugin *plugin)
 {
-	purple_plugin_ipc_unregister_all(jabber_plugin);
+	purple_plugin_ipc_unregister_all(plugin);
 
 	jabber_features_destroy();
 	jabber_identities_destroy();
--- a/libpurple/protocols/jabber/jabber.h	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Sun Nov 08 23:38:08 2009 +0000
@@ -76,11 +76,11 @@
 
 #define CAPS0115_NODE "http://pidgin.im/"
 
+#define JABBER_DEFAULT_REQUIRE_TLS    TRUE
+
 /* Index into attention_types list */
 #define JABBER_BUZZ 0
 
-extern PurplePlugin *jabber_plugin;
-
 typedef enum {
 	JABBER_STREAM_OFFLINE,
 	JABBER_STREAM_CONNECTING,
@@ -193,25 +193,16 @@
 
 	char *serverFQDN;
 
-	/* OK, this stays at the end of the struct, so plugins can depend
-	 * on the rest of the stuff being in the right place
-	 */
 #ifdef HAVE_CYRUS_SASL
 	sasl_conn_t *sasl;
 	sasl_callback_t *sasl_cb;
-#else /* keep the struct the same size */
-	void *sasl;
-	void *sasl_cb;
-#endif
-	/* did someone say something about the end of the struct? */
-#ifdef HAVE_CYRUS_SASL
 	const char *current_mech;
 	int auth_fail_count;
-#endif
 
 	int sasl_state;
 	int sasl_maxbuf;
 	GString *sasl_mechs;
+#endif
 
 	gboolean unregistration;
 	PurpleAccountUnregistrationCb unregistration_cb;
@@ -382,6 +373,6 @@
 void jabber_unregister_commands(void);
 
 void jabber_init_plugin(PurplePlugin *plugin);
-void jabber_uninit_plugin(void);
+void jabber_uninit_plugin(PurplePlugin *plugin);
 
 #endif /* PURPLE_JABBER_H_ */
--- a/libpurple/protocols/jabber/jingle/jingle.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.c	Sun Nov 08 23:38:08 2009 +0000
@@ -442,15 +442,15 @@
 	if (num_params > 0) {
 		params = g_new0(GParameter, num_params);
 
-		purple_debug_info("jabber", 
-						  "setting param stun-ip for stream using Google auto-config: %s\n",
-						  js->stun_ip);
+		purple_debug_info("jabber",
+			"setting param stun-ip for stream using auto-discovered IP: %s\n",
+			js->stun_ip);
 		params[0].name = "stun-ip";
 		g_value_init(&params[0].value, G_TYPE_STRING);
 		g_value_set_string(&params[0].value, js->stun_ip);
 		purple_debug_info("jabber", 
-						  "setting param stun-port for stream using Google auto-config: %d\n",
-						  js->stun_port);
+			"setting param stun-port for stream using auto-discovered port: %d\n",
+			js->stun_port);
 		params[1].name = "stun-port";
 		g_value_init(&params[1].value, G_TYPE_UINT);
 		g_value_set_uint(&params[1].value, js->stun_port);
--- a/libpurple/protocols/jabber/libxmpp.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Sun Nov 08 23:38:08 2009 +0000
@@ -227,7 +227,7 @@
 	jabber_unregister_commands();
 
 	/* Stay on target...stay on target... Almost there... */
-	jabber_uninit_plugin();
+	jabber_uninit_plugin(plugin);
 
 	return TRUE;
 }
@@ -355,7 +355,7 @@
 	purple_account_user_split_set_reverse(split, FALSE);
 	prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
 
-	option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", TRUE);
+	option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", JABBER_DEFAULT_REQUIRE_TLS);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
 											   option);
 
--- a/libpurple/protocols/jabber/message.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/message.c	Sun Nov 08 23:38:08 2009 +0000
@@ -545,7 +545,7 @@
 	to   = xmlnode_get_attrib(packet, "to");
 	type = xmlnode_get_attrib(packet, "type");
 
-	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
+	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc),
 			"jabber-receiving-message", js->gc, type, id, from, to, packet));
 	if (signal_return)
 		return;
@@ -812,7 +812,7 @@
 
 	switch(jm->type) {
 		case JABBER_MESSAGE_OTHER:
-			purple_debug(PURPLE_DEBUG_INFO, "jabber",
+			purple_debug_info("jabber",
 					"Received message of unknown type: %s\n", type);
 			/* Fall-through is intentional */
 		case JABBER_MESSAGE_NORMAL:
@@ -1103,7 +1103,7 @@
 		if ((child = xmlnode_from_str(jm->xhtml, -1))) {
 			xmlnode_insert_child(message, child);
 		} else {
-			purple_debug(PURPLE_DEBUG_ERROR, "jabber",
+			purple_debug_error("jabber",
 					"XHTML translation/validation failed, returning: %s\n",
 					jm->xhtml);
 		}
--- a/libpurple/protocols/jabber/oob.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/oob.c	Sun Nov 08 23:38:08 2009 +0000
@@ -95,7 +95,7 @@
 	if(len < 0 && errno == EAGAIN)
 		return;
 	else if(len < 0) {
-		purple_debug(PURPLE_DEBUG_ERROR, "jabber", "Write error on oob xfer!\n");
+		purple_debug_error("jabber", "Write error on oob xfer!\n");
 		purple_input_remove(jox->writeh);
 		purple_xfer_cancel_local(xfer);
 	}
@@ -150,7 +150,7 @@
 		}
 		return 0;
 	} else if (errno != EAGAIN) {
-		purple_debug(PURPLE_DEBUG_ERROR, "jabber", "Read error on oob xfer!\n");
+		purple_debug_error("jabber", "Read error on oob xfer!\n");
 		purple_xfer_cancel_local(xfer);
 	}
 
--- a/libpurple/protocols/jabber/presence.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Sun Nov 08 23:38:08 2009 +0000
@@ -522,7 +522,7 @@
 	jb = jabber_buddy_find(js, from, TRUE);
 	g_return_if_fail(jb != NULL);
 
-	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
+	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc),
 			"jabber-receiving-presence", js->gc, type, from, packet));
 	if (signal_return)
 		return;
--- a/libpurple/protocols/jabber/si.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/si.c	Sun Nov 08 23:38:08 2009 +0000
@@ -1357,7 +1357,7 @@
 		jabber_ibb_session_close(jsx->ibb_session);
 	}
 	jabber_si_xfer_free(xfer);
-	purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_send\n");
+	purple_debug_info("jabber", "in jabber_si_xfer_cancel_send\n");
 }
 
 
@@ -1389,7 +1389,7 @@
 	}
 
 	jabber_si_xfer_free(xfer);
-	purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_request_denied\n");
+	purple_debug_info("jabber", "in jabber_si_xfer_request_denied\n");
 }
 
 
@@ -1401,7 +1401,7 @@
 		jabber_ibb_session_close(jsx->ibb_session);
 	}
 	jabber_si_xfer_free(xfer);
-	purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_recv\n");
+	purple_debug_info("jabber", "in jabber_si_xfer_cancel_recv\n");
 }
 
 
--- a/libpurple/protocols/jabber/useravatar.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/jabber/useravatar.c	Sun Nov 08 23:38:08 2009 +0000
@@ -262,7 +262,7 @@
 	gpointer icon_data;
 
 	if(!url_text) {
-		purple_debug(PURPLE_DEBUG_ERROR, "jabber",
+		purple_debug_error("jabber",
 		             "do_buddy_avatar_update_fromurl got error \"%s\"",
 		             error_message);
 		goto out;
--- a/libpurple/protocols/msn/nexus.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/msn/nexus.c	Sun Nov 08 23:38:08 2009 +0000
@@ -385,7 +385,14 @@
 
 	username = purple_account_get_username(session->account);
 	password = purple_connection_get_password(session->account->gc);
-	password_xml = g_markup_escape_text(password, MIN(strlen(password), 16));
+	if (g_utf8_strlen(password, -1) > 16) {
+		/* max byte size for 16 utf8 characters is 64 + 1 for the null */
+		gchar truncated[65];
+		g_utf8_strncpy(truncated, password, 16);
+		password_xml = g_markup_escape_text(truncated, -1);
+	} else {
+		password_xml = g_markup_escape_text(password, -1);
+	}
 
 	purple_debug_info("msn", "Logging on %s, with policy '%s', nonce '%s'\n",
 	                  username, nexus->policy, nexus->nonce);
--- a/libpurple/protocols/oscar/clientlogin.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/oscar/clientlogin.c	Sun Nov 08 23:38:08 2009 +0000
@@ -40,6 +40,7 @@
 #include "core.h"
 
 #include "oscar.h"
+#include "oscarcommon.h"
 
 #define URL_CLIENT_LOGIN "https://api.screenname.aol.com/auth/clientLogin"
 #define URL_START_OSCAR_SESSION "http://api.oscar.aol.com/aim/startOSCARSession"
@@ -102,11 +103,15 @@
 	return signature;
 }
 
-static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **host, unsigned short *port, char **cookie)
+static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **host, unsigned short *port, char **cookie, char **tls_certname)
 {
 	xmlnode *response_node, *tmp_node, *data_node;
-	xmlnode *host_node = NULL, *port_node = NULL, *cookie_node = NULL;
+	xmlnode *host_node = NULL, *port_node = NULL, *cookie_node = NULL, *tls_node = NULL;
+	gboolean use_tls;
 	char *tmp;
+	guint code;
+
+	use_tls = purple_account_get_bool(purple_connection_get_account(gc), "use_ssl", OSCAR_DEFAULT_USE_SSL);
 
 	/* Parse the response as XML */
 	response_node = xmlnode_from_str(response, response_len);
@@ -131,6 +136,7 @@
 		host_node = xmlnode_get_child(data_node, "host");
 		port_node = xmlnode_get_child(data_node, "port");
 		cookie_node = xmlnode_get_child(data_node, "cookie");
+		tls_node = xmlnode_get_child(data_node, "tlsCertName");
 	}
 
 	/* Make sure we have a status code */
@@ -148,12 +154,13 @@
 	}
 
 	/* Make sure the status code was 200 */
-	if (strcmp(tmp, "200") != 0)
+	code = atoi(tmp);
+	if (code != 200)
 	{
 		purple_debug_error("oscar", "startOSCARSession response statusCode "
 				"was %s: %s\n", tmp, response);
 
-		if (strcmp(tmp, "401") == 0)
+		if (code == 401 || code == 607)
 			purple_connection_error_reason(gc,
 					PURPLE_CONNECTION_ERROR_OTHER_ERROR,
 					_("You have been connecting and disconnecting too "
@@ -177,7 +184,8 @@
 
 	/* Make sure we have everything else */
 	if (data_node == NULL || host_node == NULL ||
-		port_node == NULL || cookie_node == NULL)
+		port_node == NULL || cookie_node == NULL ||
+		(use_tls && tls_node == NULL))
 	{
 		char *msg;
 		purple_debug_error("oscar", "startOSCARSession response was missing "
@@ -195,7 +203,12 @@
 	*host = xmlnode_get_data_unescaped(host_node);
 	tmp = xmlnode_get_data_unescaped(port_node);
 	*cookie = xmlnode_get_data_unescaped(cookie_node);
-	if (*host == NULL || **host == '\0' || tmp == NULL || *tmp == '\0' || cookie == NULL || *cookie == '\0')
+
+	if (use_tls)
+		*tls_certname = xmlnode_get_data_unescaped(tls_node);
+
+	if (*host == NULL || **host == '\0' || tmp == NULL || *tmp == '\0' || *cookie == NULL || **cookie == '\0' ||
+			(use_tls && (*tls_certname == NULL || **tls_certname == '\0')))
 	{
 		char *msg;
 		purple_debug_error("oscar", "startOSCARSession response was missing "
@@ -223,6 +236,7 @@
 	OscarData *od;
 	PurpleConnection *gc;
 	char *host, *cookie;
+	char *tls_certname = NULL;
 	unsigned short port;
 	guint8 *cookiedata;
 	gsize cookiedata_len;
@@ -244,28 +258,30 @@
 		return;
 	}
 
-	if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie))
+	if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie, &tls_certname))
 		return;
 
 	cookiedata = purple_base64_decode(cookie, &cookiedata_len);
-	oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len);
+	oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len, tls_certname);
 	g_free(cookiedata);
 
 	g_free(host);
 	g_free(cookie);
+	g_free(tls_certname);
 }
 
 static void send_start_oscar_session(OscarData *od, const char *token, const char *session_key, time_t hosttime)
 {
 	char *query_string, *signature, *url;
+	gboolean use_tls = purple_account_get_bool(purple_connection_get_account(od->gc), "use_ssl", OSCAR_DEFAULT_USE_SSL);
 
 	/* Construct the GET parameters */
 	query_string = g_strdup_printf("a=%s"
 			"&f=xml"
 			"&k=%s"
 			"&ts=%" PURPLE_TIME_T_MODIFIER
-			"&useTLS=0",
-			purple_url_encode(token), get_client_key(od), hosttime);
+			"&useTLS=%d",
+			purple_url_encode(token), get_client_key(od), hosttime, use_tls);
 	signature = generate_signature("GET", URL_START_OSCAR_SESSION,
 			query_string, session_key);
 	url = g_strdup_printf(URL_START_OSCAR_SESSION "?%s&sig_sha256=%s",
--- a/libpurple/protocols/oscar/family_feedbag.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/oscar/family_feedbag.c	Sun Nov 08 23:38:08 2009 +0000
@@ -389,11 +389,10 @@
 
 /**
  * Locally find the presence flag item, and return the setting.  The returned setting is a
- * bitmask of the user flags that you are visible to.  See the AIM_FLAG_* #defines
- * in oscar.h
+ * bitmask of the preferences.  See the AIM_SSI_PRESENCE_FLAG_* #defines in oscar.h.
  *
  * @param list A pointer to the current list of items.
- * @return Return the current visibility mask.
+ * @return Return the current set of preferences.
  */
 guint32 aim_ssi_getpresence(struct aim_ssi_item *list)
 {
@@ -1130,9 +1129,11 @@
  * should show up as idle or not, etc.
  *
  * @param od The oscar odion.
- * @param presence I think it's a bitmask, but I only know what one of the bits is:
- *        0x00000002 - Hide wireless?
+ * @param presence A bitmask of the first 32 entries [0-31] from
+ *        http://dev.aol.com/aim/oscar/#FEEDBAG__BUDDY_PREFS
+ *        0x00000002 - Hide "eBuddy group" (whatever that is)
  *        0x00000400 - Allow others to see your idle time
+ *        0x00020000 - Don't show Recent Buddies
  * @return Return 0 if no errors, otherwise return the error number.
  */
 int aim_ssi_setpresence(OscarData *od, guint32 presence) {
--- a/libpurple/protocols/oscar/family_icbm.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/oscar/family_icbm.c	Sun Nov 08 23:38:08 2009 +0000
@@ -151,6 +151,55 @@
 	return AIM_CLIENTTYPE_UNKNOWN;
 }
 
+/*
+ * Subtype 0x0001 - Error
+ */
+static int
+error(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs)
+{
+	int ret = 0;
+	aim_rxcallback_t userfunc;
+	aim_snac_t *snac2;
+	guint16 reason, errcode = 0;
+	char *bn;
+	GSList *tlvlist;
+
+	if (!(snac2 = aim_remsnac(od, snac->id))) {
+		purple_debug_misc("oscar", "icbm error: received response from unknown request!\n");
+		return 0;
+	}
+
+	if (snac2->family != SNAC_FAMILY_ICBM) {
+		purple_debug_misc("oscar", "icbm error: received response from invalid request! %d\n", snac2->family);
+		g_free(snac2->data);
+		g_free(snac2);
+		return 0;
+	}
+
+	if (!(bn = snac2->data)) {
+		purple_debug_misc("oscar", "icbm error: received response from request without a buddy name!\n");
+		g_free(snac2);
+		return 0;
+	}
+
+	reason = byte_stream_get16(bs);
+
+	tlvlist = aim_tlvlist_read(bs);
+	if (aim_tlv_gettlv(tlvlist, 0x0008, 1))
+		errcode = aim_tlv_get16(tlvlist, 0x0008, 1);
+	aim_tlvlist_free(tlvlist);
+
+	/* Notify the user that the message wasn't delivered */
+	if ((userfunc = aim_callhandler(od, snac->family, snac->subtype)))
+		ret = userfunc(od, conn, frame, reason, errcode, bn);
+
+	if (snac2)
+		g_free(snac2->data);
+	g_free(snac2);
+
+	return ret;
+}
+
 /**
  * Subtype 0x0002 - Set ICBM parameters.
  *
@@ -2789,7 +2838,9 @@
 static int
 snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs)
 {
-	if (snac->subtype == 0x0005)
+	if (snac->subtype == 0x0001)
+		return error(od, conn, mod, frame, snac, bs);
+	else if (snac->subtype == 0x0005)
 		return aim_im_paraminfo(od, conn, mod, frame, snac, bs);
 	else if (snac->subtype == 0x0006)
 		return outgoingim(od, conn, mod, frame, snac, bs);
--- a/libpurple/protocols/oscar/family_oservice.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/oscar/family_oservice.c	Sun Nov 08 23:38:08 2009 +0000
@@ -319,7 +319,10 @@
 	for (i = 0; i < numclasses; i++)
 	{
 		struct rateclass *rateclass;
+		guint32 delta;
+		struct timeval now;
 
+		gettimeofday(&now, NULL);
 		rateclass = g_new0(struct rateclass, 1);
 
 		rateclass->classid = byte_stream_get16(bs);
@@ -339,11 +342,24 @@
 		 * the new version hardcoded here.
 		 */
 		if (mod->version >= 3)
-			byte_stream_getrawbuf(bs, rateclass->unknown, sizeof(rateclass->unknown));
+		{
+			rateclass->delta = byte_stream_get32(bs);
+			rateclass->dropping_snacs = byte_stream_get8(bs);
+
+			delta = rateclass->delta;
+
+			rateclass->last.tv_sec = now.tv_sec - delta / 1000;
+			delta %= 1000;
+			rateclass->last.tv_usec = now.tv_usec - delta * 1000;
+		}
+		else
+		{
+			rateclass->delta = rateclass->dropping_snacs = 0;
+			rateclass->last.tv_sec = now.tv_sec;
+			rateclass->last.tv_usec = now.tv_usec;
+		}
 
 		rateclass->members = g_hash_table_new(g_direct_hash, g_direct_equal);
-		rateclass->last.tv_sec = 0;
-		rateclass->last.tv_usec = 0;
 		conn->rateclasses = g_slist_prepend(conn->rateclasses, rateclass);
 	}
 	conn->rateclasses = g_slist_reverse(conn->rateclasses);
@@ -383,8 +399,7 @@
 	 */
 
 	/*
-	 * Last step in the conn init procedure is to acknowledge that we
-	 * agree to these draconian limitations.
+	 * Subscribe to rate change information for all rate classes.
 	 */
 	aim_srv_rates_addparam(od, conn);
 
@@ -451,7 +466,10 @@
 	aim_rxcallback_t userfunc;
 	guint16 code, classid;
 	struct rateclass *rateclass;
+	guint32 delta;
+	struct timeval now;
 
+	gettimeofday(&now, NULL);
 	code = byte_stream_get16(bs);
 	classid = byte_stream_get16(bs);
 
@@ -468,8 +486,29 @@
 	rateclass->current = byte_stream_get32(bs);
 	rateclass->max = byte_stream_get32(bs);
 
-	if ((userfunc = aim_callhandler(od, snac->family, snac->subtype)))
-		ret = userfunc(od, conn, frame, code, classid, rateclass->windowsize, rateclass->clear, rateclass->alert, rateclass->limit, rateclass->disconnect, rateclass->current, rateclass->max);
+	if (mod->version >= 3)
+	{
+		rateclass->delta = byte_stream_get32(bs);
+		rateclass->dropping_snacs = byte_stream_get8(bs);
+
+		delta = rateclass->delta;
+
+		rateclass->last.tv_sec = now.tv_sec - delta / 1000;
+		delta %= 1000;
+		rateclass->last.tv_usec = now.tv_usec - delta * 1000;
+	}
+	else
+	{
+		rateclass->delta = rateclass->dropping_snacs = 0;
+		rateclass->last.tv_sec = now.tv_sec;
+		rateclass->last.tv_usec = now.tv_usec;
+	}
+
+	if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) {
+		/* Can't pass in guint8 via ... varargs, so we use an unsigned int */
+		unsigned int dropping_snacs = rateclass->dropping_snacs;
+		ret = userfunc(od, conn, frame, code, classid, rateclass->windowsize, rateclass->clear, rateclass->alert, rateclass->limit, rateclass->disconnect, rateclass->current, rateclass->max, rateclass->delta, dropping_snacs);
+	}
 
 	return ret;
 }
--- a/libpurple/protocols/oscar/flap_connection.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Sun Nov 08 23:38:08 2009 +0000
@@ -73,7 +73,7 @@
 }
 
 void
-flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci)
+flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci, gboolean allow_multiple_logins)
 {
 	FlapFrame *frame;
 	GSList *tlvlist = NULL;
@@ -94,7 +94,7 @@
 	aim_tlvlist_add_16(&tlvlist, 0x0018, (guint16)ci->minor);
 	aim_tlvlist_add_16(&tlvlist, 0x0019, (guint16)ci->point);
 	aim_tlvlist_add_16(&tlvlist, 0x001a, (guint16)ci->build);
-	aim_tlvlist_add_8(&tlvlist, 0x004a, 0x01);
+	aim_tlvlist_add_8(&tlvlist, 0x004a, (allow_multiple_logins ? 0x01 : 0x03));
 
 	aim_tlvlist_write(&frame->data, &tlvlist);
 
@@ -131,11 +131,13 @@
 rateclass_get_new_current(FlapConnection *conn, struct rateclass *rateclass, struct timeval *now)
 {
 	unsigned long timediff; /* In milliseconds */
+	guint32 current;
 
 	timediff = (now->tv_sec - rateclass->last.tv_sec) * 1000 + (now->tv_usec - rateclass->last.tv_usec) / 1000;
+	current = ((rateclass->current * (rateclass->windowsize - 1)) + timediff) / rateclass->windowsize;
 
-	/* This formula is taken from the joscar API docs. Preesh. */
-	return MIN(((rateclass->current * (rateclass->windowsize - 1)) + timediff) / rateclass->windowsize, rateclass->max);
+	/* This formula is taken from http://dev.aol.com/aim/oscar/#RATELIMIT */
+	return MIN(current, rateclass->max);
 }
 
 /*
@@ -161,8 +163,7 @@
 
 			new_current = rateclass_get_new_current(conn, rateclass, &now);
 
-			/* (Add 100ms padding to account for inaccuracies in the calculation) */
-			if (new_current < rateclass->alert + 100)
+			if (rateclass->dropping_snacs || new_current <= rateclass->alert)
 				/* Not ready to send this SNAC yet--keep waiting. */
 				return FALSE;
 
@@ -245,10 +246,9 @@
 		gettimeofday(&now, NULL);
 		new_current = rateclass_get_new_current(conn, rateclass, &now);
 
-		/* (Add 100ms padding to account for inaccuracies in the calculation) */
-		if (new_current < rateclass->alert + 100)
+		if (rateclass->dropping_snacs || new_current <= rateclass->alert)
 		{
-			purple_debug_info("oscar", "Current rate for conn %p would be %u, but we alert at %u; enqueueing\n", conn, new_current, (rateclass->alert + 100));
+			purple_debug_info("oscar", "Current rate for conn %p would be %u, but we alert at %u; enqueueing\n", conn, new_current, rateclass->alert);
 
 			enqueue = TRUE;
 		}
--- a/libpurple/protocols/oscar/libaim.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/oscar/libaim.c	Sun Nov 08 23:38:08 2009 +0000
@@ -141,7 +141,7 @@
 static void
 init_plugin(PurplePlugin *plugin)
 {
-	oscar_init(PURPLE_PLUGIN_PROTOCOL_INFO(plugin));
+	oscar_init(plugin);
 }
 
 PURPLE_INIT_PLUGIN(aim, init_plugin, info);
--- a/libpurple/protocols/oscar/libicq.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/oscar/libicq.c	Sun Nov 08 23:38:08 2009 +0000
@@ -153,7 +153,7 @@
 {
 	PurpleAccountOption *option;
 
-	oscar_init(PURPLE_PLUGIN_PROTOCOL_INFO(plugin));
+	oscar_init(plugin);
 
 	option = purple_account_option_string_new(_("Encoding"), "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
--- a/libpurple/protocols/oscar/oscar.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Sun Nov 08 23:38:08 2009 +0000
@@ -144,6 +144,26 @@
 };
 static const int msgerrreasonlen = G_N_ELEMENTS(msgerrreason);
 
+static const char * const errcodereason[] = {
+	N_("Invalid error"),
+	N_("Not logged in"),
+	N_("Cannot receive IM due to parental controls"),
+	N_("Cannot send SMS without accepting terms"),
+	N_("Cannot send SMS"), /* SMS_WITHOUT_DISCLAIMER is weird */
+	N_("Cannot send SMS to this country"),
+	N_("Unknown error"), /* Undocumented */
+	N_("Unknown error"), /* Undocumented */
+	N_("Cannot send SMS to unknown country"),
+	N_("Bot accounts cannot initiate IMs"),
+	N_("Bot account cannot IM this user"),
+	N_("Bot account reached IM limit"),
+	N_("Bot account reached daily IM limit"),
+	N_("Bot account reached monthly IM limit"),
+	N_("Unable to receive offline messages"),
+	N_("Offline message store full")
+};
+static const int errcodereasonlen = G_N_ELEMENTS(errcodereason);
+
 /* All the libfaim->purple callback functions */
 
 /* Only used when connecting with the old-style BUCP login */
@@ -1168,7 +1188,8 @@
 			ClientInfo icqinfo = CLIENTINFO_PURPLE_ICQ;
 			flap_connection_send_version_with_cookie_and_clientinfo(od,
 					conn, conn->cookielen, conn->cookie,
-					od->icq ? &icqinfo : &aiminfo);
+					od->icq ? &icqinfo : &aiminfo,
+					purple_account_get_bool(account, "allow_multiple_logins", OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS));
 		} else {
 			flap_connection_send_version_with_cookie(od, conn,
 					conn->cookielen, conn->cookie);
@@ -1394,9 +1415,9 @@
 	presence = aim_ssi_getpresence(od->ssi.local);
 
 	if (report_idle)
-		aim_ssi_setpresence(od, presence | 0x400);
+		aim_ssi_setpresence(od, presence | AIM_SSI_PRESENCE_FLAG_SHOWIDLE);
 	else
-		aim_ssi_setpresence(od, presence & ~0x400);
+		aim_ssi_setpresence(od, presence & ~AIM_SSI_PRESENCE_FLAG_SHOWIDLE);
 }
 
 /**
@@ -1806,17 +1827,35 @@
 	return 1;
 }
 
-int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen)
+int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen, const char *tls_certname)
 {
+	PurpleAccount *account;
 	FlapConnection *conn;
 
+	account = purple_connection_get_account(gc);
+
 	conn = flap_connection_new(od, SNAC_FAMILY_LOCATE);
 	conn->cookielen = cookielen;
 	conn->cookie = g_memdup(cookie, cookielen);
-	conn->connect_data = purple_proxy_connect(NULL,
-			purple_connection_get_account(gc), host, port,
-			connection_established_cb, conn);
-	if (conn->connect_data == NULL)
+
+	/*
+	 * tls_certname is only set (and must be set if we get this far) if
+	 * SSL is enabled.
+	 */
+	if (tls_certname)
+	{
+		conn->gsc = purple_ssl_connect_with_ssl_cn(account, host, port,
+				ssl_connection_established_cb, ssl_connection_error_cb,
+				tls_certname, conn);
+	}
+	else
+	{
+		conn->connect_data = purple_proxy_connect(NULL,
+				account, host, port,
+				connection_established_cb, conn);
+	}
+
+	if (conn->gsc == NULL && conn->connect_data == NULL)
 	{
 		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect"));
 		return 0;
@@ -1877,7 +1916,7 @@
 			break;
 		case 0x18:
 			/* username connecting too frequently */
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer."));
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("Your username has been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer."));
 			break;
 		case 0x1c:
 		{
@@ -1889,7 +1928,7 @@
 		}
 		case 0x1d:
 			/* IP address connecting too frequently */
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too frequently. Wait a minute and try again. If you continue to try, you will need to wait even longer."));
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("Your IP address has been connecting and disconnecting too frequently. Wait a minute and try again. If you continue to try, you will need to wait even longer."));
 			break;
 		default:
 			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Unknown reason"));
@@ -2876,7 +2915,7 @@
 			if (text) {
 				/* Read the number of contacts that we were sent */
 				errno = 0;
-				num = strtoul(text[0], NULL, 10);
+				num = text[0] ? strtoul(text[0], NULL, 10) : 0;
 
 				if (num > 0 && errno == 0) {
 					for (i=0; i<num; i++) {
@@ -3217,17 +3256,18 @@
 	PurpleXfer *xfer;
 #endif
 	va_list ap;
-	guint16 reason;
-	char *data, *buf;
+	guint16 reason, errcode;
+	char *data, *reason_str, *buf;
 
 	va_start(ap, fr);
 	reason = (guint16)va_arg(ap, unsigned int);
+	errcode = (guint16)va_arg(ap, unsigned int);
 	data = va_arg(ap, char *);
 	va_end(ap);
 
 	purple_debug_error("oscar",
-			   "Message error with data %s and reason %hu\n",
-				(data != NULL ? data : ""), reason);
+			   "Message error with data %s and reason %hu and errcode %hu\n",
+				(data != NULL ? data : ""), reason, errcode);
 
 	if ((data == NULL) || (*data == '\0'))
 		/* We can't do anything if data is empty */
@@ -3242,14 +3282,27 @@
 #endif
 
 	/* Data is assumed to be the destination bn */
-	buf = g_strdup_printf(_("Unable to send message: %s"), (reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason."));
+
+	reason_str = g_strdup((reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason"));
+	if (errcode != 0 && errcode < errcodereasonlen)
+		buf = g_strdup_printf(_("Unable to send message: %s (%s)"), reason_str,
+		                      _(errcodereason[errcode]));
+	else
+		buf = g_strdup_printf(_("Unable to send message: %s"), reason_str);
+
 	if (!purple_conv_present_error(data, purple_connection_get_account(gc), buf)) {
 		g_free(buf);
-		buf = g_strdup_printf(_("Unable to send message to %s:"), data ? data : "(unknown)");
-		purple_notify_error(od->gc, NULL, buf,
-				  (reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason."));
+		if (errcode != 0 && errcode < errcodereasonlen)
+			buf = g_strdup_printf(_("Unable to send message to %s: %s (%s)"),
+			                      data ? data : "(unknown)", reason_str,
+			                      _(errcodereason[errcode]));
+		else
+			buf = g_strdup_printf(_("Unable to send message to %s: %s"),
+			                      data ? data : "(unknown)", reason_str);
+		purple_notify_error(od->gc, NULL, buf, reason_str);
 	}
 	g_free(buf);
+	g_free(reason_str);
 
 	return 1;
 }
@@ -3740,7 +3793,8 @@
 	};
 	va_list ap;
 	guint16 code, rateclass;
-	guint32 windowsize, clear, alert, limit, disconnect, currentavg, maxavg;
+	guint32 windowsize, clear, alert, limit, disconnect, currentavg, maxavg, delta;
+	guint8 dropping_snacs;
 
 	va_start(ap, fr);
 	code = (guint16)va_arg(ap, unsigned int);
@@ -3752,23 +3806,28 @@
 	disconnect = va_arg(ap, guint32);
 	currentavg = va_arg(ap, guint32);
 	maxavg = va_arg(ap, guint32);
+	delta = va_arg(ap, guint32);
+	dropping_snacs = (guint8)va_arg(ap, unsigned int);
 	va_end(ap);
 
 	purple_debug_misc("oscar",
 			   "rate %s (param ID 0x%04hx): curavg = %u, maxavg = %u, alert at %u, "
-		     "clear warning at %u, limit at %u, disconnect at %u (window size = %u)\n",
+		     "clear warning at %u, limit at %u, disconnect at %u, delta is %u, dropping is %u (window size = %u)\n",
 		     (code < 5) ? codes[code] : codes[0],
 		     rateclass,
 		     currentavg, maxavg,
 		     alert, clear,
 		     limit, disconnect,
-		     windowsize);
+		     delta,
+		     dropping_snacs,
+		     windowsize
+		     );
 
 	if (code == AIM_RATE_CODE_LIMIT)
 	{
 		purple_debug_warning("oscar",  _("The last action you attempted could not be "
 				"performed because you are over the rate limit. "
-				"Please wait 10 seconds and try again."));
+				"Please wait 10 seconds and try again.\n"));
 	}
 
 	return 1;
@@ -3958,9 +4017,6 @@
 	presence = purple_status_get_presence(status);
 	aim_srv_setidle(od, !purple_presence_is_idle(presence) ? 0 : time(NULL) - purple_presence_get_idle_time(presence));
 
-	/* Request offline messages for AIM and ICQ */
-	aim_im_reqofflinemsgs(od);
-
 	if (od->icq) {
 #ifdef OLDSTYLE_ICQ_OFFLINEMSGS
 		aim_icq_reqofflinemsgs(od);
@@ -3987,6 +4043,10 @@
 	 */
 	if (od->ssi.received_data) {
 		aim_srv_clientready(od, conn);
+
+		/* Request offline messages for AIM and ICQ */
+		aim_im_reqofflinemsgs(od);
+
 		purple_connection_set_state(gc, PURPLE_CONNECTED);
 	}
 
@@ -5184,7 +5244,7 @@
 	{ /* If not in server list then prune from local list */
 		GSList *cur, *next;
 		GSList *buddies = purple_find_buddies(account, NULL);
-		
+
 		/* Buddies */
 		cur = NULL;
 
@@ -5264,9 +5324,9 @@
 			report_idle = strcmp(idle_reporting_pref, "none") != 0;
 
 			if (report_idle)
-				aim_ssi_setpresence(od, tmp | 0x400);
+				aim_ssi_setpresence(od, tmp | AIM_SSI_PRESENCE_FLAG_SHOWIDLE);
 			else
-				aim_ssi_setpresence(od, tmp & ~0x400);
+				aim_ssi_setpresence(od, tmp & ~AIM_SSI_PRESENCE_FLAG_SHOWIDLE);
 		}
 
 
@@ -5274,45 +5334,28 @@
 
 	/* Add from server list to local list */
 	for (curitem=od->ssi.local; curitem; curitem=curitem->next) {
-	  if ((curitem->name == NULL) || (g_utf8_validate(curitem->name, -1, NULL)))
 		switch (curitem->type) {
-			case 0x0000: { /* Buddy */
+			case AIM_SSI_TYPE_BUDDY: { /* Buddy */
 				if (curitem->name) {
 					struct aim_ssi_item *groupitem;
-					char *gname, *gname_utf8, *alias, *alias_utf8;
+					const char *gname, *alias;
 
 					groupitem = aim_ssi_itemlist_find(od->ssi.local, curitem->gid, 0x0000);
 					gname = groupitem ? groupitem->name : NULL;
-					if (gname != NULL) {
-						if (g_utf8_validate(gname, -1, NULL))
-							gname_utf8 = g_strdup(gname);
-						else
-							gname_utf8 = oscar_utf8_try_convert(account, gname);
-					} else
-						gname_utf8 = NULL;
-
-					g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans"));
+
+					g = purple_find_group(gname ? gname : _("Orphans"));
 					if (g == NULL) {
-						g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans"));
+						g = purple_group_new(gname ? gname : _("Orphans"));
 						purple_blist_add_group(g, NULL);
 					}
 
 					alias = aim_ssi_getalias(od->ssi.local, gname, curitem->name);
-					if (alias != NULL) {
-						if (g_utf8_validate(alias, -1, NULL))
-							alias_utf8 = g_strdup(alias);
-						else
-							alias_utf8 = oscar_utf8_try_convert(account, alias);
-						g_free(alias);
-					} else
-						alias_utf8 = NULL;
-
 					b = purple_find_buddy_in_group(account, curitem->name, g);
 					if (b) {
 						/* Get server stored alias */
-						purple_blist_alias_buddy(b, alias_utf8);
+						purple_blist_alias_buddy(b, alias);
 					} else {
-						b = purple_buddy_new(account, curitem->name, alias_utf8);
+						b = purple_buddy_new(account, curitem->name, alias);
 
 						purple_debug_info("oscar",
 								   "ssi: adding buddy %s to group %s to local list\n", curitem->name, gname);
@@ -5336,33 +5379,18 @@
 								purple_buddy_get_name(b),
 								OSCAR_STATUS_ID_MOBILE, NULL);
 					}
-
-					g_free(gname_utf8);
-					g_free(alias_utf8);
 				}
 			} break;
 
-			case 0x0001: { /* Group */
-				char *gname;
-				char *gname_utf8;
-
-				gname = curitem->name;
-				if (gname != NULL) {
-					if (g_utf8_validate(gname, -1, NULL))
-						gname_utf8 = g_strdup(gname);
-					else
-						gname_utf8 = oscar_utf8_try_convert(account, gname);
-				} else
-					gname_utf8 = NULL;
-
-				if (gname_utf8 != NULL && purple_find_group(gname_utf8) == NULL) {
-					g = purple_group_new(gname_utf8);
+			case AIM_SSI_TYPE_GROUP: { /* Group */
+				const char *gname = curitem->name;
+				if (gname != NULL && purple_find_group(gname) == NULL) {
+					g = purple_group_new(gname);
 					purple_blist_add_group(g, NULL);
 				}
-				g_free(gname_utf8);
 			} break;
 
-			case 0x0002: { /* Permit buddy */
+			case AIM_SSI_TYPE_PERMIT: { /* Permit buddy */
 				if (curitem->name) {
 					/* if (!find_permdeny_by_name(gc->permit, curitem->name)) { AAA */
 					GSList *list;
@@ -5375,7 +5403,7 @@
 				}
 			} break;
 
-			case 0x0003: { /* Deny buddy */
+			case AIM_SSI_TYPE_DENY: { /* Deny buddy */
 				if (curitem->name) {
 					GSList *list;
 					for (list=account->deny; (list && oscar_util_name_compare(curitem->name, list->data)); list=list->next);
@@ -5387,7 +5415,7 @@
 				}
 			} break;
 
-			case 0x0004: { /* Permit/deny setting */
+			case AIM_SSI_TYPE_PDINFO: { /* Permit/deny setting */
 				/*
 				 * We don't inherit the permit/deny setting from the server
 				 * for ICQ because, for ICQ, this setting controls who can
@@ -5405,7 +5433,7 @@
 				}
 			} break;
 
-			case 0x0005: { /* Presence setting */
+			case AIM_SSI_TYPE_PRESENCEPREFS: { /* Presence setting */
 				/* We don't want to change Purple's setting because it applies to all accounts */
 			} break;
 		} /* End of switch on curitem->type */
@@ -5435,6 +5463,10 @@
 	 */
 	if (od->bos.have_rights) {
 		aim_srv_clientready(od, conn);
+
+		/* Request offline messages for AIM and ICQ */
+		aim_im_reqofflinemsgs(od);
+
 		purple_connection_set_state(gc, PURPLE_CONNECTED);
 	}
 
@@ -5494,7 +5526,8 @@
 {
 	PurpleConnection *gc;
 	PurpleAccount *account;
-	char *gname, *gname_utf8, *alias, *alias_utf8;
+	const char *gname;
+	char *alias;
 	PurpleBuddy *b;
 	PurpleGroup *g;
 	struct aim_ssi_item *ssi_item;
@@ -5515,19 +5548,7 @@
 		return 1;
 
 	gname = aim_ssi_itemlist_findparentname(od->ssi.local, name);
-	gname_utf8 = gname ? oscar_utf8_try_convert(account, gname) : NULL;
-
 	alias = aim_ssi_getalias(od->ssi.local, gname, name);
-	if (alias != NULL)
-	{
-		if (g_utf8_validate(alias, -1, NULL))
-			alias_utf8 = g_strdup(alias);
-		else
-			alias_utf8 = oscar_utf8_try_convert(account, alias);
-	}
-	else
-		alias_utf8 = NULL;
-	g_free(alias);
 
 	b = purple_find_buddy(account, name);
 	if (b) {
@@ -5536,21 +5557,21 @@
 		 * of your buddies, so update our local buddy list with
 		 * the person's new alias.
 		 */
-		purple_blist_alias_buddy(b, alias_utf8);
+		purple_blist_alias_buddy(b, alias);
 	} else if (snac_subtype == 0x0008) {
 		/*
 		 * You're logged in somewhere else and you added a buddy to
 		 * your server list, so add them to your local buddy list.
 		 */
-		b = purple_buddy_new(account, name, alias_utf8);
-
-		if (!(g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans")))) {
-			g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans"));
+		b = purple_buddy_new(account, name, alias);
+
+		if (!(g = purple_find_group(gname ? gname : _("Orphans")))) {
+			g = purple_group_new(gname ? gname : _("Orphans"));
 			purple_blist_add_group(g, NULL);
 		}
 
 		purple_debug_info("oscar",
-				   "ssi: adding buddy %s to group %s to local list\n", name, gname_utf8 ? gname_utf8 : _("Orphans"));
+				   "ssi: adding buddy %s to group %s to local list\n", name, gname ? gname : _("Orphans"));
 		purple_blist_add_buddy(b, NULL, g, NULL);
 
 		/* Mobile users should always be online */
@@ -5563,6 +5584,8 @@
 
 	}
 
+	g_free(alias);
+
 	ssi_item = aim_ssi_itemlist_finditem(od->ssi.local,
 			gname, name, AIM_SSI_TYPE_BUDDY);
 	if (ssi_item == NULL)
@@ -5572,9 +5595,6 @@
 				"group %s\n", name, gname);
 	}
 
-	g_free(gname_utf8);
-	g_free(alias_utf8);
-
 	return 1;
 }
 
@@ -6277,7 +6297,6 @@
 	struct name_data *data;
 	PurpleGroup *g;
 	char *comment;
-	gchar *comment_utf8;
 	gchar *title;
 	PurpleAccount *account;
 	const char *name;
@@ -6296,7 +6315,6 @@
 	data = g_new(struct name_data, 1);
 
 	comment = aim_ssi_getcomment(od->ssi.local, purple_group_get_name(g), name);
-	comment_utf8 = comment ? oscar_utf8_try_convert(account, comment) : NULL;
 
 	data->gc = gc;
 	data->name = g_strdup(name);
@@ -6304,7 +6322,7 @@
 
 	title = g_strdup_printf(_("Buddy Comment for %s"), data->name);
 	purple_request_input(gc, title, _("Buddy Comment:"), NULL,
-					   comment_utf8, TRUE, FALSE, NULL,
+					   comment, TRUE, FALSE, NULL,
 					   _("_OK"), G_CALLBACK(oscar_ssi_editcomment),
 					   _("_Cancel"), G_CALLBACK(oscar_free_name_data),
 					   account, data->name, NULL,
@@ -6312,7 +6330,6 @@
 	g_free(title);
 
 	g_free(comment);
-	g_free(comment_utf8);
 }
 
 static void
@@ -7118,8 +7135,9 @@
 	return FALSE;
 }
 
-void oscar_init(PurplePluginProtocolInfo *prpl_info)
+void oscar_init(PurplePlugin *plugin)
 {
+	PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
 	PurpleAccountOption *option;
 	static gboolean init = FALSE;
 
@@ -7142,9 +7160,11 @@
 		OSCAR_DEFAULT_ALWAYS_USE_RV_PROXY);
 	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
 
-	option = purple_account_option_bool_new(_("Allow multiple simultaneous logins"), "allow_multiple_logins",
-											OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS);
-	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+	if (g_str_equal(purple_plugin_get_id(plugin), "prpl-aim")) {
+		option = purple_account_option_bool_new(_("Allow multiple simultaneous logins"), "allow_multiple_logins",
+												OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS);
+		prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+	}
 
 	if (init)
 		return;
--- a/libpurple/protocols/oscar/oscar.h	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Sun Nov 08 23:38:08 2009 +0000
@@ -623,7 +623,7 @@
 	} chat;
 };
 
-int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen);
+int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen, const char *tls_certname);
 
 /* family_auth.c */
 
@@ -661,7 +661,7 @@
 void flap_connection_send(FlapConnection *conn, FlapFrame *frame);
 void flap_connection_send_version(OscarData *od, FlapConnection *conn);
 void flap_connection_send_version_with_cookie(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy);
-void flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci);
+void flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci, gboolean allow_multiple_login);
 void flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data);
 void flap_connection_send_snac_with_priority(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data, gboolean high_priority);
 void flap_connection_send_keepalive(OscarData *od, FlapConnection *conn);
@@ -1240,7 +1240,7 @@
 #define AIM_SSI_ACK_INVALIDNAME		0x000d
 #define AIM_SSI_ACK_AUTHREQUIRED	0x000e
 
-/* These flags are set in the 0x00c9 TLV of SSI teyp 0x0005 */
+/* These flags are set in the 0x00c9 TLV of SSI type 0x0005 */
 #define AIM_SSI_PRESENCE_FLAG_SHOWIDLE        0x00000400
 #define AIM_SSI_PRESENCE_FLAG_NORECENTBUDDIES 0x00020000
 
@@ -1685,7 +1685,8 @@
 	guint32 disconnect;
 	guint32 current;
 	guint32 max;
-	guint8 unknown[5]; /* only present in versions >= 3 */
+	guint32 delta;
+	guint8 dropping_snacs;
 	GHashTable *members; /* Key is family and subtype, value is TRUE. */
 
 	struct timeval last; /**< The time when we last sent a SNAC of this rate class. */
--- a/libpurple/protocols/oscar/oscarcommon.h	Sun Nov 08 23:36:28 2009 +0000
+++ b/libpurple/protocols/oscar/oscarcommon.h	Sun Nov 08 23:38:08 2009 +0000
@@ -94,4 +94,4 @@
 gboolean oscar_offline_message(const PurpleBuddy *buddy);
 void oscar_format_username(PurpleConnection *gc, const char *nick);
 GList *oscar_actions(PurplePlugin *plugin, gpointer context);
-void oscar_init(PurplePluginProtocolInfo *prpl_info);
+void oscar_init(PurplePlugin *plugin);
--- a/pidgin.desktop.in	Sun Nov 08 23:36:28 2009 +0000
+++ b/pidgin.desktop.in	Sun Nov 08 23:38:08 2009 +0000
@@ -1,7 +1,7 @@
 [Desktop Entry]
 _Name=Pidgin Internet Messenger
 _GenericName=Internet Messenger
-_Comment=Send instant messages over multiple protocols
+_Comment=Chat over IM.  Supports AIM, Google Talk, Jabber/XMPP, MSN, Yahoo and more
 Exec=pidgin
 Icon=pidgin
 StartupNotify=true
--- a/pidgin/gtkmain.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/pidgin/gtkmain.c	Sun Nov 08 23:38:08 2009 +0000
@@ -143,6 +143,10 @@
 }
 
 #ifdef HAVE_SIGNAL_H
+static char *segfault_message;
+
+static int signal_sockets[2];
+
 static void sighandler(int sig);
 
 /*
@@ -168,31 +172,60 @@
 	}
 }
 
-char *segfault_message;
+static void sighandler(int sig)
+{
+	ssize_t written;
+
+	/*
+	 * We won't do any of the heavy lifting for the signal handling here
+	 * because we have no idea what was interrupted.  Previously this signal
+	 * handler could result in some calls to malloc/free, which can cause
+	 * deadlock in libc when the signal handler was interrupting a previous
+	 * malloc or free.  So instead we'll do an ugly hack where we write the
+	 * signal number to one end of a socket pair.  The other half of the
+	 * socket pair is watched by our main loop.  When the main loop sees new
+	 * data on the socket it reads in the signal and performs the appropriate
+	 * action without fear of interrupting stuff.
+	 */
+	if (sig == SIGSEGV) {
+		fprintf(stderr, "%s", segfault_message);
+		abort();
+		return;
+	}
 
-/*
- * This signal handler shouldn't be touching this much stuff.
- * It should just set a flag and return, and something else in
- * Pidgin should monitor the flag to see if something needs to
- * be done.  Because the signal handler interrupts the program,
- * it could be called in the middle of adding a new connection
- * to the list of connections, and then if we try to disconnect
- * all connections it could lead to a crash because the linked
- * list of connections could be in a weird state.  But, well,
- * this signal handler probably isn't called very often, so it's
- * not a big deal.
- */
-static void
-sighandler(int sig)
+	written = write(signal_sockets[0], &sig, sizeof(int));
+	if (written < 0 || written != sizeof(int)) {
+		/* This should never happen */
+		purple_debug_error("sighandler", "Received signal %d but only "
+				"wrote %" G_GSSIZE_FORMAT " bytes out of %"
+				G_GSIZE_FORMAT ": %s\n",
+				sig, written, sizeof(int), g_strerror(errno));
+		exit(1);
+	}
+}
+
+static gboolean
+mainloop_sighandler(GIOChannel *source, GIOCondition cond, gpointer data)
 {
+	GIOStatus stat;
+	int sig;
+	gsize bytes_read;
+	GError *error = NULL;
+
+	/* read the signal number off of the io channel */
+	stat = g_io_channel_read_chars(source, (gchar *)&sig, sizeof(int),
+			&bytes_read, &error);
+	if (stat != G_IO_STATUS_NORMAL) {
+		purple_debug_error("sighandler", "Signal callback failed to read "
+				"from signal socket: %s", error->message);
+		purple_core_quit();
+		return FALSE;
+	}
+
 	switch (sig) {
 	case SIGHUP:
 		purple_debug_warning("sighandler", "Caught signal %d\n", sig);
 		break;
-	case SIGSEGV:
-		fprintf(stderr, "%s", segfault_message);
-		abort();
-		break;
 #if defined(USE_GSTREAMER) && !defined(GST_CAN_DISABLE_FORKING)
 /* By default, gstreamer forks when you initialize it, and waitpids for the
  * child.  But if libpurple reaps the child rather than leaving it to
@@ -219,6 +252,8 @@
 		purple_debug_warning("sighandler", "Caught signal %d\n", sig);
 		purple_core_quit();
 	}
+
+	return TRUE;
 }
 #endif
 
@@ -502,11 +537,13 @@
 	sigset_t sigset;
 	RETSIGTYPE (*prev_sig_disp)(int);
 	char errmsg[BUFSIZ];
+	GIOChannel *signal_channel;
+	GIOStatus signal_status;
 #ifndef DEBUG
 	char *segfault_message_tmp;
+#endif
 	GError *error = NULL;
 #endif
-#endif
 	int opt;
 	gboolean gui_check;
 	gboolean debug_enabled;
@@ -592,6 +629,29 @@
 		);
 #endif
 
+	/*
+	 * Create a socket pair for receiving unix signals from a signal
+	 * handler.
+	 */
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sockets) < 0) {
+		perror("Failed to create sockets for GLib signal handling");
+		exit(1);
+	}
+	signal_channel = g_io_channel_unix_new(signal_sockets[1]);
+
+	/*
+	 * Set the channel encoding to raw binary instead of the default of
+	 * UTF-8, because we'll be sending integers across instead of strings.
+	 */
+	error = NULL;
+	signal_status = g_io_channel_set_encoding(signal_channel, NULL, &error);
+	if (signal_status != G_IO_STATUS_NORMAL) {
+		fprintf(stderr, "Failed to set the signal channel to raw "
+				"binary: %s", error->message);
+		exit(1);
+	}
+	g_io_add_watch(signal_channel, G_IO_IN, mainloop_sighandler, NULL);
+
 	/* Let's not violate any PLA's!!!! */
 	/* jseymour: whatever the fsck that means */
 	/* Robot101: for some reason things like gdm like to block     *
@@ -743,7 +803,7 @@
 		return 1;
 	}
 
-	g_set_application_name(_("Pidgin"));
+	g_set_application_name(PIDGIN_NAME);
 
 #ifdef _WIN32
 	winpidgin_init(hint);
--- a/pidgin/plugins/disco/gtkdisco.c	Sun Nov 08 23:36:28 2009 +0000
+++ b/pidgin/plugins/disco/gtkdisco.c	Sun Nov 08 23:38:08 2009 +0000
@@ -162,12 +162,15 @@
 
 static void discolist_cancel_cb(PidginDiscoList *pdl, const char *server)
 {
+	pdl->dialog->prompt_handle = NULL;
+
 	pidgin_disco_list_set_in_progress(pdl, FALSE);
 	pidgin_disco_list_unref(pdl);
 }
 
 static void discolist_ok_cb(PidginDiscoList *pdl, const char *server)
 {
+	pdl->dialog->prompt_handle = NULL;
 	gtk_widget_set_sensitive(pdl->dialog->browse_button, TRUE);
 
 	if (!server || !*server) {
@@ -236,7 +239,7 @@
 
 	/* Note to translators: The string "Enter an XMPP Server" is asking the
 	   user to type the name of an XMPP server which will then be queried */
-	purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"),
+	dialog->prompt_handle = purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"),
 			_("Select an XMPP server to query"),
 			server, FALSE, FALSE, NULL,
 			_("Find Services"), PURPLE_CALLBACK(discolist_ok_cb),
@@ -390,6 +393,9 @@
 	PidginDiscoDialog *dialog = d;
 	PidginDiscoList *list = dialog->discolist;
 
+	if (dialog->prompt_handle)
+		purple_request_close(PURPLE_REQUEST_INPUT, dialog->prompt_handle);
+
 	if (list) {
 		list->dialog = NULL;
 
--- a/pidgin/plugins/disco/gtkdisco.h	Sun Nov 08 23:36:28 2009 +0000
+++ b/pidgin/plugins/disco/gtkdisco.h	Sun Nov 08 23:38:08 2009 +0000
@@ -43,6 +43,8 @@
 
 	PurpleAccount *account;
 	PidginDiscoList *discolist;
+
+	gpointer *prompt_handle;
 };
 
 struct _PidginDiscoList {