changeset 25432:8c8948b9f602

propagate from branch 'im.pidgin.pidgin' (head 8ae2fca06a3be4b99cc677c8f880d9521a35ec8b) to branch 'im.pidgin.pidgin.next.minor' (head 3725318168b46a11998066b36f9311e26d51885b)
author Mark Doliner <mark@kingant.net>
date Wed, 28 Jan 2009 10:23:37 +0000
parents 9bff90dfb2e5 (current diff) 29f953732186 (diff)
children e0572c12265a
files .todo COPYRIGHT ChangeLog libpurple/certificate.c libpurple/core.c libpurple/protocols/oscar/oscar.c libpurple/protocols/oscar/oscar.h libpurple/protocols/oscar/peer.c libpurple/status.c pidgin/gtkimhtml.c
diffstat 19 files changed, 513 insertions(+), 109 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Wed Jan 28 10:16:41 2009 +0000
+++ b/COPYRIGHT	Wed Jan 28 10:23:37 2009 +0000
@@ -445,6 +445,7 @@
 Todd Troxell
 Brad Turcotte
 Kyle Turman
+Jon Turney
 Junichi Uekawa
 Igor Vlasenko
 István Váradi
--- a/ChangeLog	Wed Jan 28 10:16:41 2009 +0000
+++ b/ChangeLog	Wed Jan 28 10:23:37 2009 +0000
@@ -20,6 +20,7 @@
 	* Fix transfer of buddy icons, custom smileys and files from the
 	latest WLM 9 official client. (Thomas Gibson-Robinson)
 	* Fix a crash when removing an account with an unknown protocol id.
+	* Large (multi-part) messages on MSN are now correctly re-combined.
 
 	Finch:
 	* Allow rebinding keys to change the focused widget (details in the
--- a/libpurple/dbus-analyze-functions.py	Wed Jan 28 10:16:41 2009 +0000
+++ b/libpurple/dbus-analyze-functions.py	Wed Jan 28 10:23:37 2009 +0000
@@ -117,7 +117,7 @@
             self.params.append(Parameter.fromtokens(paramtexts[i].split(), i))
 
         self.call = "%s(%s)" % (self.function.name,
-                                ", ".join([param.name for param in self.params]))
+                                ", ".join(param.name for param in self.params))
         
     
     def process(self):
@@ -160,6 +160,10 @@
             elif type[0].startswith("Purple") or type[0] == "xmlnode":
                 return self.inputpurplestructure(type, name)
 
+            # special case for *_get_data functions, be careful here...
+            elif (type[0] == "size_t") and (name == "len"):
+                return self.inputgetdata(type, name)
+            
             # unknown pointers are always replaced with NULL
             else:
                 return self.inputpointer(type, name)
@@ -196,6 +200,10 @@
             if type[0] in ["GList", "GSList"]:
                 return self.outputlist(type, name)
 
+        # Special case for *_get_data functions
+        if type[0] == "gconstpointer":
+            return self.outputgetdata(type, name)
+
         raise myexception
     
 
@@ -309,7 +317,13 @@
         self.returncode.append("return garray_int_to_%s(%s);" %
                                (type[0].lower(), name));
 
- 
+    # Special case for *_get_data functions, don't need client bindings,
+    #  but do need the name so it doesn't crash
+    def inputgetdata(self, type, name):
+        raise myexception
+    def outputgetdata(self, type, name):
+        raise myexception
+
 class ServerBinding (Binding):
     def __init__(self, functiontext, paramtexts):
         Binding.__init__(self, functiontext, paramtexts)
@@ -475,6 +489,21 @@
                               % (name, name))
             self.addouttype("ai", name)
 
+    # Special case for *_get_data functions
+    def inputgetdata(self, type, name):
+        self.cdecls.append("\tsize_t %s = 0;" % name)
+        return True
+    def outputgetdata(self, type, name):
+        # This is a total hack, but self.call is set up before the parameters
+        #  are processed, so we can't tell it to pass a parameter by reference.
+        self.call = "%s(%s)" % (self.function.name,
+                                ", ".join(param.name if param.name != "len" else "&len" for param in self.params))
+
+        self.cdecls.append("\tgconstpointer %s;" % name)
+        self.ccode.append("\t%s = %s;" % (name, self.call))
+        self.cparamsout.append("DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &%s, %s" \
+                               % (name, "len"))
+        self.addouttype("ay", name)
 
 class BindingSet:
     regexp = r"^(\w[^()]*)\(([^()]*)\)\s*;\s*$";
--- a/libpurple/protocols/msn/cmdproc.c	Wed Jan 28 10:16:41 2009 +0000
+++ b/libpurple/protocols/msn/cmdproc.c	Wed Jan 28 10:23:37 2009 +0000
@@ -35,6 +35,9 @@
 	cmdproc->txqueue = g_queue_new();
 	cmdproc->history = msn_history_new();
 
+	cmdproc->multiparts = g_hash_table_new_full(g_str_hash, g_str_equal,
+	                                            NULL, (GDestroyNotify)msn_message_unref);
+
 	return cmdproc;
 }
 
@@ -53,6 +56,8 @@
 	if (cmdproc->last_cmd != NULL)
 		msn_command_destroy(cmdproc->last_cmd);
 
+	g_hash_table_destroy(cmdproc->multiparts);
+
 	g_free(cmdproc);
 }
 
@@ -235,6 +240,61 @@
 msn_cmdproc_process_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
 {
 	MsnMsgTypeCb cb;
+	const char *messageId = NULL;
+
+	/* Multi-part messages */
+	if ((messageId = msn_message_get_attr(msg, "Message-ID")) != NULL) {
+		const char *chunk_text = msn_message_get_attr(msg, "Chunks");
+		guint chunk;
+		if (chunk_text != NULL) {
+			chunk = strtol(chunk_text, NULL, 10);
+			/* 1024 chunks of ~1300 bytes is ~1MB, which seems OK to prevent 
+			   some random client causing pidgin to hog a ton of memory.
+			   Probably should figure out the maximum that the official client
+			   actually supports, though. */
+			if (chunk > 0 && chunk < 1024) {
+				msg->total_chunks = chunk;
+				msg->received_chunks = 1;
+				g_hash_table_insert(cmdproc->multiparts, (gpointer)messageId, msn_message_ref(msg));
+				purple_debug_info("msn", "Received chunked message, messageId: '%s', total chunks: %d\n",
+				                  messageId, chunk);
+			} else {
+				purple_debug_error("msn", "MessageId '%s' has too many chunks: %d\n", messageId, chunk);
+			}
+			return;
+		} else {
+			chunk_text = msn_message_get_attr(msg, "Chunk");
+			if (chunk_text != NULL) {
+				MsnMessage *first = g_hash_table_lookup(cmdproc->multiparts, messageId);
+				chunk = strtol(chunk_text, NULL, 10);
+				if (first == NULL) {
+					purple_debug_error("msn",
+					                   "Unable to find first chunk of messageId '%s' to correspond with chunk %d.\n",
+					                   messageId, chunk+1);
+				} else if (first->received_chunks == chunk) {
+					/* Chunk is from 1 to total-1 (doesn't count first one) */
+					purple_debug_info("msn", "Received chunk %d of %d, messageId: '%s'\n",
+					                  chunk+1, first->total_chunks, messageId);
+					first->body = g_realloc(first->body, first->body_len + msg->body_len);
+					memcpy(first->body + first->body_len, msg->body, msg->body_len);
+					first->body_len += msg->body_len;
+					first->received_chunks++;
+					if (first->received_chunks != first->total_chunks)
+						return;
+					else
+						/* We're done! Send it along... The caller takes care of
+						   freeing the old one. */
+						msg = first;
+				} else {
+					/* TODO: Can you legitimately receive chunks out of order? */
+					g_hash_table_remove(cmdproc->multiparts, messageId);
+					return;
+				}
+			} else {
+				purple_debug_error("msn", "Received MessageId '%s' with no chunk number!\n", messageId);
+			}
+		}
+	}
 
 	if (msn_message_get_content_type(msg) == NULL)
 	{
@@ -245,15 +305,14 @@
 	cb = g_hash_table_lookup(cmdproc->cbs_table->msgs,
 							 msn_message_get_content_type(msg));
 
-	if (cb == NULL)
-	{
+	if (cb != NULL)
+		cb(cmdproc, msg);
+	else
 		purple_debug_warning("msn", "Unhandled content-type '%s'\n",
 						   msn_message_get_content_type(msg));
 
-		return;
-	}
-
-	cb(cmdproc, msg);
+	if (messageId != NULL)
+		g_hash_table_remove(cmdproc->multiparts, messageId);
 }
 
 void
--- a/libpurple/protocols/msn/cmdproc.h	Wed Jan 28 10:16:41 2009 +0000
+++ b/libpurple/protocols/msn/cmdproc.h	Wed Jan 28 10:23:37 2009 +0000
@@ -46,6 +46,8 @@
 
 	MsnHistory *history;
 
+	GHashTable *multiparts; /**< Multi-part message ID's */
+
 	void *data; /**< Extra data, like the switchboard. */
 };
 
--- a/libpurple/protocols/msn/msg.h	Wed Jan 28 10:16:41 2009 +0000
+++ b/libpurple/protocols/msn/msg.h	Wed Jan 28 10:23:37 2009 +0000
@@ -109,6 +109,8 @@
 	char *charset;
 	char *body;
 	gsize body_len;
+	guint total_chunks;   /**< How many chunks in this multi-part message */
+	guint received_chunks; /**< How many chunks we've received so far */
 
 	MsnSlpHeader msnslp_header;
 	MsnSlpFooter msnslp_footer;
--- a/libpurple/protocols/msn/switchboard.c	Wed Jan 28 10:16:41 2009 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Wed Jan 28 10:23:37 2009 +0000
@@ -799,7 +799,7 @@
 
 	msn_cmdproc_process_msg(cmdproc, msg);
 
-	msn_message_destroy(msg);
+	msn_message_unref(msg);
 }
 
 static void
--- a/libpurple/protocols/oscar/family_chat.c	Wed Jan 28 10:16:41 2009 +0000
+++ b/libpurple/protocols/oscar/family_chat.c	Wed Jan 28 10:23:37 2009 +0000
@@ -79,13 +79,15 @@
 
 		if (conn->type != SNAC_FAMILY_CHAT)
 			continue;
-		if (!conn->internal) {
-			purple_debug_misc("oscar", "faim: chat: chat connection with no name! (fd = %d)\n", conn->fd);
+		if (!conn->internal)
+		{
+			purple_debug_misc("oscar", "%sfaim: chat: chat connection with no name! (fd = %d)\n",
+					conn->gsc ? "(ssl) " : "", conn->gsc ? conn->gsc->fd : conn->fd);
 			continue;
 		}
 
 		if (strcmp(ccp->name, name) == 0)
-			return conn;;
+			return conn;
 	}
 
 	return NULL;
--- a/libpurple/protocols/oscar/family_oservice.c	Wed Jan 28 10:16:41 2009 +0000
+++ b/libpurple/protocols/oscar/family_oservice.c	Wed Jan 28 10:23:37 2009 +0000
@@ -103,12 +103,29 @@
 aim_srv_requestnew(OscarData *od, guint16 serviceid)
 {
 	FlapConnection *conn;
+	ByteStream bs;
+	aim_snacid_t snacid;
+	GSList *tlvlist = NULL;
 
 	conn = flap_connection_findbygroup(od, SNAC_FAMILY_BOS);
 	if(!conn)
 		return;
 
-	aim_genericreq_s(od, conn, SNAC_FAMILY_OSERVICE, 0x0004, &serviceid);
+	byte_stream_new(&bs, 6);
+
+	byte_stream_put16(&bs, serviceid);
+
+	if (od->use_ssl)
+		/* Request SSL Connection */
+		aim_tlvlist_add_noval(&tlvlist, 0x008c);
+
+	aim_tlvlist_write(&bs, &tlvlist);
+	aim_tlvlist_free(tlvlist);
+
+	snacid = aim_cachesnac(od, SNAC_FAMILY_OSERVICE, 0x0004, 0x0000, NULL, 0);
+	flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0004, 0x0000, snacid, &bs);
+
+	byte_stream_destroy(&bs);
 }
 
 /*
@@ -127,10 +144,10 @@
 	struct chatsnacinfo csi;
 
 	conn = flap_connection_findbygroup(od, SNAC_FAMILY_BOS);
-	if (!conn || !roomname || !strlen(roomname))
+	if (!conn || !roomname || roomname[0] == '\0')
 		return -EINVAL;
 
-	byte_stream_new(&bs, 502);
+	byte_stream_new(&bs, 506);
 
 	memset(&csi, 0, sizeof(csi));
 	csi.exchange = exchange;
@@ -143,6 +160,11 @@
 	byte_stream_put16(&bs, 0x000e);
 
 	aim_tlvlist_add_chatroom(&tlvlist, 0x0001, exchange, roomname, instance);
+
+	if (od->use_ssl)
+		/* Request SSL Connection */
+		aim_tlvlist_add_noval(&tlvlist, 0x008c);
+
 	aim_tlvlist_write(&bs, &tlvlist);
 	aim_tlvlist_free(tlvlist);
 
@@ -179,6 +201,8 @@
 	redir.ip = aim_tlv_getstr(tlvlist, 0x0005, 1);
 	redir.cookielen = aim_tlv_gettlv(tlvlist, 0x0006, 1)->length;
 	redir.cookie = (guchar *)aim_tlv_getstr(tlvlist, 0x0006, 1);
+	redir.ssl_cert_cn = aim_tlv_getstr(tlvlist, 0x008d, 1);
+	redir.use_ssl = aim_tlv_get8(tlvlist, 0x008e, 1);
 
 	/* Fetch original SNAC so we can get csi if needed */
 	origsnac = aim_remsnac(od, snac->id);
@@ -196,6 +220,7 @@
 
 	g_free((void *)redir.ip);
 	g_free((void *)redir.cookie);
+	g_free((void *)redir.ssl_cert_cn);
 
 	if (origsnac)
 		g_free(origsnac->data);
--- a/libpurple/protocols/oscar/flap_connection.c	Wed Jan 28 10:16:41 2009 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Wed Jan 28 10:23:37 2009 +0000
@@ -364,6 +364,15 @@
 		conn->fd = -1;
 	}
 
+	if (conn->gsc != NULL)
+	{
+		if (conn->type == SNAC_FAMILY_LOCATE)
+			flap_connection_send_close(od, conn);
+
+		purple_ssl_close(conn->gsc);
+		conn->gsc = NULL;
+	}
+
 	if (conn->watcher_incoming != 0)
 	{
 		purple_input_remove(conn->watcher_incoming);
@@ -467,6 +476,7 @@
 
 	g_free(conn->error_message);
 	g_free(conn->cookie);
+	g_free(conn->ssl_cert_cn);
 
 	/*
 	 * Free conn->internal, if necessary
@@ -844,24 +854,31 @@
  * All complete FLAPs handled immedate after they're received.
  * Incomplete FLAP data is stored locally and appended to the next
  * time this callback is triggered.
+ *
+ * This is called by flap_connection_recv_cb and
+ * flap_connection_recv_cb_ssl for unencrypted/encrypted connections.
  */
-void
-flap_connection_recv_cb(gpointer data, gint source, PurpleInputCondition cond)
+static void
+flap_connection_recv(FlapConnection *conn)
 {
-	FlapConnection *conn;
+	gpointer buf;
+	gsize buflen;
 	gssize read;
 
-	conn = data;
-
 	/* Read data until we run out of data and break out of the loop */
 	while (TRUE)
 	{
 		/* Start reading a new FLAP */
 		if (conn->buffer_incoming.data.data == NULL)
 		{
+			buf = conn->header + conn->header_received;
+			buflen = 6 - conn->header_received;
+
 			/* Read the first 6 bytes (the FLAP header) */
-			read = recv(conn->fd, conn->header + conn->header_received,
-					6 - conn->header_received, 0);
+			if (conn->gsc)
+				read = purple_ssl_read(conn->gsc, buf, buflen);
+			else
+				read = recv(conn->fd, buf, buflen, 0);
 
 			/* Check if the FLAP server closed the connection */
 			if (read == 0)
@@ -918,13 +935,15 @@
 			conn->buffer_incoming.data.offset = 0;
 		}
 
-		if (conn->buffer_incoming.data.len - conn->buffer_incoming.data.offset)
+		buflen = conn->buffer_incoming.data.len - conn->buffer_incoming.data.offset;
+		if (buflen)
 		{
+			buf = &conn->buffer_incoming.data.data[conn->buffer_incoming.data.offset];
 			/* Read data into the temporary FlapFrame until it is complete */
-			read = recv(conn->fd,
-						&conn->buffer_incoming.data.data[conn->buffer_incoming.data.offset],
-						conn->buffer_incoming.data.len - conn->buffer_incoming.data.offset,
-						0);
+			if (conn->gsc)
+				read = purple_ssl_read(conn->gsc, buf, buflen);
+			else
+				read = recv(conn->fd, buf, buflen, 0);
 
 			/* Check if the FLAP server closed the connection */
 			if (read == 0)
@@ -964,6 +983,29 @@
 	}
 }
 
+void
+flap_connection_recv_cb(gpointer data, gint source, PurpleInputCondition cond)
+{
+	FlapConnection *conn = data;
+
+	flap_connection_recv(conn);
+}
+
+void
+flap_connection_recv_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
+{
+	FlapConnection *conn = data;
+
+	flap_connection_recv(conn);
+}
+
+/**
+ * @param source When this function is called as a callback source is
+ *        set to the fd that triggered the callback.  But this function
+ *        is also called directly from flap_connection_send_byte_stream(),
+ *        in which case source will be -1.  So don't use source--use
+ *        conn->gsc or conn->fd instead.
+ */
 static void
 send_cb(gpointer data, gint source, PurpleInputCondition cond)
 {
@@ -980,7 +1022,11 @@
 		return;
 	}
 
-	ret = send(conn->fd, conn->buffer_outgoing->outptr, writelen, 0);
+	if (conn->gsc)
+		ret = purple_ssl_write(conn->gsc, conn->buffer_outgoing->outptr,
+				writelen);
+	else
+		ret = send(conn->fd, conn->buffer_outgoing->outptr, writelen, 0);
 	if (ret <= 0)
 	{
 		if (ret < 0 && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
@@ -990,8 +1036,13 @@
 		/* Error! */
 		purple_input_remove(conn->watcher_outgoing);
 		conn->watcher_outgoing = 0;
-		close(conn->fd);
-		conn->fd = -1;
+		if (conn->gsc) {
+			purple_ssl_close(conn->gsc);
+			conn->gsc = NULL;
+		} else {
+			close(conn->fd);
+			conn->fd = -1;
+		}
 		flap_connection_schedule_destroy(conn,
 				OSCAR_DISCONNECT_LOST_CONNECTION, g_strerror(errno));
 		return;
@@ -1017,11 +1068,17 @@
 	purple_circ_buffer_append(conn->buffer_outgoing, bs->data, count);
 
 	/* If we haven't already started writing stuff, then start the cycle */
-	if ((conn->watcher_outgoing == 0) && (conn->fd >= 0))
+	if (conn->watcher_outgoing == 0)
 	{
-		conn->watcher_outgoing = purple_input_add(conn->fd,
-				PURPLE_INPUT_WRITE, send_cb, conn);
-		send_cb(conn, conn->fd, 0);
+		if (conn->gsc) {
+			conn->watcher_outgoing = purple_input_add(conn->gsc->fd,
+					PURPLE_INPUT_WRITE, send_cb, conn);
+			send_cb(conn, -1, 0);
+		} else if (conn->fd >= 0) {
+			conn->watcher_outgoing = purple_input_add(conn->fd,
+					PURPLE_INPUT_WRITE, send_cb, conn);
+			send_cb(conn, -1, 0);
+		}
 	}
 }
 
--- a/libpurple/protocols/oscar/oscar.c	Wed Jan 28 10:16:41 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Wed Jan 28 10:23:37 2009 +0000
@@ -1090,59 +1090,64 @@
 }
 
 /**
- * This is the callback function anytime purple_proxy_connect()
- * establishes a new TCP connection with an oscar host.  Depending
- * on the type of host, we do a few different things here.
+ * This is called from the callback functions for establishing
+ * a TCP connection with an oscar host if an error occurred.
  */
 static void
-connection_established_cb(gpointer data, gint source, const gchar *error_message)
+connection_common_error_cb(FlapConnection *conn, const gchar *error_message)
 {
-	PurpleConnection *gc;
 	OscarData *od;
+	PurpleConnection *gc;
+
+	od = conn->od;
+	gc = od->gc;
+
+	purple_debug_error("oscar", "unable to connect to FLAP "
+			"server of type 0x%04hx\n", conn->type);
+
+	if (conn->type == SNAC_FAMILY_AUTH)
+	{
+		gchar *msg;
+		msg = g_strdup_printf(_("Could not connect to authentication server:\n%s"),
+				error_message);
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
+		g_free(msg);
+	}
+	else if (conn->type == SNAC_FAMILY_LOCATE)
+	{
+		gchar *msg;
+		msg = g_strdup_printf(_("Could not connect to BOS server:\n%s"),
+				error_message);
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
+		g_free(msg);
+	}
+	else
+	{
+		/* Maybe we should call this for BOS connections, too? */
+		flap_connection_schedule_destroy(conn,
+				OSCAR_DISCONNECT_COULD_NOT_CONNECT, error_message);
+	}
+}
+
+/**
+ * This is called from the callback functions for establishing
+ * a TCP connection with an oscar host. Depending on the type
+ * of host, we do a few different things here.
+ */
+static void
+connection_common_established_cb(FlapConnection *conn)
+{
+	OscarData *od;
+	PurpleConnection *gc;
 	PurpleAccount *account;
-	FlapConnection *conn;
-
-	conn = data;
+
 	od = conn->od;
 	gc = od->gc;
 	account = purple_connection_get_account(gc);
 
-	conn->connect_data = NULL;
-	conn->fd = source;
-
-	if (source < 0)
-	{
-		purple_debug_error("oscar", "unable to connect to FLAP "
-				"server of type 0x%04hx\n", conn->type);
-		if (conn->type == SNAC_FAMILY_AUTH)
-		{
-			gchar *msg;
-			msg = g_strdup_printf(_("Could not connect to authentication server:\n%s"),
-					error_message);
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
-			g_free(msg);
-		}
-		else if (conn->type == SNAC_FAMILY_LOCATE)
-		{
-			gchar *msg;
-			msg = g_strdup_printf(_("Could not connect to BOS server:\n%s"),
-					error_message);
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
-			g_free(msg);
-		}
-		else
-		{
-			/* Maybe we should call this for BOS connections, too? */
-			flap_connection_schedule_destroy(conn,
-					OSCAR_DISCONNECT_COULD_NOT_CONNECT, error_message);
-		}
-		return;
-	}
-
 	purple_debug_info("oscar", "connected to FLAP server of type 0x%04hx\n",
 			conn->type);
-	conn->watcher_incoming = purple_input_add(conn->fd,
-			PURPLE_INPUT_READ, flap_connection_recv_cb, conn);
+
 	if (conn->cookie == NULL)
 		flap_connection_send_version(od, conn);
 	else
@@ -1173,6 +1178,85 @@
 }
 
 static void
+connection_established_cb(gpointer data, gint source, const gchar *error_message)
+{
+	FlapConnection *conn;
+
+	conn = data;
+
+	conn->connect_data = NULL;
+	conn->fd = source;
+
+	if (source < 0)
+	{
+		connection_common_error_cb(conn, error_message);
+		return;
+	}
+
+	conn->watcher_incoming = purple_input_add(conn->fd,
+			PURPLE_INPUT_READ, flap_connection_recv_cb, conn);
+	connection_common_established_cb(conn);
+}
+
+static void
+ssl_connection_established_cb(gpointer data, PurpleSslConnection *gsc,
+		PurpleInputCondition cond)
+{
+	FlapConnection *conn;
+
+	conn = data;
+
+	purple_ssl_input_add(gsc, flap_connection_recv_cb_ssl, conn);
+	connection_common_established_cb(conn);
+}
+
+static void
+ssl_connection_error_cb(PurpleSslConnection *gsc, PurpleSslErrorType error,
+		gpointer data)
+{
+	FlapConnection *conn;
+
+	conn = data;
+
+	if (conn->watcher_outgoing)
+	{
+		purple_input_remove(conn->watcher_outgoing);
+		conn->watcher_outgoing = 0;
+	}
+
+	/* sslconn frees the connection on error */
+	conn->gsc = NULL;
+
+	connection_common_error_cb(conn, purple_ssl_strerror(error));
+}
+
+static void
+ssl_proxy_conn_established_cb(gpointer data, gint source, const gchar *error_message)
+{
+	OscarData *od;
+	PurpleConnection *gc;
+	PurpleAccount *account;
+	FlapConnection *conn;
+
+	conn = data;
+	od = conn->od;
+	gc = od->gc;
+	account = purple_connection_get_account(gc);
+
+	conn->connect_data = NULL;
+
+	if (source < 0)
+	{
+		connection_common_error_cb(conn, error_message);
+		return;
+	}
+
+	conn->gsc = purple_ssl_connect_with_host_fd(account, source,
+			ssl_connection_established_cb, ssl_connection_error_cb,
+			conn->ssl_cert_cn, conn);
+}
+
+static void
 flap_connection_established_bos(OscarData *od, FlapConnection *conn)
 {
 	PurpleConnection *gc = od->gc;
@@ -1433,17 +1517,56 @@
 		gc->flags |= PURPLE_CONNECTION_AUTO_RESP;
 	}
 
+	od->use_ssl = purple_account_get_bool(account, "use_ssl", OSCAR_DEFAULT_USE_SSL);
+
 	/* Connect to core Purple signals */
 	purple_prefs_connect_callback(gc, "/purple/away/idle_reporting", idle_reporting_pref_cb, gc);
 	purple_prefs_connect_callback(gc, "/plugins/prpl/oscar/recent_buddies", recent_buddies_pref_cb, gc);
 
 	newconn = flap_connection_new(od, SNAC_FAMILY_AUTH);
-	newconn->connect_data = purple_proxy_connect(NULL, account,
-			purple_account_get_string(account, "server", OSCAR_DEFAULT_LOGIN_SERVER),
-			purple_account_get_int(account, "port", OSCAR_DEFAULT_LOGIN_PORT),
-			connection_established_cb, newconn);
-	if (newconn->connect_data == NULL)
-	{
+	if (od->use_ssl) {
+		if (purple_ssl_is_supported()) {
+			const char *server = purple_account_get_string(account, "server", OSCAR_DEFAULT_SSL_LOGIN_SERVER);
+			/*
+			 * If the account's server is what the oscar prpl has offered as
+			 * the default login server through the vast eons (all two of
+			 * said default options, AFAIK) and the user wants SSL, we'll
+			 * do what we know is best for them and change the setting out
+			 * from under them to the SSL login server.
+			 */
+			if (!strcmp(server, OSCAR_DEFAULT_LOGIN_SERVER) || !strcmp(server, OSCAR_OLD_LOGIN_SERVER)) {
+				purple_debug_info("oscar", "Account uses SSL, so changing server to default SSL server\n");
+				purple_account_set_string(account, "server", OSCAR_DEFAULT_SSL_LOGIN_SERVER);
+				server = OSCAR_DEFAULT_SSL_LOGIN_SERVER;
+			}
+
+			newconn->gsc = purple_ssl_connect(account, server,
+					purple_account_get_int(account, "port", OSCAR_DEFAULT_LOGIN_PORT),
+					ssl_connection_established_cb, ssl_connection_error_cb, newconn);
+		} else {
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
+					_("SSL support unavailable"));
+		}
+	} else {
+		const char *server = purple_account_get_string(account, "server", OSCAR_DEFAULT_LOGIN_SERVER);
+
+		/*
+		 * See the comment above. We do the reverse here. If they don't want
+		 * SSL but their server is set to OSCAR_DEFAULT_SSL_LOGIN_SERVER,
+		 * set it back to the default.
+		 */
+		if (!strcmp(server, OSCAR_DEFAULT_SSL_LOGIN_SERVER)) {
+			purple_debug_info("oscar", "Account does not use SSL, so changing server back to non-SSL\n");
+			purple_account_set_string(account, "server", OSCAR_DEFAULT_LOGIN_SERVER);
+			server = OSCAR_DEFAULT_LOGIN_SERVER;
+		}
+
+		newconn->connect_data = purple_proxy_connect(NULL, account, server,
+				purple_account_get_int(account, "port", OSCAR_DEFAULT_LOGIN_PORT),
+				connection_established_cb, newconn);
+	}
+
+	if (newconn->gsc == NULL && newconn->connect_data == NULL) {
 		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Couldn't connect to host"));
 		return;
@@ -1568,8 +1691,23 @@
 	newconn = flap_connection_new(od, SNAC_FAMILY_LOCATE);
 	newconn->cookielen = info->cookielen;
 	newconn->cookie = g_memdup(info->cookie, info->cookielen);
-	newconn->connect_data = purple_proxy_connect(NULL, account, host, port,
-			connection_established_cb, newconn);
+
+	if (od->use_ssl)
+	{
+		/*
+		 * This shouldn't be hardcoded except that the server isn't sending
+		 * us a name to use for comparing the certificate common name.
+		 */
+		newconn->ssl_cert_cn = g_strdup("bos.oscar.aol.com");
+		newconn->connect_data = purple_proxy_connect(NULL, account, host, port,
+				ssl_proxy_conn_established_cb, newconn);
+	}
+	else
+	{
+		newconn->connect_data = purple_proxy_connect(NULL, account, host, port,
+				connection_established_cb, newconn);
+	}
+
 	g_free(host);
 	if (newconn->connect_data == NULL)
 	{
@@ -1874,8 +2012,22 @@
 	else
 		host = g_strdup(redir->ip);
 
-	purple_debug_info("oscar", "Connecting to FLAP server %s:%d of type 0x%04hx\n",
-					host, port, redir->group);
+	/*
+	 * These FLAP servers advertise SSL (type "0x02"), but SSL connections to these hosts
+	 * die a painful death. iChat and Miranda, when using SSL, still do these in plaintext.
+	 */
+	if (redir->use_ssl && (redir->group == SNAC_FAMILY_ADMIN ||
+	                       redir->group == SNAC_FAMILY_BART))
+	{
+		purple_debug_info("oscar", "Ignoring broken SSL for FLAP type 0x%04hx.\n",
+						redir->group);
+		redir->use_ssl = 0;
+	}
+
+	purple_debug_info("oscar", "Connecting to FLAP server %s:%d of type 0x%04hx%s\n",
+					host, port, redir->group,
+					od->use_ssl && !redir->use_ssl ? " without SSL, despite main stream encryption" : "");
+
 	newconn = flap_connection_new(od, redir->group);
 	newconn->cookielen = redir->cookielen;
 	newconn->cookie = g_memdup(redir->cookie, redir->cookielen);
@@ -1893,9 +2045,26 @@
 		purple_debug_info("oscar", "Connecting to chat room %s exchange %hu\n", cc->name, cc->exchange);
 	}
 
-	newconn->connect_data = purple_proxy_connect(NULL, account, host, port,
-			connection_established_cb, newconn);
-	if (newconn->connect_data == NULL)
+
+	if (redir->use_ssl)
+	{
+		/*
+		 * TODO: It should be possible to specify a certificate common name
+		 * distinct from the host we're passing to purple_ssl_connect. The
+		 * way to work around that is to use purple_proxy_connect +
+		 * purple_ssl_connect_with_host_fd
+		 */
+		newconn->ssl_cert_cn = g_strdup(redir->ssl_cert_cn);
+		newconn->connect_data = purple_proxy_connect(NULL, account, host, port,
+				ssl_proxy_conn_established_cb, newconn);
+	}
+	else
+	{
+		newconn->connect_data = purple_proxy_connect(NULL, account, host, port,
+				connection_established_cb, newconn);
+	}
+
+	if (newconn->gsc == NULL && newconn->connect_data == NULL)
 	{
 		flap_connection_schedule_destroy(newconn,
 				OSCAR_DISCONNECT_COULD_NOT_CONNECT,
@@ -6927,6 +7096,10 @@
 	option = purple_account_option_int_new(_("Port"), "port", OSCAR_DEFAULT_LOGIN_PORT);
 	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
 
+	option = purple_account_option_bool_new(_("Use SSL"), "use_ssl",
+			OSCAR_DEFAULT_USE_SSL);
+	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+
 	option = purple_account_option_bool_new(
 		_("Always use AIM/ICQ proxy server for\nfile transfers and direct IM (slower,\nbut does not reveal your IP address)"), "always_use_rv_proxy",
 		OSCAR_DEFAULT_ALWAYS_USE_RV_PROXY);
--- a/libpurple/protocols/oscar/oscar.h	Wed Jan 28 10:16:41 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Wed Jan 28 10:23:37 2009 +0000
@@ -34,6 +34,7 @@
 #include "eventloop.h"
 #include "internal.h"
 #include "proxy.h"
+#include "sslconn.h"
 
 #include <stdio.h>
 #include <string.h>
@@ -417,8 +418,10 @@
 	guint16 cookielen;
 	guint8 *cookie;
 	gpointer new_conn_data;
+	gchar *ssl_cert_cn;
 
 	int fd;
+	PurpleSslConnection *gsc;
 	guint8 header[6];
 	gssize header_received;
 	FlapFrame buffer_incoming;
@@ -476,6 +479,7 @@
 	GHashTable *buddyinfo;
 	GSList *requesticon;
 
+	gboolean use_ssl;
 	gboolean icq;
 	guint getblisttimer;
 
@@ -593,6 +597,8 @@
 	const char *ip;
 	guint16 cookielen;
 	const guint8 *cookie;
+	const char *ssl_cert_cn;
+	guint8 use_ssl;
 	struct { /* group == SNAC_FAMILY_CHAT */
 		guint16 exchange;
 		const char *room;
@@ -616,6 +622,8 @@
 FlapConnection *flap_connection_getbytype(OscarData *, int type);
 FlapConnection *flap_connection_getbytype_all(OscarData *, int type);
 void flap_connection_recv_cb(gpointer data, gint source, PurpleInputCondition cond);
+void flap_connection_recv_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
+
 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);
--- a/libpurple/protocols/oscar/oscarcommon.h	Wed Jan 28 10:16:41 2009 +0000
+++ b/libpurple/protocols/oscar/oscarcommon.h	Wed Jan 28 10:23:37 2009 +0000
@@ -32,6 +32,8 @@
 
 #define OSCAR_DEFAULT_LOGIN_SERVER "login.messaging.aol.com"
 #define OSCAR_DEFAULT_LOGIN_PORT 5190
+#define OSCAR_DEFAULT_SSL_LOGIN_SERVER "slogin.oscar.aol.com"
+#define OSCAR_OLD_LOGIN_SERVER "login.oscar.aol.com"
 #ifndef _WIN32
 #define OSCAR_DEFAULT_CUSTOM_ENCODING "ISO-8859-1"
 #else
@@ -42,6 +44,7 @@
 #define OSCAR_DEFAULT_WEB_AWARE FALSE
 #define OSCAR_DEFAULT_ALWAYS_USE_RV_PROXY FALSE
 #define OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS TRUE
+#define OSCAR_DEFAULT_USE_SSL FALSE
 
 #ifdef _WIN32
 const char *oscar_get_locale_charset(void);
--- a/libpurple/protocols/oscar/peer.c	Wed Jan 28 10:16:41 2009 +0000
+++ b/libpurple/protocols/oscar/peer.c	Wed Jan 28 10:23:37 2009 +0000
@@ -690,7 +690,10 @@
 		return;
 	}
 
-	listener_ip = purple_network_get_my_ip(bos_conn->fd);
+	if (bos_conn->gsc)
+		listener_ip = purple_network_get_my_ip(bos_conn->gsc->fd);
+	else
+		listener_ip = purple_network_get_my_ip(bos_conn->fd);
 	listener_port = purple_network_get_port_from_fd(conn->listenerfd);
 	if (conn->type == OSCAR_CAPABILITY_DIRECTIM)
 	{
--- a/libpurple/status.c	Wed Jan 28 10:16:41 2009 +0000
+++ b/libpurple/status.c	Wed Jan 28 10:23:37 2009 +0000
@@ -817,28 +817,42 @@
 	/* Reset any unspecified attributes to their default value */
 	status_type = purple_status_get_type(status);
 	l = purple_status_type_get_attrs(status_type);
-	while (l != NULL)
-	{
+	while (l != NULL) {
 		PurpleStatusAttr *attr;
 
 		attr = l->data;
-		if (!g_list_find_custom(specified_attr_ids, attr->id, (GCompareFunc)strcmp))
-		{
+		l = l->next;
+
+		if (!g_list_find_custom(specified_attr_ids, attr->id, (GCompareFunc)strcmp)) {
 			PurpleValue *default_value;
 			default_value = purple_status_attr_get_value(attr);
-			if (default_value->type == PURPLE_TYPE_STRING)
-				purple_status_set_attr_string(status, attr->id,
-						purple_value_get_string(default_value));
-			else if (default_value->type == PURPLE_TYPE_INT)
-				purple_status_set_attr_int(status, attr->id,
-						purple_value_get_int(default_value));
-			else if (default_value->type == PURPLE_TYPE_BOOLEAN)
-				purple_status_set_attr_boolean(status, attr->id,
-						purple_value_get_boolean(default_value));
+			if (default_value->type == PURPLE_TYPE_STRING) {
+				const char *cur = purple_status_get_attr_string(status, attr->id);
+				const char *def = purple_value_get_string(default_value);
+				if ((cur == NULL && def == NULL)
+				    || (cur != NULL && def != NULL
+					&& !strcmp(cur, def))) {
+					continue;
+				}
+
+				purple_status_set_attr_string(status, attr->id, def);
+			} else if (default_value->type == PURPLE_TYPE_INT) {
+				int cur = purple_status_get_attr_int(status, attr->id);
+				int def = purple_value_get_int(default_value);
+				if (cur == def)
+					continue;
+
+				purple_status_set_attr_int(status, attr->id, def);
+			} else if (default_value->type == PURPLE_TYPE_BOOLEAN) {
+				gboolean cur = purple_status_get_attr_boolean(status, attr->id);
+				gboolean def = purple_value_get_boolean(default_value);
+				if (cur == def)
+					continue;
+
+				purple_status_set_attr_boolean(status, attr->id, def);
+			}
 			changed = TRUE;
 		}
-
-		l = l->next;
 	}
 	g_list_free(specified_attr_ids);
 
--- a/pidgin/gtkimhtml.c	Wed Jan 28 10:16:41 2009 +0000
+++ b/pidgin/gtkimhtml.c	Wed Jan 28 10:23:37 2009 +0000
@@ -5395,12 +5395,14 @@
 						text_tag_data_destroy(tmp);
 				}
 
-				if (tmp == NULL)
-					purple_debug_warning("gtkimhtml", "empty queue, more closing tags than open tags!\n");
-				else {
+				if (tmp != NULL) {
 					g_string_append(str, tmp->end);
 					text_tag_data_destroy(tmp);
 				}
+#if 0 /* This can't be allowed to happen because it causes the iters to be invalidated in the debug window imhtml during text copying */
+				else
+					purple_debug_warning("gtkimhtml", "empty queue, more closing tags than open tags!\n");
+#endif
 
 				while ((tmp = g_queue_pop_head(r))) {
 					g_string_append(str, tmp->start);
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Wed Jan 28 10:16:41 2009 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Wed Jan 28 10:23:37 2009 +0000
@@ -718,6 +718,7 @@
     Delete "$INSTDIR\ca-certs\StartCom_Free_SSL_CA.pem"
     Delete "$INSTDIR\ca-certs\Verisign_Class3_Primary_CA.pem"
     Delete "$INSTDIR\ca-certs\VeriSign_Class_3_Public_Primary_Certification_Authority_-_G5.pem"
+    Delete "$INSTDIR\ca-certs\VeriSign_International_Server_Class_3_CA.pem"
     Delete "$INSTDIR\ca-certs\Verisign_RSA_Secure_Server_CA.pem"
     RMDir "$INSTDIR\ca-certs"
     RMDir /r "$INSTDIR\locale"
--- a/share/ca-certs/Makefile.am	Wed Jan 28 10:16:41 2009 +0000
+++ b/share/ca-certs/Makefile.am	Wed Jan 28 10:23:37 2009 +0000
@@ -10,7 +10,8 @@
 
 EXTRA_CERTS = \
 		Microsoft_Internet_Authority.pem \
-		Microsoft_Secure_Server_Authority.pem
+		Microsoft_Secure_Server_Authority.pem \
+		VeriSign_International_Server_Class_3_CA.pem
 
 
 cacertsdir =	$(datadir)/purple/ca-certs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/ca-certs/VeriSign_International_Server_Class_3_CA.pem	Wed Jan 28 10:23:37 2009 +0000
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDgzCCAuygAwIBAgIQRvzrurTQLw+SYJgjP5MHjzANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNOTcwNDE3MDAwMDAwWhcNMTYxMDI0MjM1OTU5WjCBujEfMB0GA1UEChMWVmVy
+aVNpZ24gVHJ1c3QgTmV0d29yazEXMBUGA1UECxMOVmVyaVNpZ24sIEluYy4xMzAx
+BgNVBAsTKlZlcmlTaWduIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gQ2xhc3Mg
+MzFJMEcGA1UECxNAd3d3LnZlcmlzaWduLmNvbS9DUFMgSW5jb3JwLmJ5IFJlZi4g
+TElBQklMSVRZIExURC4oYyk5NyBWZXJpU2lnbjCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEA2IKA6NYZAn0fhRg5JaJlK+G/1AXTvOY2O6rwTGxbtueqPHNFVbLx
+veqXQu2aNAoV1Klc9UAl3dkHwTKydWzEyruj/lYncUOqY/UwPpMo5frxCTvzt01O
+OfdcSVq4wR3Tsor+cDCVQsv+K1GLWjw6+SJPkLICp1OcTzTnqwSye28CAwEAAaOB
+4zCB4DAPBgNVHRMECDAGAQH/AgEAMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQEw
+KjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL0NQUzA0BgNV
+HSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIGCWCGSAGG+EIEAQYKYIZIAYb4RQEI
+ATALBgNVHQ8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgEGMDEGA1UdHwQqMCgwJqAk
+oCKGIGh0dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA0GCSqGSIb3DQEB
+BQUAA4GBAECOSZeWinPdjk3vPmG3yqBirfQOCrt1PeJu2CzHv/S5jDabyqLQnHJG
+OfamggNlEcS8vy2m9dk7CrWY+rN4uR7yK0xi1f2yeh3fM/1z+aXYLYwq6tH8sCi2
+6UlIE0uDihtIeyT3ON5vQVS4q1drBt/HotSp9vE2YoCI8ot11oBx
+-----END CERTIFICATE-----