changeset 27956:c815b2aa78ee

propagate from branch 'im.pidgin.pidgin' (head 6b28d34173486d3e4f658f62a61c93b56a489cb0) to branch 'im.pidgin.pidgin.yaz' (head 3ed688e1eeb2baab73799c0a95dc146c862ba339)
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Thu, 25 Jun 2009 12:49:23 +0000
parents 6c044b5083c4 (current diff) 4c4c747112e0 (diff)
children 164b1bc23377
files configure.ac libpurple/conversation.c libpurple/protocols/jabber/google.c libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/message.c libpurple/protocols/msn/msn.c libpurple/protocols/oscar/oscar.c libpurple/protocols/oscar/oscar.h libpurple/protocols/yahoo/util.c libpurple/protocols/yahoo/yahoo.c libpurple/server.c libpurple/util.c pidgin/gtkconv.c pidgin/gtkimhtml.c
diffstat 38 files changed, 1747 insertions(+), 392 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Sun Jun 21 05:28:32 2009 +0000
+++ b/COPYRIGHT	Thu Jun 25 12:49:23 2009 +0000
@@ -224,6 +224,7 @@
 Jochen Kemnade
 Yann Kerherve
 Gordian Klein
+Krzysztof Klinikowski
 Akuke Kok
 Kir Kolyshkin
 F.W. Kong
@@ -332,6 +333,7 @@
 Luke Petre
 Diego Petten
 Nathan Peterson
+Dmitry Petroff
 Sebastián E. Peyrott
 Andrea Piccinelli
 Celso Pinto
--- a/ChangeLog	Sun Jun 21 05:28:32 2009 +0000
+++ b/ChangeLog	Thu Jun 25 12:49:23 2009 +0000
@@ -18,11 +18,13 @@
 	  from you on MSN.
 	* DNS servers are re-read when DNS queries fail in case the system has
 	  moved to a new network and the old servers are not accessible.
-	* Gadu-Gadu accounts can specify a server to which to connect.
+
+	Gadu-Gadu:
+	* Accounts can specify a server to which to connect.
 	  (Krzysztof "kreez" Tobola)
-	* Modifying the MSN privacy list for buddies not added by you (i.e.
-	  spammers and other generally unwanted users) should no longer cause
-	  a 240 error and disconnection.
+	* Correctly show tooltip status for contacts with status messages.
+	  (Krzysztof "kkszysiu" Klinikowski)
+	* Support for fetching buddy icons.  (Krzysztof "kkszysiu" Klinikowski)
 
 	XMPP:
 	* Voice & Video support with Jingle (XEP-0166, 0167, 0176, & 0177), voice
@@ -69,9 +71,6 @@
 	  (Sulabh Mahajan)
 	* Addition of MSN buddies to Yahoo accounts by adding them as
 	  'msn/buddy@somedomain.com' is now supported.  (Sulabh Mahajan)
-	* Yahoo Protocol 16 support, including new HTTPS login method; this should
-	  fix a number of login problems that have recently cropped up.  (Sulabh
-	  Mahajan, Mike "Maiku" Ruprecht)
 
 	Pidgin:
 	* Added -f command line option to tell Pidgin to ignore NetworkManager
@@ -100,6 +99,11 @@
 	* Support saving animated custom smileys as animated images or animated
 	  custom smileys. (Andrea Piccinelli)
 	* Support for keyboard navigation on the status icon. (Li Yuan)
+	* IMG tags without 'id' attributes are turned into links to the image URL.
+	  (Dmitry Petroff)
+	* Draw the user's buddy icon at the bottom of the Buddy List with rounded
+	  corners for visual consistency with the actual icons in the Buddy List.
+	  (Kosta Arvanitis)
 
 	Finch:
 	* The hardware cursor is updated correctly. This will be useful
@@ -113,6 +117,16 @@
 	* Preferences have been reorganized into three tabs for Colors, Fonts, and
 	  Miscellaneous categories.
 
+version 2.5.7 (06/20/2009):
+	* Yahoo Protocol 16 support, including new HTTPS login method; this should
+	  fix a number of login problems that have recently cropped up.  (Sulabh
+	  Mahajan, Mike "Maiku" Ruprecht)
+	* Only display the AIM "Unable to Retrieve Buddy List" message once per
+	  connection.  (Rob Taft)
+	* Blocking MSN users not on your buddy list no longer disconnects you.
+	* When performing operations on MSN, assume users are on the MSN/Passport
+	  network if we don't get network ID's for them.
+
 version 2.5.6 (05/19/2009):
 	libpurple:
 	* Improve sleep behavior by aggregation of longer timeouts on second
--- a/ChangeLog.API	Sun Jun 21 05:28:32 2009 +0000
+++ b/ChangeLog.API	Thu Jun 25 12:49:23 2009 +0000
@@ -128,6 +128,12 @@
 			* Purple::Request::Field::string_new
 			* Purple::Request::Field::group_new
 
+version 2.5.7 (06/20/2009):
+	No changes
+
+version 2.5.6 (05/19/2009):
+	No changes
+
 version 2.5.5 (03/01/2009):
 	libpurple:
 		Changed:
--- a/ChangeLog.win32	Sun Jun 21 05:28:32 2009 +0000
+++ b/ChangeLog.win32	Thu Jun 25 12:49:23 2009 +0000
@@ -1,5 +1,11 @@
 version 2.6.0 (??/??/2009):
 
+version 2.5.7 (06/20/2009):
+	* No changes
+
+version 2.5.6 (05/19/2009):
+	* No changes
+
 version 2.5.5 (03/01/2009):
 	* Remove the "Flash window when chat messages are received" pref from
 	  the Windows Pidgin Options plugin - the Message Notification plugin
--- a/NEWS	Sun Jun 21 05:28:32 2009 +0000
+++ b/NEWS	Thu Jun 25 12:49:23 2009 +0000
@@ -2,26 +2,32 @@
 
 Our development blog is available at: http://planet.pidgin.im
 
+2.5.7 (06/20/2009):
+	John:  This release is really just a rushed fix for the broken Yahoo
+	protocol plugin.  I spent way more time on this release than I care
+	to admit, so I hope that time is well spent and this fixes the issues
+	people have been having.
+
 2.5.6 (05/19/2009):
-    Ka-Hing: Many much bugfixes. Hooray. (Paul told me to say that)
-    Oh, no one has met Paul yet? He's awesome, he backported my fixes
-    to the release branch so I didn't have to checkout a
-    workspace... except I just did to NEWS to tell you all about
-    that. Oh and I actually did do something for this release, none of
-    which is user visible though. This basically applies to the rest
-    of the release as well, nothing exciting, but you definitely want
-    it.
-
-    Daniel: This should fix a number of annoying issues that some users
-    have encountered.  We also would like to thank Veracode
-    (http://www.veracode.com) who performed a code analysis and found some
-    bugs that were addressed in this release.
-
-    Elliott: I feel like I'm repeating myself, but there are some more MSN
-    fixes that should make things better behaved at login as well, and
-    maybe you'll stop getting some of those annoying errors (though not all
-    are fixed yet).  Some other bugfixes, plus the craziness that is the
-    libxml "structured error handler" make up the rest of this release.
+	Ka-Hing: Many much bugfixes. Hooray. (Paul told me to say that)
+	Oh, no one has met Paul yet? He's awesome, he backported my fixes
+	to the release branch so I didn't have to checkout a
+	workspace... except I just did to NEWS to tell you all about
+	that. Oh and I actually did do something for this release, none of
+	which is user visible though. This basically applies to the rest
+	of the release as well, nothing exciting, but you definitely want
+	it.
+
+	Daniel: This should fix a number of annoying issues that some users
+	have encountered.  We also would like to thank Veracode
+	(http://www.veracode.com) who performed a code analysis and found some
+	bugs that were addressed in this release.
+
+	Elliott: I feel like I'm repeating myself, but there are some more MSN
+	fixes that should make things better behaved at login as well, and
+	maybe you'll stop getting some of those annoying errors (though not all
+	are fixed yet).  Some other bugfixes, plus the craziness that is the
+	libxml "structured error handler" make up the rest of this release.
 
 2.5.5 (03/01/2009):
 	John: Well, yet another release with bug fixing and patches.  Hopefully
--- a/configure.ac	Sun Jun 21 05:28:32 2009 +0000
+++ b/configure.ac	Thu Jun 25 12:49:23 2009 +0000
@@ -1663,7 +1663,7 @@
 	[enable_nss="$enableval"],
 	[enable_nss="yes"])
 
-msg_ssl="None. MSN, Novell Groupwise, Yahoo! and Google Talk will not work without GnuTLS or NSS. OpenSSL is NOT usable!"
+msg_ssl="None. MSN, Yahoo!, Novell Groupwise and Google Talk will not work without GnuTLS or NSS. OpenSSL is NOT usable!"
 looked_for_gnutls="no"
 dnl #
 dnl # Check for GnuTLS if it's specified.
@@ -2057,19 +2057,19 @@
 	AC_MSG_ERROR([
 Neither GnuTLS or NSS SSL development headers found.
 Use --disable-nss --disable-gnutls if you do not need SSL support.
-MSN, Novell Groupwise and Google Talk will not work without GnuTLS or NSS. OpenSSL is NOT usable!
+MSN, Yahoo!, Novell Groupwise and Google Talk will not work without GnuTLS or NSS. OpenSSL is NOT usable!
 ])
 elif test "x$looked_for_gnutls" = "xyes" -a "x$force_deps" = "xyes" ; then
 	AC_MSG_ERROR([
 GnuTLS SSL development headers not found.
 Use --disable-gnutls if you do not need SSL support.
-MSN, Novell Groupwise and Google Talk will not work without SSL support.
+MSN, Yahoo!, Novell Groupwise and Google Talk will not work without SSL support.
 ])
 elif test "x$looked_for_nss" = "xyes" -a "x$force_deps" = "xyes" ; then
 	AC_MSG_ERROR([
 NSS SSL development headers not found.
 Use --disable-nss if you do not need SSL support.
-MSN, Novell Groupwise and Google Talk will not work without SSL support.
+MSN, Yahoo!, Novell Groupwise and Google Talk will not work without SSL support.
 ])
 fi
 
--- a/libpurple/cipher.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/cipher.c	Thu Jun 25 12:49:23 2009 +0000
@@ -1862,6 +1862,261 @@
 };
 
 /*******************************************************************************
+ * SHA-256
+ ******************************************************************************/
+#define SHA256_HMAC_BLOCK_SIZE	64
+#define SHA256_ROTR(X,n) ((((X) >> (n)) | ((X) << (32-(n)))) & 0xFFFFFFFF)
+
+static const guint32 sha256_K[64] =
+{
+	0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+	0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+	0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+	0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+	0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+	0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+	0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+	0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+struct SHA256Context {
+	guint32 H[8];
+	guint32 W[64];
+
+	gint lenW;
+
+	guint32 sizeHi;
+	guint32 sizeLo;
+};
+
+static void
+sha256_hash_block(struct SHA256Context *sha256_ctx) {
+	gint i;
+	guint32 A, B, C, D, E, F, G, H, T1, T2;
+
+	for(i = 16; i < 64; i++) {
+		sha256_ctx->W[i] =
+			  (SHA256_ROTR(sha256_ctx->W[i-2], 17) ^ SHA256_ROTR(sha256_ctx->W[i-2],  19) ^ (sha256_ctx->W[i-2] >> 10))
+			+ sha256_ctx->W[i-7]
+			+ (SHA256_ROTR(sha256_ctx->W[i-15], 7) ^ SHA256_ROTR(sha256_ctx->W[i-15], 18) ^ (sha256_ctx->W[i-15] >> 3))
+			+ sha256_ctx->W[i-16];
+	}
+
+	A = sha256_ctx->H[0];
+	B = sha256_ctx->H[1];
+	C = sha256_ctx->H[2];
+	D = sha256_ctx->H[3];
+	E = sha256_ctx->H[4];
+	F = sha256_ctx->H[5];
+	G = sha256_ctx->H[6];
+	H = sha256_ctx->H[7];
+
+	for(i = 0; i < 64; i++) {
+        T1 = H
+			+ (SHA256_ROTR(E, 6) ^ SHA256_ROTR(E, 11) ^ SHA256_ROTR(E, 25))
+			+ ((E & F) ^ ((~E) & G))
+			+ sha256_K[i] + sha256_ctx->W[i];
+        T2 = (SHA256_ROTR(A, 2) ^ SHA256_ROTR(A, 13) ^ SHA256_ROTR(A, 22))
+			+ ((A & B) ^ (A & C) ^ (B & C));
+		H = G;
+		G = F;
+		F = E;
+		E = D + T1;
+		D = C;
+		C = B;
+		B = A;
+		A = T1 + T2;
+	}
+
+	sha256_ctx->H[0] += A;
+	sha256_ctx->H[1] += B;
+	sha256_ctx->H[2] += C;
+	sha256_ctx->H[3] += D;
+	sha256_ctx->H[4] += E;
+	sha256_ctx->H[5] += F;
+	sha256_ctx->H[6] += G;
+	sha256_ctx->H[7] += H;
+}
+
+static void
+sha256_set_opt(PurpleCipherContext *context, const gchar *name, void *value) {
+	struct SHA256Context *ctx;
+
+	ctx = purple_cipher_context_get_data(context);
+
+	if(!strcmp(name, "sizeHi")) {
+		ctx->sizeHi = GPOINTER_TO_INT(value);
+	} else if(!strcmp(name, "sizeLo")) {
+		ctx->sizeLo = GPOINTER_TO_INT(value);
+	} else if(!strcmp(name, "lenW")) {
+		ctx->lenW = GPOINTER_TO_INT(value);
+	}
+}
+
+static void *
+sha256_get_opt(PurpleCipherContext *context, const gchar *name) {
+	struct SHA256Context *ctx;
+
+	ctx = purple_cipher_context_get_data(context);
+
+	if(!strcmp(name, "sizeHi")) {
+		return GINT_TO_POINTER(ctx->sizeHi);
+	} else if(!strcmp(name, "sizeLo")) {
+		return GINT_TO_POINTER(ctx->sizeLo);
+	} else if(!strcmp(name, "lenW")) {
+		return GINT_TO_POINTER(ctx->lenW);
+	}
+
+	return NULL;
+}
+
+static void
+sha256_init(PurpleCipherContext *context, void *extra) {
+	struct SHA256Context *sha256_ctx;
+
+	sha256_ctx = g_new0(struct SHA256Context, 1);
+
+	purple_cipher_context_set_data(context, sha256_ctx);
+
+	purple_cipher_context_reset(context, extra);
+}
+
+static void
+sha256_reset(PurpleCipherContext *context, void *extra) {
+	struct SHA256Context *sha256_ctx;
+	gint i;
+
+	sha256_ctx = purple_cipher_context_get_data(context);
+
+	g_return_if_fail(sha256_ctx);
+
+	sha256_ctx->lenW = 0;
+	sha256_ctx->sizeHi = 0;
+	sha256_ctx->sizeLo = 0;
+
+	sha256_ctx->H[0] = 0x6a09e667;
+	sha256_ctx->H[1] = 0xbb67ae85;
+	sha256_ctx->H[2] = 0x3c6ef372;
+	sha256_ctx->H[3] = 0xa54ff53a;
+	sha256_ctx->H[4] = 0x510e527f;
+	sha256_ctx->H[5] = 0x9b05688c;
+	sha256_ctx->H[6] = 0x1f83d9ab;
+	sha256_ctx->H[7] = 0x5be0cd19;
+
+	for(i = 0; i < 64; i++)
+		sha256_ctx->W[i] = 0;
+}
+
+static void
+sha256_uninit(PurpleCipherContext *context) {
+	struct SHA256Context *sha256_ctx;
+
+	purple_cipher_context_reset(context, NULL);
+
+	sha256_ctx = purple_cipher_context_get_data(context);
+
+	memset(sha256_ctx, 0, sizeof(struct SHA256Context));
+
+	g_free(sha256_ctx);
+	sha256_ctx = NULL;
+}
+
+
+static void
+sha256_append(PurpleCipherContext *context, const guchar *data, size_t len) {
+	struct SHA256Context *sha256_ctx;
+	gint i;
+
+	sha256_ctx = purple_cipher_context_get_data(context);
+
+	g_return_if_fail(sha256_ctx);
+
+	for(i = 0; i < len; i++) {
+		sha256_ctx->W[sha256_ctx->lenW / 4] <<= 8;
+		sha256_ctx->W[sha256_ctx->lenW / 4] |= data[i];
+
+		if((++sha256_ctx->lenW) % 64 == 0) {
+			sha256_hash_block(sha256_ctx);
+			sha256_ctx->lenW = 0;
+		}
+
+		sha256_ctx->sizeLo += 8;
+		sha256_ctx->sizeHi += (sha256_ctx->sizeLo < 8);
+	}
+}
+
+static gboolean
+sha256_digest(PurpleCipherContext *context, size_t in_len, guchar digest[32],
+			size_t *out_len)
+{
+	struct SHA256Context *sha256_ctx;
+	guchar pad0x80 = 0x80, pad0x00 = 0x00;
+	guchar padlen[8];
+	gint i;
+
+	g_return_val_if_fail(in_len >= 32, FALSE);
+
+	sha256_ctx = purple_cipher_context_get_data(context);
+
+	g_return_val_if_fail(sha256_ctx, FALSE);
+
+	padlen[0] = (guchar)((sha256_ctx->sizeHi >> 24) & 255);
+	padlen[1] = (guchar)((sha256_ctx->sizeHi >> 16) & 255);
+	padlen[2] = (guchar)((sha256_ctx->sizeHi >> 8) & 255);
+	padlen[3] = (guchar)((sha256_ctx->sizeHi >> 0) & 255);
+	padlen[4] = (guchar)((sha256_ctx->sizeLo >> 24) & 255);
+	padlen[5] = (guchar)((sha256_ctx->sizeLo >> 16) & 255);
+	padlen[6] = (guchar)((sha256_ctx->sizeLo >> 8) & 255);
+	padlen[7] = (guchar)((sha256_ctx->sizeLo >> 0) & 255);
+
+	/* pad with a 1, then zeroes, then length */
+	purple_cipher_context_append(context, &pad0x80, 1);
+	while(sha256_ctx->lenW != 56)
+		purple_cipher_context_append(context, &pad0x00, 1);
+	purple_cipher_context_append(context, padlen, 8);
+
+	for(i = 0; i < 32; i++) {
+		digest[i] = (guchar)(sha256_ctx->H[i / 4] >> 24);
+		sha256_ctx->H[i / 4] <<= 8;
+	}
+
+	purple_cipher_context_reset(context, NULL);
+
+	if(out_len)
+		*out_len = 32;
+
+	return TRUE;
+}
+
+static size_t
+sha256_get_block_size(PurpleCipherContext *context)
+{
+	/* This does not change (in this case) */
+	return SHA256_HMAC_BLOCK_SIZE;
+}
+
+static PurpleCipherOps SHA256Ops = {
+	sha256_set_opt,	/* Set Option		*/
+	sha256_get_opt,	/* Get Option		*/
+	sha256_init,	/* init				*/
+	sha256_reset,	/* reset			*/
+	sha256_uninit,	/* uninit			*/
+	NULL,			/* set iv			*/
+	sha256_append,	/* append			*/
+	sha256_digest,	/* digest			*/
+	NULL,			/* encrypt			*/
+	NULL,			/* decrypt			*/
+	NULL,			/* set salt			*/
+	NULL,			/* get salt size	*/
+	NULL,			/* set key			*/
+	NULL,			/* get key size		*/
+	NULL,			/* set batch mode */
+	NULL,			/* get batch mode */
+	sha256_get_block_size,	/* get block size */
+	NULL			/* set key with len */
+};
+
+/*******************************************************************************
  * RC4
  ******************************************************************************/
 
@@ -2228,6 +2483,7 @@
 
 	purple_ciphers_register_cipher("md5", &MD5Ops);
 	purple_ciphers_register_cipher("sha1", &SHA1Ops);
+	purple_ciphers_register_cipher("sha256", &SHA256Ops);
 	purple_ciphers_register_cipher("md4", &MD4Ops);
 	purple_ciphers_register_cipher("hmac", &HMACOps);
 	purple_ciphers_register_cipher("des", &DESOps);
--- a/libpurple/protocols/gg/gg.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/gg/gg.c	Thu Jun 25 12:49:23 2009 +0000
@@ -7,6 +7,7 @@
  *
  * Some parts of the code are adapted or taken from the previous implementation
  * of this plugin written by Arkadiusz Miskiewicz <misiek@pld.org.pl>
+ * Some parts Copyright (C) 2009  Krzysztof Klinikowski <grommasher@gmail.com>
  *
  * Thanks to Google's Summer of Code Program.
  *
@@ -36,6 +37,7 @@
 #include "debug.h"
 #include "util.h"
 #include "request.h"
+#include "xmlnode.h"
 
 #include <libgadu.h>
 
@@ -858,6 +860,133 @@
 static void ggp_set_status(PurpleAccount *account, PurpleStatus *status);
 static int ggp_to_gg_status(PurpleStatus *status, char **msg);
 
+struct gg_fetch_avatar_data
+{
+	PurpleConnection *gc;
+	gchar *uin;
+	gchar *avatar_url;
+};
+
+
+static void gg_fetch_avatar_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
+               const gchar *data, size_t len, const gchar *error_message) {
+	struct gg_fetch_avatar_data *d = user_data;
+	PurpleAccount *account;
+	PurpleBuddy *buddy;
+	gpointer buddy_icon_data;
+
+	/* FIXME: This shouldn't be necessary */
+	if (!PURPLE_CONNECTION_IS_VALID(d->gc)) {
+		g_free(d->uin);
+		g_free(d->avatar_url);
+		g_free(d);
+		g_return_if_reached();
+	}
+
+	account = purple_connection_get_account(d->gc);
+	buddy = purple_find_buddy(account, d->uin);
+
+	if (buddy == NULL)
+		goto out;
+
+	buddy_icon_data = g_memdup(data, len);
+
+	purple_buddy_icons_set_for_user(account, purple_buddy_get_name(buddy),
+			buddy_icon_data, len, d->avatar_url);
+	purple_debug_info("gg", "UIN: %s should have avatar now\n", d->uin);
+
+out:
+	g_free(d->uin);
+	g_free(d->avatar_url);
+	g_free(d);
+}
+
+static void gg_get_avatar_url_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
+               const gchar *url_text, size_t len, const gchar *error_message) {
+	struct gg_fetch_avatar_data *data;
+	PurpleConnection *gc = user_data;
+	PurpleAccount *account;
+	PurpleBuddy *buddy;
+	const char *uin;
+	const char *is_blank;
+	const char *checksum;
+
+	gchar *bigavatar = NULL;
+	xmlnode *xml = NULL;
+	xmlnode *xmlnode_users;
+	xmlnode *xmlnode_user;
+	xmlnode *xmlnode_avatars;
+	xmlnode *xmlnode_avatar;
+	xmlnode *xmlnode_bigavatar;
+
+	g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
+	account = purple_connection_get_account(gc);
+
+	if (error_message != NULL)
+		purple_debug_error("gg", "gg_get_avatars_cb error: %s\n", error_message);
+	else if (len > 0 && url_text && *url_text) {
+		xml = xmlnode_from_str(url_text, -1);
+		if (xml == NULL)
+			goto out;
+
+		xmlnode_users = xmlnode_get_child(xml, "users");
+		if (xmlnode_users == NULL)
+			goto out;
+
+		xmlnode_user = xmlnode_get_child(xmlnode_users, "user");
+		if (xmlnode_user == NULL)
+			goto out;
+
+		uin = xmlnode_get_attrib(xmlnode_user, "uin");
+
+		xmlnode_avatars = xmlnode_get_child(xmlnode_user, "avatars");
+		if (xmlnode_avatars == NULL)
+			goto out;
+
+		xmlnode_avatar = xmlnode_get_child(xmlnode_avatars, "avatar");
+		if (xmlnode_avatar == NULL)
+			goto out;
+
+		xmlnode_bigavatar = xmlnode_get_child(xmlnode_avatar, "bigAvatar");
+		if (xmlnode_bigavatar == NULL)
+			goto out;
+
+		is_blank = xmlnode_get_attrib(xmlnode_avatar, "blank");
+		bigavatar = xmlnode_get_data(xmlnode_bigavatar);
+
+		purple_debug_info("gg", "gg_get_avatar_url_cb: UIN %s, IS_BLANK %s, "
+		                        "URL %s\n",
+		                  uin ? uin : "(null)", is_blank ? is_blank : "(null)",
+		                  bigavatar ? bigavatar : "(null)");
+
+		if (uin != NULL && bigavatar != NULL) {
+			buddy = purple_find_buddy(account, uin);
+			if (buddy == NULL)
+				goto out;
+
+			checksum = purple_buddy_icons_get_checksum_for_user(buddy);
+
+			if (purple_strequal(is_blank, "1")) {
+				purple_buddy_icons_set_for_user(account,
+						purple_buddy_get_name(buddy), NULL, 0, NULL);
+			} else if (!purple_strequal(checksum, bigavatar)) {
+				data = g_new0(struct gg_fetch_avatar_data, 1);
+				data->gc = gc;
+				data->uin = g_strdup(uin);
+				data->avatar_url = g_strdup(bigavatar);
+
+				url_data = purple_util_fetch_url_request_len_with_account(account,
+						bigavatar, TRUE, "Mozilla/4.0 (compatible; MSIE 5.0)",
+						FALSE, NULL, FALSE, -1, gg_fetch_avatar_cb, data);
+			}
+		}
+	}
+
+out:
+	if (xml)
+		xmlnode_free(xml);
+	g_free(bigavatar);
+}
 
 /**
  * Handle change of the status of the buddy.
@@ -873,8 +1002,19 @@
 	gchar *from;
 	const char *st;
 	gchar *msg;
-
-	from = g_strdup_printf("%ld", (unsigned long int)uin);
+	gchar *avatarurl;
+	PurpleUtilFetchUrlData *url_data;
+
+	from = g_strdup_printf("%u", uin);
+	avatarurl = g_strdup_printf("http://api.gadu-gadu.pl/avatars/%s/0.xml", from);
+
+	url_data = purple_util_fetch_url_request_len_with_account(
+			purple_connection_get_account(gc), avatarurl, TRUE,
+			"Mozilla/4.0 (compatible; MSIE 5.5)", FALSE, NULL, FALSE, -1,
+			gg_get_avatar_url_cb, gc);
+
+	g_free(avatarurl);
+
 	switch (status) {
 		case GG_STATUS_NOT_AVAIL:
 		case GG_STATUS_NOT_AVAIL_DESCR:
@@ -931,12 +1071,15 @@
 	purple_debug_info("gg", "ggp_status_by_id: %d\n", id);
 	switch (id) {
 		case GG_STATUS_NOT_AVAIL:
+		case GG_STATUS_NOT_AVAIL_DESCR:
 			st = _("Offline");
 			break;
 		case GG_STATUS_AVAIL:
+		case GG_STATUS_AVAIL_DESCR:
 			st = _("Available");
 			break;
 		case GG_STATUS_BUSY:
+		case GG_STATUS_BUSY_DESCR:
 			st = _("Away");
 			break;
 		default:
--- a/libpurple/protocols/jabber/auth.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/jabber/auth.c	Thu Jun 25 12:49:23 2009 +0000
@@ -990,12 +990,16 @@
 			xmlnode_set_namespace(response, "urn:ietf:params:xml:ns:xmpp-sasl");
 			if (clen > 0) {
 				/* Cyrus SASL 2.1.22 appears to contain code to add the charset
-				 * to the response but there is no possibility it will be executed.
+				 * to the response for DIGEST-MD5 but there is no possibility
+				 * it will be executed.
+				 *
 				 * My reading of the digestmd5 plugin indicates the username and
 				 * 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 (strstr(c_out, ",charset="))
+				if (!js->current_mech || !g_str_equal(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);
 				else {
 					char *tmp = g_strdup_printf("%s,charset=utf-8", c_out);
--- a/libpurple/protocols/jabber/bosh.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/jabber/bosh.c	Thu Jun 25 12:49:23 2009 +0000
@@ -243,7 +243,7 @@
 	/* Easy solution: Does everyone involved support pipelining? Hooray! Just use
 	 * one TCP connection! */
 	if (conn->pipelining)
-		return conn->connections[0];
+		return conn->connections[0]->ready ? conn->connections[0] : NULL;
 
 	/* First loop, look for a connection that's ready */
 	for (i = 0; i < MAX_HTTP_CONNECTIONS; ++i) {
@@ -267,8 +267,8 @@
 }
 
 static void
-jabber_bosh_connection_send(PurpleBOSHConnection *conn, PurpleBOSHPacketType type,
-                            const char *data)
+jabber_bosh_connection_send(PurpleBOSHConnection *conn,
+                            PurpleBOSHPacketType type, const char *data)
 {
 	PurpleHTTPConnection *chosen;
 	GString *packet = NULL;
@@ -277,14 +277,16 @@
 
 	if (type != PACKET_NORMAL && !chosen) {
 		/*
-		 * For non-ordinary traffic, we don't want to 'buffer' it, so use the first
-		 * connection.
+		 * For non-ordinary traffic, we don't want to 'buffer' it, so use the
+		 * first connection.
 		 */
 		chosen = conn->connections[0];
 
-		if (!chosen->ready)
-			purple_debug_warning("jabber", "First BOSH connection wasn't ready. Bad "
-					"things may happen.\n");
+		if (!chosen->ready) {
+			purple_debug_info("jabber", "Unable to find a ready BOSH "
+					"connection. Ignoring send of type 0x%02x.\n", type);
+			return;
+		}
 	}
 
 	if (type == PACKET_NORMAL && (!chosen ||
--- a/libpurple/protocols/jabber/caps.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/jabber/caps.c	Thu Jun 25 12:49:23 2009 +0000
@@ -534,6 +534,31 @@
 		return;
 	}
 
+	node_exts = (userdata->data->info ? userdata->data->info->exts :
+	                                    userdata->data->node_exts);
+
+	/* TODO: I don't see how this can actually happen, but it crashed khc. */
+	if (!node_exts) {
+		purple_debug_error("jabber", "Couldn't find JabberCapsNodeExts. If you "
+				"see this, please tell darkrain42 and save your debug log.\n"
+				"JabberCapsClientInfo = %p\n", userdata->data->info);
+
+
+		/* Try once more to find the exts and then fail */
+		node_exts = jabber_caps_find_exts_by_node(userdata->data->node);
+		if (node_exts) {
+			purple_debug_info("jabber", "Found the exts on the second try.\n");
+			if (userdata->data->info)
+				userdata->data->info->exts = node_exts;
+			else
+				userdata->data->node_exts = node_exts;
+		} else {
+			cbplususerdata_unref(userdata->data);
+			g_free(userdata);
+			g_return_if_reached();
+		}
+	}
+
 	/* So, we decrement this after checking for an error, which means that
 	 * if there *is* an error, we'll never call the callback passed to
 	 * jabber_caps_get_info. We will still free all of our data, though.
@@ -547,8 +572,6 @@
 			features = g_list_prepend(features, g_strdup(var));
 	}
 
-	node_exts = (userdata->data->info ? userdata->data->info->exts :
-	                                    userdata->data->node_exts);
 	g_hash_table_insert(node_exts->exts, g_strdup(userdata->name), features);
 	schedule_caps_save();
 
@@ -589,9 +612,7 @@
 	}
 
 	userdata = g_new0(jabber_caps_cbplususerdata, 1);
-	/* This ref is given to fetching the basic node#ver info if we need it
-	 * or unrefed at the bottom of this function */
-	cbplususerdata_ref(userdata);
+	/* We start out with 0 references. Every query takes one */
 	userdata->cb = cb;
 	userdata->cb_data = user_data;
 	userdata->who = g_strdup(who);
@@ -617,6 +638,8 @@
 		g_free(nodever);
 		xmlnode_set_attrib(iq->node, "to", who);
 
+		cbplususerdata_ref(userdata);
+
 		jabber_iq_set_callback(iq, jabber_caps_client_iqcb, userdata);
 		jabber_iq_send(iq);
 	}
@@ -669,6 +692,10 @@
 	}
 
 	if (userdata->info && userdata->extOutstanding == 0) {
+		/* Start with 1 ref so the below functions are happy */
+		userdata->ref = 1;
+
+		/* We have everything we need right now */
 		jabber_caps_get_info_complete(userdata);
 		cbplususerdata_unref(userdata);
 	}
--- a/libpurple/protocols/jabber/jabber.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Thu Jun 25 12:49:23 2009 +0000
@@ -760,6 +760,8 @@
 	PurpleConnection *gc = purple_account_get_connection(account);
 	const char *connect_server = purple_account_get_string(account,
 			"connect_server", "");
+	const char *bosh_url = purple_account_get_string(account,
+			"bosh_url", "");
 	JabberStream *js;
 	PurplePresence *presence;
 	PurpleStoredImage *image;
@@ -826,21 +828,25 @@
 	jabber_stream_set_state(js, JABBER_STREAM_CONNECTING);
 
 	/* TODO: Just use purple_url_parse? */
-	if (!g_ascii_strncasecmp(connect_server, "http://", 7) || !g_ascii_strncasecmp(connect_server, "https://", 8)) {
+	/* If both BOSH and a Connect Server are specified, we prefer BOSH. I'm not
+	 * attached to that choice, though.
+	 */
+	if (*bosh_url) {
 		js->use_bosh = TRUE;
-		js->bosh = jabber_bosh_connection_init(js, connect_server);
-		if (!js->bosh) {
+		js->bosh = jabber_bosh_connection_init(js, bosh_url);
+		if (js->bosh)
+			jabber_bosh_connection_connect(js->bosh);
+		else {
 			purple_connection_error_reason (js->gc,
 				PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
-				_("Malformed BOSH Connect Server"));
-			return;
+				_("Malformed BOSH URL"));
 		}
-		jabber_bosh_connection_connect(js->bosh);
+
 		return;
-	} else {
-		js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user->domain);
 	}
 
+	js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user->domain);
+
 	/* if they've got old-ssl mode going, we probably want to ignore SRV lookups */
 	if(purple_account_get_bool(js->gc->account, "old_ssl", FALSE)) {
 		if(purple_ssl_is_supported()) {
--- a/libpurple/protocols/jabber/libxmpp.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Thu Jun 25 12:49:23 2009 +0000
@@ -319,6 +319,11 @@
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
 						  option);
 
+	option = purple_account_option_string_new(_("BOSH URL"),
+						  "bosh_url", NULL);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+						  option);
+
 	/* this should probably be part of global smiley theme settings later on,
 	  shared with MSN */
 	option = purple_account_option_bool_new(_("Show Custom Smileys"),
--- a/libpurple/protocols/jabber/useravatar.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/jabber/useravatar.c	Thu Jun 25 12:49:23 2009 +0000
@@ -257,6 +257,8 @@
                                gsize len, const gchar *error_message)
 {
 	JabberBuddyAvatarUpdateURLInfo *info = user_data;
+	gpointer icon_data;
+
 	if(!url_text) {
 		purple_debug(PURPLE_DEBUG_ERROR, "jabber",
 		             "do_buddy_avatar_update_fromurl got error \"%s\"",
@@ -264,7 +266,8 @@
 		goto out;
 	}
 
-	purple_buddy_icons_set_for_user(purple_connection_get_account(info->js->gc), info->from, (void*)url_text, len, info->id);
+	icon_data = g_memdup(url_text, len);
+	purple_buddy_icons_set_for_user(purple_connection_get_account(info->js->gc), info->from, icon_data, len, info->id);
 
 out:
 	g_free(info->from);
--- a/libpurple/protocols/myspace/markup.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/myspace/markup.c	Thu Jun 25 12:49:23 2009 +0000
@@ -112,8 +112,10 @@
 	}
 }
 
-/** Convert typographical font point size to HTML font size.
- * Based on libpurple/gtkimhtml.c */
+/**
+ * Convert typographical font point size to HTML font size.
+ * Based on libpurple/gtkimhtml.c
+ */
 static guint
 msim_point_to_purple_size(MsimSession *session, guint point)
 {
@@ -135,7 +137,9 @@
 	return this_point;
 }
 
-/** Convert HTML font size to point size. */
+/**
+ * Convert HTML font size to point size.
+ */
 static guint
 msim_purple_size_to_point(MsimSession *session, guint size)
 {
@@ -155,7 +159,9 @@
 	return point;
 }
 
-/** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */
+/**
+ * Convert a msim markup font pixel height to the more usual point size, for incoming messages.
+ */
 static guint
 msim_height_to_point(MsimSession *session, guint height)
 {
@@ -169,7 +175,9 @@
 	 * _font_size_ichat_to_purple */
 }
 
-/** Convert point size to msim pixel height font size specification, for outgoing messages. */
+/**
+ * Convert point size to msim pixel height font size specification, for outgoing messages.
+ */
 static guint
 msim_point_to_height(MsimSession *session, guint point)
 {
@@ -180,7 +188,9 @@
 	return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point);
 }
 
-/** Convert the msim markup <f> (font) tag into HTML. */
+/**
+ * Convert the msim markup <f> (font) tag into HTML.
+ */
 static void
 msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
 {
@@ -244,7 +254,8 @@
 	*end = g_string_free(gs_end, FALSE);
 }
 
-/** Convert a msim markup color to a color suitable for libpurple.
+/**
+ * Convert a msim markup color to a color suitable for libpurple.
  *
  * @param msim Either a color name, or an rgb(x,y,z) code.
  *
@@ -268,7 +279,9 @@
 	return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue);
 }
 
-/** Convert the msim markup <a> (anchor) tag into HTML. */
+/**
+ * Convert the msim markup <a> (anchor) tag into HTML.
+ */
 static void
 msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
 {
@@ -283,7 +296,9 @@
 	*end = g_strdup("</a>");
 }
 
-/** Convert the msim markup <p> (paragraph) tag into HTML. */
+/**
+ * Convert the msim markup <p> (paragraph) tag into HTML.
+ */
 static void
 msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
 {
@@ -356,7 +371,9 @@
 	g_free(purple_color);
 }
 
-/** Convert the msim markup <i> tag (emoticon image) into HTML. */
+/**
+ * Convert the msim markup <i> tag (emoticon image) into HTML.
+ */
 static void
 msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
 {
@@ -387,7 +404,9 @@
 	*end = g_strdup("");
 }
 
-/** Convert an individual msim markup tag to HTML. */
+/**
+ * Convert an individual msim markup tag to HTML.
+ */
 static int
 msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin,
 		gchar **end)
@@ -416,7 +435,9 @@
 	return 0;
 }
 
-/** Convert an individual HTML tag to msim markup. */
+/**
+ * Convert an individual HTML tag to msim markup.
+ */
 static int
 html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin,
 		gchar **end)
@@ -568,7 +589,8 @@
 	return ret;
 }
 
-/** Convert an xmlnode of msim markup or HTML to an HTML string or msim markup.
+/**
+ * Convert an xmlnode of msim markup or HTML to an HTML string or msim markup.
  *
  * @param f Function to convert tags.
  *
@@ -635,7 +657,9 @@
 	g_free(end);
 }
 
-/** Convert XML to something based on MSIM_XMLNODE_CONVERT. */
+/**
+ * Convert XML to something based on MSIM_XMLNODE_CONVERT.
+ */
 static gchar *
 msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f)
 {
@@ -669,7 +693,8 @@
 	return g_string_free(str, FALSE);
 }
 
-/** Convert plaintext smileys to <i> markup tags.
+/**
+ * Convert plaintext smileys to <i> markup tags.
  *
  * @param before Original text with ASCII smileys. Will be freed.
  * @return A new string with <i> tags, if applicable. Must be g_free()'d.
--- a/libpurple/protocols/myspace/myspace.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Thu Jun 25 12:49:23 2009 +0000
@@ -519,6 +519,18 @@
 	return types;
 }
 
+/*
+ * TODO: This define is stolen from oscar.h.
+ *       It's also in yahoo.h.
+ *       It should be in libpurple/util.c
+ */
+#define msim_put32(buf, data) ( \
+		(*((buf)) = (unsigned char)((data)>>24)&0xff), \
+		(*((buf)+1) = (unsigned char)((data)>>16)&0xff), \
+		(*((buf)+2) = (unsigned char)((data)>>8)&0xff), \
+		(*((buf)+3) = (unsigned char)(data)&0xff), \
+		4)
+
 /**
  * Compute the base64'd login challenge response based on username, password, nonce, and IPs.
  *
@@ -619,15 +631,27 @@
 	purple_cipher_context_set_option(rc4, "key_len", (gpointer)0x10);
 	purple_cipher_context_set_key(rc4, key);
 
-	/* TODO: obtain IPs of network interfaces */
-
 	/* rc4 encrypt:
 	 * nonce1+email+IP list */
 
 	data = g_string_new(NULL);
 	g_string_append_len(data, nonce, NONCE_SIZE);
-	g_string_append(data, email);
+
+	/* Include the null terminator */
+	g_string_append_len(data, email, strlen(email) + 1);
+
+	while (data->len % 4 != 0)
+		g_string_append_c(data, 0xfb);
+
+#ifdef SEND_OUR_IP_ADDRESSES
+	/* TODO: Obtain IPs of network interfaces instead of using this hardcoded value */
+	g_string_set_size(data, data->len + 4);
+	msim_put32(data->str + data->len - 4, MSIM_LOGIN_IP_LIST_LEN);
 	g_string_append_len(data, MSIM_LOGIN_IP_LIST, MSIM_LOGIN_IP_LIST_LEN);
+#else
+	g_string_set_size(data, data->len + 4);
+	msim_put32(data->str + data->len - 4, 0);
+#endif /* !SEND_OUR_IP_ADDRESSES */
 
 	data_out = g_new0(guchar, data->len);
 
@@ -1661,8 +1685,8 @@
 	switch (bm) {
 		case MSIM_BM_STATUS:
 			return msim_incoming_status(session, msg);
-		case MSIM_BM_INSTANT_ACTION_OR_IM:
-		case MSIM_BM_DELAYABLE_ACTION_OR_IM:
+		case MSIM_BM_ACTION_OR_IM_DELAYABLE:
+		case MSIM_BM_ACTION_OR_IM_INSTANT:
 			return msim_incoming_action_or_im(session, msg);
 		case MSIM_BM_MEDIA:
 			return msim_incoming_media(session, msg);
@@ -2287,7 +2311,7 @@
 
 	message_msim = html_to_msim_markup(session, message);
 
-	if (msim_send_bm(session, who, message_msim, MSIM_BM_DELAYABLE_ACTION_OR_IM)) {
+	if (msim_send_bm(session, who, message_msim, MSIM_BM_ACTION_OR_IM_DELAYABLE)) {
 		/* Return 1 to have Purple show this IM as being sent, 0 to not. I always
 		 * return 1 even if the message could not be sent, since I don't know if
 		 * it has failed yet--because the IM is only sent after the userid is
@@ -2340,7 +2364,7 @@
 	}
 
 	purple_debug_info("msim", "msim_send_typing(%s): %d (%s)\n", name, state, typing_str);
-	msim_send_bm(session, name, typing_str, MSIM_BM_INSTANT_ACTION_OR_IM);
+	msim_send_bm(session, name, typing_str, MSIM_BM_ACTION_OR_IM_INSTANT);
 	return 0;
 }
 
--- a/libpurple/protocols/myspace/myspace.h	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/myspace/myspace.h	Thu Jun 25 12:49:23 2009 +0000
@@ -127,9 +127,9 @@
 #define MSIM_FINAL_STRING           "\\final\\" /**< Message end marker */
 
 /* Messages */
-#define MSIM_BM_DELAYABLE_ACTION_OR_IM  1
+#define MSIM_BM_ACTION_OR_IM_DELAYABLE  1
 #define MSIM_BM_STATUS                  100
-#define MSIM_BM_INSTANT_ACTION_OR_IM    121
+#define MSIM_BM_ACTION_OR_IM_INSTANT    121
 #define MSIM_BM_MEDIA                   122
 #define MSIM_BM_PROFILE                 124
 #define MSIM_BM_UNOFFICIAL_CLIENT       200
@@ -140,6 +140,7 @@
 /* Recognized challenge length */
 #define MSIM_AUTH_CHALLENGE_LENGTH  0x40
 
+#ifdef SEND_OUR_IP_ADDRESSES
 /* TODO: obtain IPs of network interfaces from user's machine, instead of
  * hardcoding these values below (used in msim_compute_login_response).
  * This is not immediately
@@ -152,6 +153,7 @@
 
 #define MSIM_LOGIN_IP_LIST  "\x00\x00\x00\x00\x05\x7f\x00\x00\x01\x00\x00\x00\x00\x0a\x00\x00\x40\xc0\xa8\x58\x01\xc0\xa8\x3c\x01"
 #define MSIM_LOGIN_IP_LIST_LEN         25
+#endif /* SEND_OUR_IP_ADDRESSES */
 
 /* Indexes into status string (0|1|2|3|..., but 0 always empty) */
 #define MSIM_STATUS_ORDINAL_EMPTY       0
--- a/libpurple/protocols/myspace/user.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/myspace/user.c	Thu Jun 25 12:49:23 2009 +0000
@@ -58,6 +58,7 @@
 
 		user = g_new0(MsimUser, 1);
 		user->buddy = buddy;
+		user->id = purple_blist_node_get_int(&buddy->node, "UserID");
 		purple_buddy_set_protocol_data(buddy, user);
 	}
 
@@ -109,7 +110,6 @@
 {
 	PurplePresence *presence;
 	gchar *str;
-	guint uid;
 	guint cv;
 
 	/* Useful to identify the account the tooltip refers to.
@@ -118,8 +118,6 @@
 		purple_notify_user_info_add_pair(user_info, _("User"), user->username);
 	}
 
-	uid = purple_blist_node_get_int((PurpleBlistNode *)user->buddy, "UserID");
-
 	/* a/s/l...the vitals */
 	if (user->age) {
 		char age[16];
--- a/libpurple/protocols/myspace/zap.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/myspace/zap.c	Thu Jun 25 12:49:23 2009 +0000
@@ -109,7 +109,7 @@
 	/* Construct and send the actual zap command. */
 	zap_string = g_strdup_printf("!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", code);
 
-	if (!msim_send_bm(session, username, zap_string, MSIM_BM_INSTANT_ACTION_OR_IM)) {
+	if (!msim_send_bm(session, username, zap_string, MSIM_BM_ACTION_OR_IM_INSTANT)) {
 		purple_debug_info("msim_send_zap",
 				"msim_send_bm failed: zapping %s with %s\n",
 				username, zap_string);
--- a/libpurple/protocols/oscar/Makefile.am	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/oscar/Makefile.am	Thu Jun 25 12:49:23 2009 +0000
@@ -7,6 +7,7 @@
 
 OSCARSOURCES = \
 	bstream.c           \
+	clientlogin.c       \
 	family_admin.c      \
 	family_advert.c     \
 	family_alert.c      \
--- a/libpurple/protocols/oscar/Makefile.mingw	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/oscar/Makefile.mingw	Thu Jun 25 12:49:23 2009 +0000
@@ -42,6 +42,7 @@
 ##
 C_SRC = \
 	bstream.c		\
+	clientlogin.c		\
 	family_admin.c		\
 	family_advert.c		\
 	family_alert.c		\
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/oscar/clientlogin.c	Thu Jun 25 12:49:23 2009 +0000
@@ -0,0 +1,530 @@
+/*
+ * Purple's oscar protocol plugin
+ * This file is the legal property of its developers.
+ * Please see the AUTHORS file distributed alongside this file.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+*/
+
+/**
+ * This file implements AIM's clientLogin procedure for authenticating
+ * users.  This replaces the older MD5-based and XOR-based
+ * authentication methods that use SNAC family 0x0017.
+ *
+ * This doesn't use SNACs or FLAPs at all.  It makes http and https
+ * POSTs to AOL to validate the user based on the password they
+ * provided to us.  Upon successful authentication we request a
+ * connection to the BOS server by calling startOSCARsession.  The
+ * AOL server gives us the hostname and port number to use, as well
+ * as the cookie to use to authenticate to the BOS server.  And then
+ * everything else is the same as with BUCP.
+ *
+ * For details, see:
+ * http://dev.aol.com/aim/oscar/#AUTH
+ * http://dev.aol.com/authentication_for_clients
+ */
+
+#include "cipher.h"
+#include "core.h"
+
+#include "oscar.h"
+
+#define URL_CLIENT_LOGIN "https://api.screenname.aol.com/auth/clientLogin"
+#define URL_START_OSCAR_SESSION "http://api.oscar.aol.com/aim/startOSCARSession"
+
+/*
+ * Using clientLogin requires a developer ID.  This dev ID is owned by
+ * the AIM account "markdoliner"
+ */
+#define CLIENT_KEY "ma15d7JTxbmVG-RP"
+
+/**
+ * This is similar to purple_url_encode() except that it follows
+ * RFC3986 a little more closely by not encoding - . _ and ~
+ * It also uses capital letters as hex characters because capital
+ * letters are required by AOL.  The RFC says that capital letters
+ * are a SHOULD and that URLs that use capital letters are
+ * equivalent to URLs that use small letters.
+ *
+ * TODO: Check if purple_url_encode() can be replaced with this
+ *       version without breaking anything.
+ */
+static const char *oscar_auth_url_encode(const char *str)
+{
+	const char *iter;
+	static char buf[BUF_LEN];
+	char utf_char[6];
+	guint i, j = 0;
+
+	g_return_val_if_fail(str != NULL, NULL);
+	g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
+
+	iter = str;
+	for (; *iter && j < (BUF_LEN - 1) ; iter = g_utf8_next_char(iter)) {
+		gunichar c = g_utf8_get_char(iter);
+		/* If the character is an ASCII character and is alphanumeric
+		 * no need to escape */
+		if ((c < 128 && isalnum(c)) || c =='-' || c == '.' || c == '_' || c == '~') {
+			buf[j++] = c;
+		} else {
+			int bytes = g_unichar_to_utf8(c, utf_char);
+			for (i = 0; i < bytes; i++) {
+				if (j > (BUF_LEN - 4))
+					break;
+				sprintf(buf + j, "%%%02X", utf_char[i] & 0xff);
+				j += 3;
+			}
+		}
+	}
+
+	buf[j] = '\0';
+
+	return buf;
+}
+
+/**
+ * @return A null-terminated base64 encoded version of the HMAC
+ *         calculated using the given key and data.
+ */
+static gchar *hmac_sha256(const char *key, const char *message)
+{
+	PurpleCipherContext *context;
+	guchar digest[32];
+
+	context = purple_cipher_context_new_by_name("hmac", NULL);
+	purple_cipher_context_set_option(context, "hash", "sha256");
+	purple_cipher_context_set_key(context, (guchar *)key);
+	purple_cipher_context_append(context, (guchar *)message, strlen(message));
+	purple_cipher_context_digest(context, sizeof(digest), digest, NULL);
+	purple_cipher_context_destroy(context);
+
+	return purple_base64_encode(digest, sizeof(digest));
+}
+
+/**
+ * @return A base-64 encoded HMAC-SHA256 signature created using the
+ *         technique documented at
+ *         http://dev.aol.com/authentication_for_clients#signing
+ */
+static gchar *generate_signature(const char *method, const char *url, const char *parameters, const char *session_key)
+{
+	char *encoded_url, *signature_base_string, *signature;
+	const char *encoded_parameters;
+
+	encoded_url = g_strdup(oscar_auth_url_encode(url));
+	encoded_parameters = oscar_auth_url_encode(parameters);
+	signature_base_string = g_strdup_printf("%s&%s&%s",
+			method, encoded_url, encoded_parameters);
+	g_free(encoded_url);
+
+	signature = hmac_sha256(session_key, signature_base_string);
+	g_free(signature_base_string);
+
+	return signature;
+}
+
+static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **host, unsigned short *port, char **cookie)
+{
+	xmlnode *response_node, *tmp_node, *data_node;
+	xmlnode *host_node, *port_node, *cookie_node;
+	char *tmp;
+
+	/* Parse the response as XML */
+	response_node = xmlnode_from_str(response, response_len);
+	if (response_node == NULL)
+	{
+		purple_debug_error("oscar", "startOSCARSession could not parse "
+				"response as XML: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_START_OSCAR_SESSION));
+		return FALSE;
+	}
+
+	/* Grab the necessary XML nodes */
+	tmp_node = xmlnode_get_child(response_node, "statusCode");
+	data_node = xmlnode_get_child(response_node, "data");
+	if (data_node != NULL) {
+		host_node = xmlnode_get_child(data_node, "host");
+		port_node = xmlnode_get_child(data_node, "port");
+		cookie_node = xmlnode_get_child(data_node, "cookie");
+	}
+
+	/* Make sure we have a status code */
+	if (tmp_node == NULL || (tmp = xmlnode_get_data_unescaped(tmp_node)) == NULL) {
+		purple_debug_error("oscar", "startOSCARSession response was "
+				"missing statusCode: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_START_OSCAR_SESSION));
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+
+	/* Make sure the status code was 200 */
+	if (strcmp(tmp, "200") != 0)
+	{
+		purple_debug_error("oscar", "startOSCARSession response statusCode "
+				"was %s: %s\n", tmp, response);
+
+		if (strcmp(tmp, "401") == 0)
+			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."));
+		else
+			purple_connection_error_reason(gc,
+					PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+					_("Received unexpected response from " URL_START_OSCAR_SESSION));
+
+		g_free(tmp);
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+	g_free(tmp);
+
+	/* Make sure we have everything else */
+	if (data_node == NULL || host_node == NULL ||
+		port_node == NULL || cookie_node == NULL)
+	{
+		purple_debug_error("oscar", "startOSCARSession response was missing "
+				"something: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_START_OSCAR_SESSION));
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+
+	/* Extract data from the XML */
+	*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')
+	{
+		purple_debug_error("oscar", "startOSCARSession response was missing "
+				"something: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_START_OSCAR_SESSION));
+		g_free(*host);
+		g_free(tmp);
+		g_free(*cookie);
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+
+	*port = atoi(tmp);
+	g_free(tmp);
+
+	return TRUE;
+}
+
+static void start_oscar_session_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
+{
+	OscarData *od;
+	PurpleConnection *gc;
+	char *host, *cookie;
+	unsigned short port;
+	guint8 *cookiedata;
+	gsize cookiedata_len;
+
+	od = user_data;
+	gc = od->gc;
+
+	od->url_data = NULL;
+
+	if (error_message != NULL || len == 0) {
+		gchar *tmp;
+		tmp = g_strdup_printf(_("Error requesting " URL_START_OSCAR_SESSION
+				": %s"), error_message);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
+		g_free(tmp);
+		return;
+	}
+
+	if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie))
+		return;
+
+	cookiedata = purple_base64_decode(cookie, &cookiedata_len);
+	oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len);
+	g_free(cookiedata);
+
+	g_free(host);
+	g_free(cookie);
+}
+
+static void send_start_oscar_session(OscarData *od, const char *token, const char *session_key, time_t hosttime)
+{
+	char *query_string, *signature, *url;
+
+	/* Construct the GET parameters */
+	query_string = g_strdup_printf("a=%s"
+			"&f=xml"
+			"&k=" CLIENT_KEY
+			"&ts=%zu"
+			"&useTLS=0",
+			oscar_auth_url_encode(token), hosttime);
+	signature = generate_signature("GET", URL_START_OSCAR_SESSION,
+			query_string, session_key);
+	url = g_strdup_printf(URL_START_OSCAR_SESSION "?%s&sig_sha256=%s",
+			query_string, signature);
+	g_free(query_string);
+	g_free(signature);
+
+	/* Make the request */
+	od->url_data = purple_util_fetch_url(url, TRUE, NULL, FALSE,
+			start_oscar_session_cb, od);
+	g_free(url);
+}
+
+/**
+ * This function parses the given response from a clientLogin request
+ * and extracts the useful information.
+ *
+ * @param gc           The PurpleConnection.  If the response data does
+ *                     not indicate then purple_connection_error_reason()
+ *                     will be called to close this connection.
+ * @param response     The response data from the clientLogin request.
+ * @param response_len The length of the above response, or -1 if
+ *                     @response is NUL terminated.
+ * @param token        If parsing was successful then this will be set to
+ *                     a newly allocated string containing the token.  The
+ *                     caller should g_free this string when it is finished
+ *                     with it.  On failure this value will be untouched.
+ * @param secret       If parsing was successful then this will be set to
+ *                     a newly allocated string containing the secret.  The
+ *                     caller should g_free this string when it is finished
+ *                     with it.  On failure this value will be untouched.
+ * @param hosttime     If parsing was successful then this will be set to
+ *                     the time on the OpenAuth Server in seconds since the
+ *                     Unix epoch.  On failure this value will be untouched.
+ *
+ * @return TRUE if the request was successful and we were able to
+ *         extract all info we need.  Otherwise FALSE.
+ */
+static gboolean parse_client_login_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **token, char **secret, time_t *hosttime)
+{
+	xmlnode *response_node, *tmp_node, *data_node;
+	xmlnode *secret_node, *hosttime_node, *token_node, *tokena_node;
+	char *tmp;
+
+	/* Parse the response as XML */
+	response_node = xmlnode_from_str(response, response_len);
+	if (response_node == NULL)
+	{
+		purple_debug_error("oscar", "clientLogin could not parse "
+				"response as XML: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_CLIENT_LOGIN));
+		return FALSE;
+	}
+
+	/* Grab the necessary XML nodes */
+	tmp_node = xmlnode_get_child(response_node, "statusCode");
+	data_node = xmlnode_get_child(response_node, "data");
+	if (data_node != NULL) {
+		secret_node = xmlnode_get_child(data_node, "sessionSecret");
+		hosttime_node = xmlnode_get_child(data_node, "hostTime");
+		token_node = xmlnode_get_child(data_node, "token");
+		if (token_node != NULL)
+			tokena_node = xmlnode_get_child(token_node, "a");
+	}
+
+	/* Make sure we have a status code */
+	if (tmp_node == NULL || (tmp = xmlnode_get_data_unescaped(tmp_node)) == NULL) {
+		purple_debug_error("oscar", "clientLogin response was "
+				"missing statusCode: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_CLIENT_LOGIN));
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+
+	/* Make sure the status code was 200 */
+	if (strcmp(tmp, "200") != 0)
+	{
+		int status_code, status_detail_code = 0;
+
+		status_code = atoi(tmp);
+		g_free(tmp);
+		tmp_node = xmlnode_get_child(response_node, "statusDetailCode");
+		if (tmp_node != NULL && (tmp = xmlnode_get_data_unescaped(tmp_node)) != NULL) {
+			status_detail_code = atoi(tmp);
+			g_free(tmp);
+		}
+
+		purple_debug_error("oscar", "clientLogin response statusCode "
+				"was %d (%d): %s\n", status_code, status_detail_code, response);
+
+		if (status_code == 330 && status_detail_code == 3011) {
+			purple_connection_error_reason(gc,
+					PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
+					_("Incorrect password."));
+		} else if (status_code == 401 && status_detail_code == 3019) {
+			purple_connection_error_reason(gc,
+					PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+					_("AOL does not allow your screen name to authenticate via this site."));
+		} else
+			purple_connection_error_reason(gc,
+					PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+					_("Received unexpected response from " URL_CLIENT_LOGIN));
+
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+	g_free(tmp);
+
+	/* Make sure we have everything else */
+	if (data_node == NULL || secret_node == NULL ||
+		token_node == NULL || tokena_node == NULL)
+	{
+		purple_debug_error("oscar", "clientLogin response was missing "
+				"something: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_CLIENT_LOGIN));
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+
+	/* Extract data from the XML */
+	*token = xmlnode_get_data_unescaped(tokena_node);
+	*secret = xmlnode_get_data_unescaped(secret_node);
+	tmp = xmlnode_get_data_unescaped(hosttime_node);
+	if (*token == NULL || **token == '\0' || *secret == NULL || **secret == '\0' || tmp == NULL || *tmp == '\0')
+	{
+		purple_debug_error("oscar", "clientLogin response was missing "
+				"something: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_CLIENT_LOGIN));
+		g_free(*token);
+		g_free(*secret);
+		g_free(tmp);
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+
+	*hosttime = strtol(tmp, NULL, 10);
+	g_free(tmp);
+
+	xmlnode_free(response_node);
+
+	return TRUE;
+}
+
+static void client_login_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
+{
+	OscarData *od;
+	PurpleConnection *gc;
+	char *token, *secret, *session_key;
+	time_t hosttime;
+	int password_len;
+	char *password;
+
+	od = user_data;
+	gc = od->gc;
+
+	od->url_data = NULL;
+
+	if (error_message != NULL || len == 0) {
+		gchar *tmp;
+		tmp = g_strdup_printf(_("Error requesting " URL_CLIENT_LOGIN
+				": %s"), error_message);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
+		g_free(tmp);
+		return;
+	}
+
+	if (!parse_client_login_response(gc, url_text, len, &token, &secret, &hosttime))
+		return;
+
+	password_len = strlen(purple_connection_get_password(gc));
+	password = g_strdup_printf("%.*s",
+			od->icq ? MIN(password_len, MAXICQPASSLEN) : password_len,
+			purple_connection_get_password(gc));
+	session_key = hmac_sha256(password, secret);
+	g_free(password);
+	g_free(secret);
+
+	send_start_oscar_session(od, token, session_key, hosttime);
+
+	g_free(token);
+	g_free(session_key);
+}
+
+/**
+ * This function sends a request to
+ * https://api.screenname.aol.com/auth/clientLogin with the user's
+ * username and password and receives the user's session key, which is
+ * used to request a connection to the BOSS server.
+ */
+void send_client_login(OscarData *od, const char *username)
+{
+	PurpleConnection *gc;
+	GString *request, *body;
+	const char *tmp;
+	char *password;
+	int password_len;
+
+	gc = od->gc;
+
+	/*
+	 * We truncate ICQ passwords to 8 characters.  There is probably a
+	 * limit for AIM passwords, too, but we really only need to do
+	 * this for ICQ because older ICQ clients let you enter a password
+	 * as long as you wanted and then they truncated it silently.
+	 *
+	 * And we can truncate based on the number of bytes and not the
+	 * number of characters because passwords for AIM and ICQ are
+	 * supposed to be plain ASCII (I don't know if this has always been
+	 * the case, though).
+	 */
+	tmp = purple_connection_get_password(gc);
+	password_len = strlen(tmp);
+	password = g_strndup(tmp, od->icq ? MIN(password_len, MAXICQPASSLEN) : password_len);
+
+	/* Construct the body of the HTTP POST request */
+	body = g_string_new("");
+	g_string_append_printf(body, "devId=" CLIENT_KEY);
+	g_string_append_printf(body, "&f=xml");
+	g_string_append_printf(body, "&pwd=%s", oscar_auth_url_encode(password));
+	g_string_append_printf(body, "&s=%s", oscar_auth_url_encode(username));
+	g_free(password);
+
+	/* Construct an HTTP POST request */
+	request = g_string_new("POST /auth/clientLogin HTTP/1.0\r\n"
+			"Connection: close\r\n"
+			"Accept: */*\r\n");
+
+	/* Tack on the body */
+	g_string_append_printf(request, "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n");
+	g_string_append_printf(request, "Content-Length: %lu\r\n\r\n", body->len);
+	g_string_append_len(request, body->str, body->len);
+	g_string_free(body, TRUE);
+
+	/* Send the POST request  */
+	od->url_data = purple_util_fetch_url_request(URL_CLIENT_LOGIN,
+			TRUE, NULL, FALSE, request->str, FALSE,
+			client_login_cb, od);
+	g_string_free(request, TRUE);
+}
--- a/libpurple/protocols/oscar/family_auth.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/oscar/family_auth.c	Thu Jun 25 12:49:23 2009 +0000
@@ -26,11 +26,11 @@
  *
  */
 
-#include "oscar.h"
+#include <ctype.h>
 
 #include "cipher.h"
 
-#include <ctype.h>
+#include "oscar.h"
 
 /* #define USE_XOR_FOR_ICQ */
 
--- a/libpurple/protocols/oscar/flap_connection.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Thu Jun 25 12:49:23 2009 +0000
@@ -46,7 +46,7 @@
 	FlapFrame *frame;
 
 	frame = flap_frame_new(od, 0x01, 4);
-	byte_stream_put32(&frame->data, 0x00000001);
+	byte_stream_put32(&frame->data, 0x00000001); /* FLAP Version */
 	flap_connection_send(conn, frame);
 }
 
@@ -64,7 +64,7 @@
 	GSList *tlvlist = NULL;
 
 	frame = flap_frame_new(od, 0x01, 4 + 2 + 2 + length);
-	byte_stream_put32(&frame->data, 0x00000001);
+	byte_stream_put32(&frame->data, 0x00000001); /* FLAP Version */
 	aim_tlvlist_add_raw(&tlvlist, 0x0006, length, chipsahoy);
 	aim_tlvlist_write(&frame->data, &tlvlist);
 	aim_tlvlist_free(tlvlist);
@@ -72,6 +72,32 @@
 	flap_connection_send(conn, frame);
 }
 
+void
+flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci)
+{
+	FlapFrame *frame;
+	GSList *tlvlist = NULL;
+
+	frame = flap_frame_new(od, 0x01, 1152 + length);
+
+	byte_stream_put32(&frame->data, 0x00000001); /* FLAP Version */
+	aim_tlvlist_add_raw(&tlvlist, 0x0006, length, chipsahoy);
+
+	if (ci->clientstring)
+		aim_tlvlist_add_str(&tlvlist, 0x0003, ci->clientstring);
+	aim_tlvlist_add_16(&tlvlist, 0x0017, (guint16)ci->major);
+	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_write(&frame->data, &tlvlist);
+
+	aim_tlvlist_free(tlvlist);
+
+	flap_connection_send(conn, frame);
+}
+
 static struct rateclass *
 flap_connection_get_rateclass(FlapConnection *conn, guint16 family, guint16 subtype)
 {
@@ -355,23 +381,9 @@
 		}
 	}
 
-	if (conn->fd >= 0)
-	{
-		if (conn->type == SNAC_FAMILY_LOCATE)
-			flap_connection_send_close(od, conn);
-
-		close(conn->fd);
-		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->fd >= 0 || conn->gsc != NULL)
+			&& conn->type == SNAC_FAMILY_LOCATE)
+		flap_connection_send_close(od, conn);
 
 	if (conn->watcher_incoming != 0)
 	{
@@ -385,6 +397,18 @@
 		conn->watcher_outgoing = 0;
 	}
 
+	if (conn->fd >= 0)
+	{
+		close(conn->fd);
+		conn->fd = -1;
+	}
+
+	if (conn->gsc != NULL)
+	{
+		purple_ssl_close(conn->gsc);
+		conn->gsc = NULL;
+	}
+
 	g_free(conn->buffer_incoming.data.data);
 	conn->buffer_incoming.data.data = NULL;
 
--- a/libpurple/protocols/oscar/oscar.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Thu Jun 25 12:49:23 2009 +0000
@@ -145,9 +145,12 @@
 static const int msgerrreasonlen = G_N_ELEMENTS(msgerrreason);
 
 /* All the libfaim->purple callback functions */
+
+/* Only used when connecting with the old-style BUCP login */
 static int purple_parse_auth_resp  (OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_parse_login      (OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_parse_auth_securid_request(OscarData *, FlapConnection *, FlapFrame *, ...);
+
 static int purple_handle_redirect  (OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_info_change      (OscarData *, FlapConnection *, FlapFrame *, ...);
 static int purple_account_confirm  (OscarData *, FlapConnection *, FlapFrame *, ...);
@@ -204,7 +207,6 @@
 void oscar_set_info(PurpleConnection *gc, const char *info);
 static void oscar_set_info_and_status(PurpleAccount *account, gboolean setinfo, const char *rawinfo, gboolean setstatus, PurpleStatus *status);
 static void oscar_set_extendedstatus(PurpleConnection *gc);
-static void oscar_format_username(PurpleConnection *gc, const char *nick);
 static gboolean purple_ssi_rerequestdata(gpointer data);
 
 static void oscar_free_name_data(struct name_data *data) {
@@ -1135,6 +1137,7 @@
 
 	if (conn->type == SNAC_FAMILY_AUTH)
 	{
+		/* This only happens when connecting with the old-style BUCP login */
 		gchar *msg;
 		msg = g_strdup_printf(_("Could not connect to authentication server:\n%s"),
 				error_message);
@@ -1180,14 +1183,26 @@
 		flap_connection_send_version(od, conn);
 	else
 	{
-		flap_connection_send_version_with_cookie(od, conn,
-				conn->cookielen, conn->cookie);
+		if (purple_account_get_bool(account, "use_clientlogin", OSCAR_DEFAULT_USE_CLIENTLOGIN))
+		{
+			ClientInfo aiminfo = CLIENTINFO_PURPLE_AIM;
+			ClientInfo icqinfo = CLIENTINFO_PURPLE_ICQ;
+			flap_connection_send_version_with_cookie_and_clientinfo(od,
+					conn, conn->cookielen, conn->cookie,
+					od->icq ? &icqinfo : &aiminfo);
+		} else {
+			flap_connection_send_version_with_cookie(od, conn,
+					conn->cookielen, conn->cookie);
+		}
+
+
 		g_free(conn->cookie);
 		conn->cookie = NULL;
 	}
 
 	if (conn->type == SNAC_FAMILY_AUTH)
 	{
+		/* This only happens when connecting with the old-style BUCP login */
 		aim_request_login(od, conn, purple_account_get_username(account));
 		purple_debug_info("oscar", "Username sent, waiting for response\n");
 		purple_connection_update_progress(gc, _("Username sent"), 1, OSCAR_CONNECT_STEPS);
@@ -1458,7 +1473,6 @@
 {
 	PurpleConnection *gc;
 	OscarData *od;
-	FlapConnection *newconn;
 
 	gc = purple_account_get_connection(account);
 	od = oscar_data_new();
@@ -1473,9 +1487,12 @@
 	oscar_data_addhandler(od, SNAC_FAMILY_ADMIN, 0x0007, purple_account_confirm, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_ALERT, 0x0001, purple_parse_genericerr, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_ALERT, SNAC_SUBTYPE_ALERT_MAILSTATUS, purple_email_parseupdate, 0);
+
+	/* These are only needed when connecting with the old-style BUCP login */
 	oscar_data_addhandler(od, SNAC_FAMILY_AUTH, 0x0003, purple_parse_auth_resp, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_AUTH, 0x0007, purple_parse_login, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_AUTH, SNAC_SUBTYPE_AUTH_SECURID_REQUEST, purple_parse_auth_securid_request, 0);
+
 	oscar_data_addhandler(od, SNAC_FAMILY_BART, SNAC_SUBTYPE_BART_RESPONSE, purple_icon_parseicon, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_BOS, 0x0001, purple_parse_genericerr, 0);
 	oscar_data_addhandler(od, SNAC_FAMILY_BOS, 0x0003, purple_bosrights, 0);
@@ -1551,10 +1568,34 @@
 	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);
-	if (od->use_ssl) {
-		if (purple_ssl_is_supported()) {
-			const char *server = purple_account_get_string(account, "server", OSCAR_DEFAULT_SSL_LOGIN_SERVER);
+	/*
+	 * On 2008-03-05 AOL released some documentation on the OSCAR protocol
+	 * which includes a new login method called clientLogin.  It is similar
+	 * (though not the same?) as what the AIM 6.0 series uses to
+	 * authenticate.
+	 *
+	 * AIM 5.9 and lower use an MD5-based login procedure called "BUCP".
+	 * Note that some people were unable to log in to ICQ using the MD5
+	 * method, and so ICQ, when not using clientLogin, is still using a
+	 * very insecure XOR-based login scheme.
+	 */
+	if (purple_account_get_bool(account, "use_clientlogin", OSCAR_DEFAULT_USE_CLIENTLOGIN)) {
+		send_client_login(od, purple_account_get_username(account));
+	} else {
+		FlapConnection *newconn;
+		const char *server;
+
+		newconn = flap_connection_new(od, SNAC_FAMILY_AUTH);
+
+		if (od->use_ssl) {
+			if (!purple_ssl_is_supported()) {
+				purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
+						_("SSL support unavailable"));
+				return;
+			}
+
+			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
@@ -1572,32 +1613,29 @@
 					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"));
+			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);
 		}
-	} 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;
+
+		if (newconn->gsc == NULL && newconn->connect_data == NULL) {
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+					_("Couldn't connect to host"));
+			return;
 		}
-
-		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;
 	}
 
 	purple_connection_update_progress(gc, _("Connecting"), 0, OSCAR_CONNECT_STEPS);
@@ -1632,165 +1670,6 @@
 	purple_debug_info("oscar", "Signed off.\n");
 }
 
-static int
-purple_parse_auth_resp(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
-{
-	PurpleConnection *gc = od->gc;
-	PurpleAccount *account = purple_connection_get_account(gc);
-	char *host; int port;
-	int i;
-	FlapConnection *newconn;
-	va_list ap;
-	struct aim_authresp_info *info;
-
-	port = purple_account_get_int(account, "port", OSCAR_DEFAULT_LOGIN_PORT);
-
-	va_start(ap, fr);
-	info = va_arg(ap, struct aim_authresp_info *);
-	va_end(ap);
-
-	purple_debug_info("oscar",
-			   "inside auth_resp (Username: %s)\n", info->bn);
-
-	if (info->errorcode || !info->bosip || !info->cookielen || !info->cookie) {
-		char buf[256];
-		switch (info->errorcode) {
-		case 0x01:
-			/* Unregistered username */
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_INVALID_USERNAME, _("Invalid username."));
-			break;
-		case 0x05:
-			/* Incorrect password */
-			if (!purple_account_get_remember_password(account))
-				purple_account_set_password(account, NULL);
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Incorrect password."));
-			break;
-		case 0x11:
-			/* Suspended account */
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Your account is currently suspended."));
-			break;
-		case 0x02:
-		case 0x14:
-			/* service temporarily unavailable */
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("The AOL Instant Messenger service is temporarily unavailable."));
-			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."));
-			break;
-		case 0x1c:
-		{
-			/* client too old */
-			GHashTable *ui_info = purple_core_get_ui_info();
-			g_snprintf(buf, sizeof(buf), _("The client version you are using is too old. Please upgrade at %s"),
-					   ((ui_info && g_hash_table_lookup(ui_info, "website")) ? (char *)g_hash_table_lookup(ui_info, "website") : PURPLE_WEBSITE));
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, buf);
-			break;
-		}
-		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 ten minutes 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, _("Authentication failed"));
-			break;
-		}
-		purple_debug_info("oscar", "Login Error Code 0x%04hx\n", info->errorcode);
-		purple_debug_info("oscar", "Error URL: %s\n", info->errorurl ? info->errorurl : "");
-		return 1;
-	}
-
-	purple_debug_misc("oscar", "Reg status: %hu\n"
-							   "Email: %s\n"
-							   "BOSIP: %s\n",
-							   info->regstatus,
-							   info->email ? info->email : "null",
-							   info->bosip ? info->bosip : "null");
-	purple_debug_info("oscar", "Closing auth connection...\n");
-	flap_connection_schedule_destroy(conn, OSCAR_DISCONNECT_DONE, NULL);
-
-	for (i = 0; i < strlen(info->bosip); i++) {
-		if (info->bosip[i] == ':') {
-			port = atoi(&(info->bosip[i+1]));
-			break;
-		}
-	}
-	host = g_strndup(info->bosip, i);
-	newconn = flap_connection_new(od, SNAC_FAMILY_LOCATE);
-	newconn->cookielen = info->cookielen;
-	newconn->cookie = g_memdup(info->cookie, info->cookielen);
-
-	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)
-	{
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Could Not Connect"));
-		return 0;
-	}
-
-	purple_connection_update_progress(gc, _("Received authorization"), 3, OSCAR_CONNECT_STEPS);
-	ck[3] = 0x64;
-
-	return 1;
-}
-
-static void
-purple_parse_auth_securid_request_yes_cb(gpointer user_data, const char *msg)
-{
-	PurpleConnection *gc = user_data;
-	OscarData *od = purple_connection_get_protocol_data(gc);
-
-	aim_auth_securid_send(od, msg);
-}
-
-static void
-purple_parse_auth_securid_request_no_cb(gpointer user_data, const char *value)
-{
-	PurpleConnection *gc = user_data;
-
-	/* Disconnect */
-	purple_connection_error_reason(gc,
-		PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
-		_("The SecurID key entered is invalid."));
-}
-
-static int
-purple_parse_auth_securid_request(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
-{
-	PurpleConnection *gc = od->gc;
-	PurpleAccount *account = purple_connection_get_account(gc);
-	gchar *primary;
-
-	purple_debug_info("oscar", "Got SecurID request\n");
-
-	primary = g_strdup_printf("Enter the SecurID key for %s.", purple_account_get_username(account));
-	purple_request_input(gc, NULL, _("Enter SecurID"), primary,
-					   _("Enter the 6 digit number from the digital display."),
-					   FALSE, FALSE, NULL,
-					   _("_OK"), G_CALLBACK(purple_parse_auth_securid_request_yes_cb),
-					   _("_Cancel"), G_CALLBACK(purple_parse_auth_securid_request_no_cb),
-					   account, NULL, NULL,
-					   gc);
-	g_free(primary);
-
-	return 1;
-}
-
 /* XXX - Should use purple_util_fetch_url for the below stuff */
 struct pieceofcrap {
 	PurpleConnection *gc;
@@ -1900,7 +1779,8 @@
 /* size of icbmui.ocm, the largest module in AIM 3.5 */
 #define AIM_MAX_FILE_SIZE 98304
 
-int purple_memrequest(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) {
+static int purple_memrequest(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
+{
 	va_list ap;
 	struct pieceofcrap *pos;
 	guint32 offset, len;
@@ -1977,6 +1857,204 @@
 	return 1;
 }
 
+int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen)
+{
+	FlapConnection *conn;
+
+	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)
+	{
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Could Not Connect"));
+		return 0;
+	}
+
+	od->default_port = port;
+
+	purple_connection_update_progress(gc, _("Received authorization"), 3, OSCAR_CONNECT_STEPS);
+	ck[3] = 0x64;
+
+	return 1;
+}
+
+/**
+ * Only used when connecting with the old-style BUCP login.
+ */
+static int
+purple_parse_auth_resp(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
+{
+	PurpleConnection *gc = od->gc;
+	PurpleAccount *account = purple_connection_get_account(gc);
+	char *host; int port;
+	int i;
+	FlapConnection *newconn;
+	va_list ap;
+	struct aim_authresp_info *info;
+
+	port = purple_account_get_int(account, "port", od->default_port);
+
+	va_start(ap, fr);
+	info = va_arg(ap, struct aim_authresp_info *);
+	va_end(ap);
+
+	purple_debug_info("oscar",
+			   "inside auth_resp (Username: %s)\n", info->bn);
+
+	if (info->errorcode || !info->bosip || !info->cookielen || !info->cookie) {
+		char buf[256];
+		switch (info->errorcode) {
+		case 0x01:
+			/* Unregistered username */
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_INVALID_USERNAME, _("Invalid username."));
+			break;
+		case 0x05:
+			/* Incorrect password */
+			if (!purple_account_get_remember_password(account))
+				purple_account_set_password(account, NULL);
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Incorrect password."));
+			break;
+		case 0x11:
+			/* Suspended account */
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Your account is currently suspended."));
+			break;
+		case 0x02:
+		case 0x14:
+			/* service temporarily unavailable */
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("The AOL Instant Messenger service is temporarily unavailable."));
+			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."));
+			break;
+		case 0x1c:
+		{
+			/* client too old */
+			GHashTable *ui_info = purple_core_get_ui_info();
+			g_snprintf(buf, sizeof(buf), _("The client version you are using is too old. Please upgrade at %s"),
+					   ((ui_info && g_hash_table_lookup(ui_info, "website")) ? (char *)g_hash_table_lookup(ui_info, "website") : PURPLE_WEBSITE));
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, buf);
+			break;
+		}
+		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."));
+			break;
+		default:
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Authentication failed"));
+			break;
+		}
+		purple_debug_info("oscar", "Login Error Code 0x%04hx\n", info->errorcode);
+		purple_debug_info("oscar", "Error URL: %s\n", info->errorurl ? info->errorurl : "");
+		return 1;
+	}
+
+	purple_debug_misc("oscar", "Reg status: %hu\n"
+							   "Email: %s\n"
+							   "BOSIP: %s\n",
+							   info->regstatus,
+							   info->email ? info->email : "null",
+							   info->bosip ? info->bosip : "null");
+	purple_debug_info("oscar", "Closing auth connection...\n");
+	flap_connection_schedule_destroy(conn, OSCAR_DISCONNECT_DONE, NULL);
+
+	for (i = 0; i < strlen(info->bosip); i++) {
+		if (info->bosip[i] == ':') {
+			port = atoi(&(info->bosip[i+1]));
+			break;
+		}
+	}
+	host = g_strndup(info->bosip, i);
+	newconn = flap_connection_new(od, SNAC_FAMILY_LOCATE);
+	newconn->cookielen = info->cookielen;
+	newconn->cookie = g_memdup(info->cookie, info->cookielen);
+
+	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)
+	{
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Could Not Connect"));
+		return 0;
+	}
+
+	purple_connection_update_progress(gc, _("Received authorization"), 3, OSCAR_CONNECT_STEPS);
+	ck[3] = 0x64;
+
+	return 1;
+}
+
+/**
+ * Only used when connecting with the old-style BUCP login.
+ */
+static void
+purple_parse_auth_securid_request_yes_cb(gpointer user_data, const char *msg)
+{
+	PurpleConnection *gc = user_data;
+	OscarData *od = purple_connection_get_protocol_data(gc);
+
+	aim_auth_securid_send(od, msg);
+}
+
+/**
+ * Only used when connecting with the old-style BUCP login.
+ */
+static void
+purple_parse_auth_securid_request_no_cb(gpointer user_data, const char *value)
+{
+	PurpleConnection *gc = user_data;
+
+	/* Disconnect */
+	purple_connection_error_reason(gc,
+		PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
+		_("The SecurID key entered is invalid."));
+}
+
+/**
+ * Only used when connecting with the old-style BUCP login.
+ */
+static int
+purple_parse_auth_securid_request(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
+{
+	PurpleConnection *gc = od->gc;
+	PurpleAccount *account = purple_connection_get_account(gc);
+	gchar *primary;
+
+	purple_debug_info("oscar", "Got SecurID request\n");
+
+	primary = g_strdup_printf("Enter the SecurID key for %s.", purple_account_get_username(account));
+	purple_request_input(gc, NULL, _("Enter SecurID"), primary,
+					   _("Enter the 6 digit number from the digital display."),
+					   FALSE, FALSE, NULL,
+					   _("_OK"), G_CALLBACK(purple_parse_auth_securid_request_yes_cb),
+					   _("_Cancel"), G_CALLBACK(purple_parse_auth_securid_request_no_cb),
+					   account, NULL, NULL,
+					   gc);
+	g_free(primary);
+
+	return 1;
+}
+
+/**
+ * Only used when connecting with the old-style BUCP login.
+ */
 static int
 purple_parse_login(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
 {
@@ -2022,7 +2100,7 @@
 	redir = va_arg(ap, struct aim_redirect_data *);
 	va_end(ap);
 
-	port = purple_account_get_int(account, "port", OSCAR_DEFAULT_LOGIN_PORT);
+	port = od->default_port;
 	separator = strchr(redir->ip, ':');
 	if (separator != NULL)
 	{
@@ -3945,20 +4023,9 @@
 			purple_account_get_bool(account, "web_aware", OSCAR_DEFAULT_WEB_AWARE));
 	}
 
+	aim_srv_requestnew(od, SNAC_FAMILY_ALERT);
 	aim_srv_requestnew(od, SNAC_FAMILY_CHATNAV);
 
-	/*
-	 * The "if" statement here is a pathetic attempt to not attempt to
-	 * connect to the alerts servce (aka email notification) if this
-	 * username does not support it.  I think mail notification
-	 * works for @mac.com accounts but does not work for the newer
-	 * @anythingelse.com accounts.  If that's true then this change
-	 * breaks mail notification for @mac.com accounts, but it gets rid
-	 * of an annoying error at signon for @anythingelse.com accounts.
-	 */
-	if (od->authinfo->email != NULL && strchr(username, '@') == NULL)
-		aim_srv_requestnew(od, SNAC_FAMILY_ALERT);
-
 	return 1;
 }
 
@@ -4456,7 +4523,8 @@
 	}
 	g_string_free(data, TRUE);
 
-	peer_odc_send_im(conn, msg->str, msg->len, charset, (imflags & PURPLE_MESSAGE_AUTO_RESP));
+	peer_odc_send_im(conn, msg->str, msg->len, charset,
+			imflags & PURPLE_MESSAGE_AUTO_RESP);
 	g_string_free(msg, TRUE);
 }
 
@@ -6405,6 +6473,10 @@
 
 	if (od->ssi.received_data && purple_buddy_get_group(buddy) != NULL)
 	{
+		/*
+		 * We only do this if the user is in our buddy list and we're
+		 * waiting for authorization.
+		 */
 		char *gname;
 		gname = aim_ssi_itemlist_findparentname(od->ssi.local, bname);
 		if (gname && aim_ssi_waitingforauth(od->ssi.local, gname, bname))
@@ -6484,7 +6556,7 @@
 						gc);
 }
 
-static void oscar_format_username(PurpleConnection *gc, const char *nick) {
+void oscar_format_username(PurpleConnection *gc, const char *nick) {
 	OscarData *od = purple_connection_get_protocol_data(gc);
 	if (!oscar_util_name_compare(purple_account_get_username(purple_connection_get_account(gc)), nick)) {
 		if (!flap_connection_getbytype(od, SNAC_FAMILY_ADMIN)) {
@@ -6645,6 +6717,9 @@
 	purple_account_request_change_password(purple_connection_get_account(gc));
 }
 
+/**
+ * Only used when connecting with the old-style BUCP login.
+ */
 static void oscar_show_chpassurl(PurplePluginAction *action)
 {
 	PurpleConnection *gc = (PurpleConnection *) action->context;
@@ -6785,12 +6860,16 @@
 			oscar_change_pass);
 	menu = g_list_prepend(menu, act);
 
-	if (od->authinfo->chpassurl != NULL)
+	if (od->authinfo != NULL && od->authinfo->chpassurl != NULL)
 	{
+		/* This only happens when connecting with the old-style BUCP login */
 		act = purple_plugin_action_new(_("Change Password (web)"),
 				oscar_show_chpassurl);
 		menu = g_list_prepend(menu, act);
-
+	}
+
+	if (!od->icq)
+	{
 		act = purple_plugin_action_new(_("Configure IM Forwarding (web)"),
 				oscar_show_imforwardingurl);
 		menu = g_list_prepend(menu, act);
@@ -7027,6 +7106,10 @@
 			OSCAR_DEFAULT_USE_SSL);
 	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
 
+	option = purple_account_option_bool_new(_("Use clientLogin"), "use_clientlogin",
+			OSCAR_DEFAULT_USE_CLIENTLOGIN);
+	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	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Thu Jun 25 12:49:23 2009 +0000
@@ -469,6 +469,9 @@
  */
 struct _OscarData
 {
+	/** Only used when connecting with clientLogin */
+	PurpleUtilFetchUrlData *url_data;
+
 	gboolean iconconnecting;
 	gboolean set_icon;
 
@@ -522,6 +525,8 @@
 
 	IcbmCookie *msgcookies;
 	struct aim_icq_info *icq_info;
+
+	/** Only used when connecting with the old-style BUCP login. */
 	struct aim_authresp_info *authinfo;
 	struct aim_emailinfo *emailinfo;
 
@@ -547,6 +552,7 @@
 
 	/** A linked list containing FlapConnections. */
 	GSList *oscar_connections;
+	guint16 default_port;
 
 	/** A linked list containing PeerConnections. */
 	GSList *peer_connections;
@@ -568,10 +574,9 @@
 #define AIM_ICQ_STATE_DIRECTREQUIREAUTH 0x10000000
 #define AIM_ICQ_STATE_DIRECTCONTACTLIST 0x20000000
 
-typedef int (*aim_rxcallback_t)(OscarData *od, FlapConnection *conn, FlapFrame *frame, ...);
-
-
-/* family_auth.c */
+/**
+ * Only used when connecting with the old-style BUCP login.
+ */
 struct aim_clientrelease
 {
 	char *name;
@@ -580,6 +585,9 @@
 	char *info;
 };
 
+/**
+ * Only used when connecting with the old-style BUCP login.
+ */
 struct aim_authresp_info
 {
 	char *bn;
@@ -611,12 +619,29 @@
 	} chat;
 };
 
+int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen);
+
+/* family_auth.c */
+
+/**
+ * Only used when connecting with the old-style BUCP login.
+ */
 int aim_request_login(OscarData *od, FlapConnection *conn, const char *bn);
+
+/**
+ * Only used when connecting with the old-style BUCP login.
+ */
 int aim_send_login(OscarData *od, FlapConnection *conn, const char *bn, const char *password, gboolean truncate_pass, ClientInfo *ci, const char *key, gboolean allow_multiple_logins);
+
+/**
+ * Only used when connecting with the old-style BUCP login.
+ */
 /* 0x000b */ int aim_auth_securid_send(OscarData *od, const char *securid);
 
-void oscar_data_addhandler(OscarData *od, guint16 family, guint16 subtype, aim_rxcallback_t newhandler, guint16 flags);
-aim_rxcallback_t aim_callhandler(OscarData *od, guint16 family, guint16 subtype);
+/**
+ * Only used when connecting with clientLogin.
+ */
+void send_client_login(OscarData *od, const char *username);
 
 /* flap_connection.c */
 FlapConnection *flap_connection_new(OscarData *, int type);
@@ -632,13 +657,19 @@
 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_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);
 FlapFrame *flap_frame_new(OscarData *od, guint16 channel, int datalen);
 
+/* oscar_data.c */
+typedef int (*aim_rxcallback_t)(OscarData *od, FlapConnection *conn, FlapFrame *frame, ...);
+
 OscarData *oscar_data_new(void);
 void oscar_data_destroy(OscarData *);
+void oscar_data_addhandler(OscarData *od, guint16 family, guint16 subtype, aim_rxcallback_t newhandler, guint16 flags);
+aim_rxcallback_t aim_callhandler(OscarData *od, guint16 family, guint16 subtype);
 
 /* misc.c */
 #define AIM_VISIBILITYCHANGE_PERMITADD    0x05
--- a/libpurple/protocols/oscar/oscar_data.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/oscar/oscar_data.c	Thu Jun 25 12:49:23 2009 +0000
@@ -70,6 +70,7 @@
 	/* missing 0x14 */
 	aim__registermodule(od, icq_modfirst);
 	/* missing 0x16 */
+	/* auth_modfirst is only needed if we're connecting with the old-style BUCP login */
 	aim__registermodule(od, auth_modfirst);
 	aim__registermodule(od, email_modfirst);
 
@@ -86,6 +87,10 @@
 {
 	aim_cleansnacs(od, -1);
 
+	/* Only used when connecting with clientLogin */
+	if (od->url_data != NULL)
+		purple_util_fetch_url_cancel(od->url_data);
+
 	while (od->requesticon)
 	{
 		g_free(od->requesticon->data);
--- a/libpurple/protocols/oscar/oscarcommon.h	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/oscar/oscarcommon.h	Thu Jun 25 12:49:23 2009 +0000
@@ -45,6 +45,7 @@
 #define OSCAR_DEFAULT_ALWAYS_USE_RV_PROXY FALSE
 #define OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS TRUE
 #define OSCAR_DEFAULT_USE_SSL FALSE
+#define OSCAR_DEFAULT_USE_CLIENTLOGIN FALSE
 
 #ifdef _WIN32
 const char *oscar_get_locale_charset(void);
@@ -91,5 +92,6 @@
 void oscar_send_file(PurpleConnection *gc, const char *who, const char *file);
 PurpleXfer *oscar_new_xfer(PurpleConnection *gc, const char *who);
 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);
--- a/libpurple/protocols/yahoo/yahoo.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Thu Jun 25 12:49:23 2009 +0000
@@ -74,12 +74,10 @@
 	if (!gc || !name || !f || !purple_find_buddy(purple_connection_get_account(gc), name))
 		return;
 
-	if (f->status == YAHOO_STATUS_OFFLINE)
-	{
-		return;
-	}
-
 	switch (f->status) {
+	case YAHOO_STATUS_OFFLINE:
+		status = YAHOO_STATUS_TYPE_OFFLINE;
+		break;
 	case YAHOO_STATUS_AVAILABLE:
 		status = YAHOO_STATUS_TYPE_AVAILABLE;
 		break;
@@ -182,8 +180,28 @@
 			name = message = NULL;
 			f = NULL;
 			if (pair->value && g_utf8_validate(pair->value, -1, NULL)) {
+				GSList *tmplist;
+				int protocol = 0;
+
 				name = pair->value;
+
+				/* Look ahead to see if we have the protocol info about the buddy */
+				for (tmplist = l->next; tmplist; tmplist = tmplist->next) {
+					struct yahoo_pair *p = tmplist->data;
+					if (p->key == 7)
+						break;
+					if (p->key == 241) {
+						if(strtol(p->value, NULL, 10) == 2) {
+							g_free(msn_name);
+							msn_name = g_strconcat("msn/", name, NULL);
+							name = msn_name;
+							protocol = 2;
+						}
+						break;
+					}
+				}
 				f = yahoo_friend_find_or_new(gc, name);
+				f->protocol = protocol;
 			}
 			break;
 		case 10: /* state */
@@ -331,11 +349,7 @@
 				f->version_id = strtol(pair->value, NULL, 10);
 			break;
 		case 241: /* protocol buddy belongs to */
-			if(strtol(pair->value, NULL, 10) == 2) {
-				msn_name = g_strconcat("msn/", name, NULL);
-				name = msn_name;
-			}
-			break;
+			break;  /* We process this when get '7' */
 		default:
 			purple_debug_warning("yahoo",
 					   "Unknown status key %d\n", pair->key);
@@ -345,11 +359,16 @@
 		l = l->next;
 	}
 
-	if (message && f)
-		yahoo_friend_set_status_message(f, yahoo_string_decode(gc, message, unicode));
-
-	if (name && f) /* update the last buddy */
-		yahoo_update_status(gc, name, f);
+	if (f) {
+		if (pkt->service == YAHOO_SERVICE_LOGOFF)
+			f->status = YAHOO_STATUS_OFFLINE;
+		if (message)
+			yahoo_friend_set_status_message(f, yahoo_string_decode(gc, message, unicode));
+
+		if (name) /* update the last buddy */
+			yahoo_update_status(gc, name, f);
+	}
+	g_free(msn_name);
 }
 
 static void yahoo_do_group_check(PurpleAccount *account, GHashTable *ht, const char *name, const char *group)
@@ -500,24 +519,24 @@
 						if (!(g = purple_find_group(yd->current_list15_grp))) {
 							g = purple_group_new(yd->current_list15_grp);
 							purple_blist_add_group(g, NULL);
+						}
+						b = purple_buddy_new(account, norm_bud, NULL);
+						purple_blist_add_buddy(b, NULL, g, NULL);
 					}
-					b = purple_buddy_new(account, norm_bud, NULL);
-					purple_blist_add_buddy(b, NULL, g, NULL);
-				}
-				yahoo_do_group_check(account, ht, norm_bud, yd->current_list15_grp);
-				if(protocol != 0) {
-					f->protocol = protocol;
-					purple_debug_info("yahoo", "Setting protocol to %d\n", f->protocol);
-				}
-				if(stealth == 2)
-					f->presence = YAHOO_PRESENCE_PERM_OFFLINE;
-
-				/* set p2p status not connected and no p2p packet sent */
-				if(protocol == 0) {
-					yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_NOT_CONNECTED);
-					f->p2p_packet_sent = 0;
-				} else
-					yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_DO_NOT_CONNECT);
+					yahoo_do_group_check(account, ht, norm_bud, yd->current_list15_grp);
+					if(protocol != 0) {
+						f->protocol = protocol;
+						purple_debug_info("yahoo", "Setting protocol to %d\n", f->protocol);
+					}
+					if(stealth == 2)
+						f->presence = YAHOO_PRESENCE_PERM_OFFLINE;
+
+					/* set p2p status not connected and no p2p packet sent */
+					if(protocol == 0) {
+						yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_NOT_CONNECTED);
+						f->p2p_packet_sent = 0;
+					} else
+						yahoo_friend_set_p2p_status(f, YAHOO_P2PSTATUS_DO_NOT_CONNECT);
 				} else {
 					/* This buddy is on the ignore list (and therefore in no group) */
 					purple_debug_info("yahoo", "%s adding %s to the deny list because of the ignore list / no group was found\n",account->username, norm_bud);
@@ -1123,11 +1142,15 @@
 	struct yahoo_add_request *add_req = data;
 	struct yahoo_packet *pkt;
 	struct yahoo_data *yd = add_req->gc->proto_data;
+	const char *who = add_req->who;
+
+	if (add_req->protocol == 2)
+		who += 4;
 
 	pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH_REQ_15, YAHOO_STATUS_AVAILABLE, 0);
 	yahoo_packet_hash(pkt, "ssiii",
 					  1, add_req->id,
-					  5, add_req->who,
+					  5, who,
 					  241, add_req->protocol,
 					  13, 1,
 					  334, 0);
@@ -1662,11 +1685,16 @@
 	}
 	else if (len > 0 && ret_data && *ret_data) {
 		gchar **split_data = g_strsplit(ret_data, "\r\n", -1);
-		int totalelements = g_strv_length(split_data);
+		int totalelements = 0;
 		int response_no = -1;
 		char *crumb = NULL;
 		char *crypt = NULL;
 
+#if GLIB_CHECK_VERSION(2,6,0)
+		totalelements = g_strv_length(split_data);
+#else
+		while (split_data[++totalelements] != NULL);	
+#endif
 		if (totalelements >= 5) {
 			response_no = strtol(split_data[1], NULL, 10);
 			crumb = g_strdup(split_data[2] + strlen("crumb="));
@@ -1744,10 +1772,15 @@
 	}
 	else if (len > 0 && ret_data && *ret_data) {
 		gchar **split_data = g_strsplit(ret_data, "\r\n", -1);
-		int totalelements = g_strv_length(split_data);
+		int totalelements = 0;
 		int response_no = -1;
 		char *token = NULL;
 
+#if GLIB_CHECK_VERSION(2,6,0)
+		totalelements = g_strv_length(split_data);
+#else
+		while (split_data[++totalelements] != NULL);	
+#endif
 		if(totalelements >= 5) {
 			response_no = strtol(split_data[1], NULL, 10);
 			token = g_strdup(split_data[2] + strlen("ymsgr="));
@@ -3319,7 +3352,8 @@
 
 	server = purple_account_get_string(account, "server", YAHOO_PAGER_HOST);
 
-	if (strcmp(server, "scs.yahoo.com") == 0)
+	if (*server == '\0' || g_str_equal(server, "scs.yahoo.com") ||
+			g_str_equal(server, "scs.msg.yahoo.com"))
 		purple_account_set_string(account, "server", YAHOO_PAGER_HOST);
 }
 
--- a/libpurple/protocols/yahoo/yahoo_friend.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_friend.c	Thu Jun 25 12:49:23 2009 +0000
@@ -172,16 +172,16 @@
 		l = l->next;
 	}
 
+	if (value != 1 && value != 2) {
+		purple_debug_error("yahoo", "Received unknown value for presence key: %d\n", value);
+		return;
+	}
+
 	if(msn)
 		who = g_strconcat("msn/", temp, NULL);
 	else
 		who = g_strdup(temp);
 
-	if (value != 1 && value != 2) {
-		purple_debug_error("yahoo", "Received unknown value for presence key: %d\n", value);
-		return;
-	}
-
 	g_return_if_fail(who != NULL);
 
 	f = yahoo_friend_find(gc, who);
--- a/libpurple/protocols/yahoo/yahoo_picture.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_picture.c	Thu Jun 25 12:49:23 2009 +0000
@@ -110,6 +110,9 @@
 		l = l->next;
 	}
 
+	if (!who)
+		return;
+
 	if (!purple_privacy_check(purple_connection_get_account(gc), who)) {
 		purple_debug_info("yahoo", "Picture packet from %s dropped.\n", who);
 		return;
--- a/pidgin/gtkimhtml.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/pidgin/gtkimhtml.c	Thu Jun 25 12:49:23 2009 +0000
@@ -3010,10 +3010,21 @@
 							break;
 
 						id = gtk_imhtml_get_html_opt(tag, "ID=");
-						if (!id)
-							break;
-						gtk_imhtml_insert_image_at_iter(imhtml, atoi(id), iter);
-						g_free(id);
+						if (id) {
+							gtk_imhtml_insert_image_at_iter(imhtml, atoi(id), iter);
+							g_free(id);
+						} else {
+							char *src, *alt;
+							src = gtk_imhtml_get_html_opt(tag, "SRC=");
+							alt = gtk_imhtml_get_html_opt(tag, "ALT=");
+							if (src) {
+								gtk_imhtml_toggle_link(imhtml, src);
+								gtk_text_buffer_insert(imhtml->text_buffer, iter, alt ? alt : src, -1);
+								gtk_imhtml_toggle_link(imhtml, NULL);
+							}
+							g_free (src);
+							g_free (alt);
+						}
 						break;
 					}
 				case 47:	/* P (opt) */
--- a/pidgin/gtkstatusbox.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/pidgin/gtkstatusbox.c	Thu Jun 25 12:49:23 2009 +0000
@@ -2045,17 +2045,11 @@
 
 	if (status_box->icon_box)
 	{
-		GtkTextDirection dir = gtk_widget_get_direction(widget);
 		parent_alc.width -= (parent_alc.height + border_width);
 		icon_alc = parent_alc;
 		icon_alc.height = MAX(1, icon_alc.height) - 2;
 		icon_alc.width = icon_alc.height;
-		if (dir == GTK_TEXT_DIR_RTL) {
-			icon_alc.x = parent_alc.x;
-			parent_alc.x += icon_alc.width + border_width;
-		} else {
-			icon_alc.x = allocation->width - (icon_alc.width + border_width + 1);
-		}
+		icon_alc.x = allocation->width - (icon_alc.width + border_width + 1);
 		icon_alc.y += 1;
 
 		if (status_box->icon_size != icon_alc.height)
@@ -2264,14 +2258,22 @@
 
 	if (status_box->buddy_icon_img != NULL)
 	{
+		GdkPixbuf *buf, *scale;
+		int scale_width, scale_height;
 		GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
 		g_signal_connect(G_OBJECT(loader), "size-prepared", G_CALLBACK(pixbuf_size_prepared_cb), NULL);
 		gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(status_box->buddy_icon_img),
 		                        purple_imgstore_get_size(status_box->buddy_icon_img), NULL);
 		gdk_pixbuf_loader_close(loader, NULL);
-		status_box->buddy_icon = gdk_pixbuf_loader_get_pixbuf(loader);
-		if (status_box->buddy_icon)
-			g_object_ref(status_box->buddy_icon);
+		buf = gdk_pixbuf_loader_get_pixbuf(loader);
+		scale_width = gdk_pixbuf_get_width(buf);
+		scale_height = gdk_pixbuf_get_height(buf);
+		scale = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
+		gdk_pixbuf_fill(scale, 0x00000000);
+		gdk_pixbuf_copy_area(buf, 0, 0, scale_width, scale_height, scale, 0, 0);
+		if (pidgin_gdk_pixbuf_is_opaque(scale))
+			pidgin_gdk_pixbuf_make_round(scale);
+		status_box->buddy_icon = scale;
 		g_object_unref(loader);
 	}
 
--- a/pidgin/plugins/Makefile.mingw	Sun Jun 21 05:28:32 2009 +0000
+++ b/pidgin/plugins/Makefile.mingw	Thu Jun 25 12:49:23 2009 +0000
@@ -7,6 +7,7 @@
 PIDGIN_TREE_TOP := ../..
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
+DISCO_PLUGIN := ./disco
 GTKPERL_PLUGIN := ./perl
 TICKER_PLUGIN := ./ticker
 TRANSPARENCY_PLUGIN := ./win32/transparency
@@ -58,12 +59,14 @@
 .PHONY: all clean plugins install
 
 all: plugins
+	$(MAKE) -C $(DISCO_PLUGIN) -f $(MINGW_MAKEFILE)
 	$(MAKE) -C $(GTKPERL_PLUGIN) -f $(MINGW_MAKEFILE)
 	$(MAKE) -C $(TICKER_PLUGIN) -f $(MINGW_MAKEFILE)
 	$(MAKE) -C $(TRANSPARENCY_PLUGIN) -f $(MINGW_MAKEFILE)
 	$(MAKE) -C $(WINPREFS_PLUGIN) -f $(MINGW_MAKEFILE)
 
 install: all $(PIDGIN_INSTALL_PLUGINS_DIR)
+	$(MAKE) -C $(DISCO_PLUGIN) -f $(MINGW_MAKEFILE) install
 	$(MAKE) -C $(GTKPERL_PLUGIN) -f $(MINGW_MAKEFILE) install
 	$(MAKE) -C $(TICKER_PLUGIN) -f $(MINGW_MAKEFILE) install
 	$(MAKE) -C $(TRANSPARENCY_PLUGIN) -f $(MINGW_MAKEFILE) install
@@ -95,6 +98,7 @@
 ##
 clean:
 	rm -f *.o *.dll
+	$(MAKE) -C $(DISCO_PLUGIN) -f $(MINGW_MAKEFILE) clean
 	$(MAKE) -C $(GTKPERL_PLUGIN) -f $(MINGW_MAKEFILE) clean
 	$(MAKE) -C $(TICKER_PLUGIN) -f $(MINGW_MAKEFILE) clean
 	$(MAKE) -C $(TRANSPARENCY_PLUGIN) -f $(MINGW_MAKEFILE) clean
--- a/pidgin/plugins/disco/Makefile.am	Sun Jun 21 05:28:32 2009 +0000
+++ b/pidgin/plugins/disco/Makefile.am	Thu Jun 25 12:49:23 2009 +0000
@@ -8,7 +8,9 @@
 
 xmppdisco_la_SOURCES = \
 	gtkdisco.c \
-	xmppdisco.c
+	gtkdisco.h \
+	xmppdisco.c \
+	xmppdisco.h
 
 xmppdisco_la_LIBADD = $(GTK_LIBS)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/plugins/disco/Makefile.mingw	Thu Jun 25 12:49:23 2009 +0000
@@ -0,0 +1,79 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for xmppdisco plugin.
+#
+
+PIDGIN_TREE_TOP := ../../..
+include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+
+TARGET = xmppdisco
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS +=	-I. \
+			-I$(GTK_TOP)/include \
+			-I$(GTK_TOP)/include/gtk-2.0 \
+			-I$(GTK_TOP)/include/glib-2.0 \
+			-I$(GTK_TOP)/include/pango-1.0 \
+			-I$(GTK_TOP)/include/atk-1.0 \
+			-I$(GTK_TOP)/include/cairo \
+			-I$(GTK_TOP)/lib/glib-2.0/include \
+			-I$(GTK_TOP)/lib/gtk-2.0/include \
+			-I$(PURPLE_TOP) \
+			-I$(PURPLE_TOP)/win32 \
+			-I$(PIDGIN_TOP) \
+			-I$(PIDGIN_TOP)/win32 \
+			-I$(PIDGIN_TREE_TOP)
+
+LIB_PATHS +=		-L$(GTK_TOP)/lib \
+			-L$(PURPLE_TOP) \
+			-L$(PIDGIN_TOP)
+
+##
+##  SOURCES, OBJECTS
+##
+C_SRC =			xmppdisco.c \
+			gtkdisco.c
+
+OBJECTS = $(C_SRC:%.c=%.o)
+
+##
+## LIBRARIES
+##
+LIBS =			-lgtk-win32-2.0 \
+			-lglib-2.0 \
+			-lgdk-win32-2.0 \
+			-lgobject-2.0 \
+			-lpango-1.0 \
+			-lgdk_pixbuf-2.0 \
+			-lintl \
+			-lpurple \
+			-lpidgin
+
+include $(PIDGIN_COMMON_RULES)
+
+##
+## TARGET DEFINITIONS
+##
+.PHONY: all install clean
+
+all: $(TARGET).dll
+
+install: $(PIDGIN_INSTALL_PLUGINS_DIR) all
+	cp $(TARGET).dll $(PIDGIN_INSTALL_PLUGINS_DIR)
+
+$(OBJECTS): $(PIDGIN_CONFIG_H)
+
+$(TARGET).dll: $(PURPLE_DLL).a $(PIDGIN_DLL).a $(OBJECTS)
+	$(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll
+
+##
+## CLEAN RULES
+##
+clean:
+	rm -rf $(OBJECTS)
+	rm -rf $(TARGET).dll
+
+include $(PIDGIN_COMMON_TARGETS)
--- a/pidgin/plugins/disco/gtkdisco.c	Sun Jun 21 05:28:32 2009 +0000
+++ b/pidgin/plugins/disco/gtkdisco.c	Thu Jun 25 12:49:23 2009 +0000
@@ -108,6 +108,34 @@
 	}
 }
 
+static GdkPixbuf *
+pidgin_disco_load_icon(XmppDiscoService *service, const char *size)
+{
+	GdkPixbuf *pixbuf = NULL;
+	char *filename;
+
+	g_return_val_if_fail(service != NULL, NULL);
+	g_return_val_if_fail(size != NULL, NULL);
+
+	if (service->type == XMPP_DISCO_SERVICE_TYPE_GATEWAY && service->gateway_type) {
+		char *tmp = g_strconcat(service->gateway_type, ".png", NULL);
+		filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", size, tmp, NULL);
+		g_free(tmp);
+#if 0
+	} else if (service->type == XMPP_DISCO_SERVICE_TYPE_USER) {
+		filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "status", size, "person.png", NULL);
+#endif
+	} else if (service->type == XMPP_DISCO_SERVICE_TYPE_CHAT)
+		filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "status", size, "chat.png", NULL);
+
+	if (filename) {
+		pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
+		g_free(filename);
+	}
+
+	return pixbuf;
+}
+
 static void pidgin_disco_create_tree(PidginDiscoList *pdl);
 
 static void dialog_select_account_cb(GObject *w, PurpleAccount *account,
@@ -667,7 +695,6 @@
 {
 	PidginDiscoDialog *dialog;
 	GtkTreeIter iter, parent_iter, child;
-	char *filename = NULL;
 	GdkPixbuf *pixbuf = NULL;
 	gboolean append = TRUE;
 
@@ -725,21 +752,7 @@
 		gtk_tree_path_free(path);
 	}
 
-	if (service->type == XMPP_DISCO_SERVICE_TYPE_GATEWAY && service->gateway_type) {
-		char *tmp = g_strconcat(service->gateway_type, ".png", NULL);
-		filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "16", tmp, NULL);
-		g_free(tmp);
-#if 0
-	} else if (service->type == XMPP_DISCO_SERVICE_TYPE_USER) {
-		filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "status", "16", "person.png", NULL);
-#endif
-	} else if (service->type == XMPP_DISCO_SERVICE_TYPE_CHAT)
-		filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "status", "16", "chat.png", NULL);
-
-	if (filename) {
-		pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
-		g_free(filename);
-	}
+	pixbuf = pidgin_disco_load_icon(service, "16");
 
 	gtk_tree_store_set(pdl->model, &iter,
 			PIXBUF_COLUMN, pixbuf,
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Sun Jun 21 05:28:32 2009 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Thu Jun 25 12:49:23 2009 +0000
@@ -773,6 +773,7 @@
     Delete "$INSTDIR\plugins\win2ktrans.dll"
     Delete "$INSTDIR\plugins\winprefs.dll"
     Delete "$INSTDIR\plugins\xmppconsole.dll"
+    Delete "$INSTDIR\plugins\xmppdisco.dll"
     RMDir "$INSTDIR\plugins"
     RMDir /r "$INSTDIR\sasl2"
     Delete "$INSTDIR\sounds\purple\alert.wav"