changeset 27275:916f266aca98

merge of '1356d70e521b4cb5ab8ea10f0927063b5c62394a' and '6e02b385a7decbbfdad9bc91c80b0bc86fbd8129'
author Paul Aurich <paul@darkrain42.org>
date Sat, 27 Jun 2009 17:50:49 +0000
parents cb3e89f5a2d5 (current diff) 291724375feb (diff)
children 7f43d6779764
files
diffstat 82 files changed, 2662 insertions(+), 850 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Sat Jun 27 17:50:35 2009 +0000
+++ b/COPYRIGHT	Sat Jun 27 17:50:49 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
@@ -510,6 +512,7 @@
 Ma Xuan
 Jared Yanovich
 Timmy Yee
+Li Yuan
 Nickolai Zeldovich
 Tom Zickel
 Marco Ziech
--- a/ChangeLog	Sat Jun 27 17:50:35 2009 +0000
+++ b/ChangeLog	Sat Jun 27 17:50:49 2009 +0000
@@ -18,11 +18,17 @@
 	  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.
+	* GnuTLS logging (disabled by default) can be controlled through the
+	  PURPLE_GNUTLS_DEBUG environment variable, which is an integer between
+	  0 and 9 (higher is more verbose). Higher values may reveal sensitive
+	  information.
+
+	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
@@ -60,6 +66,8 @@
 	* Fix an issue where Cyrus SASL DIGEST MD5 authentication might fail if
 	  the username, password, or realm (the JID domain) contain non-ASCII
 	  characters.
+	* Show emblem for mobile, handheld, and web clients and bots (if the other
+	  client supports it).
 
 	Yahoo:
 	* P2P file transfers.  (Sulabh Mahajan)
@@ -67,9 +75,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
@@ -97,6 +102,12 @@
 	  collapse buddy groups or contacts. (Peter Ruibal)
 	* 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
@@ -110,6 +121,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	Sat Jun 27 17:50:35 2009 +0000
+++ b/ChangeLog.API	Sat Jun 27 17:50:49 2009 +0000
@@ -39,6 +39,7 @@
 		* purple_network_set_turn_server
 		* purple_network_get_stun_ip
 		* purple_network_get_turn_ip
+		* purple_network_remove_port_mapping
 		* purple_proxy_connect_udp
 		* purple_prpl_get_media_caps
 		* purple_prpl_got_account_actions
@@ -127,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	Sat Jun 27 17:50:35 2009 +0000
+++ b/ChangeLog.win32	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/NEWS	Sat Jun 27 17:50:49 2009 +0000
@@ -2,6 +2,33 @@
 
 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.
+
 2.5.5 (03/01/2009):
 	John: Well, yet another release with bug fixing and patches.  Hopefully
 	one of the fixed bugs is one that irritated you.  Also, thank Dimmuxx
--- a/configure.ac	Sat Jun 27 17:50:35 2009 +0000
+++ b/configure.ac	Sat Jun 27 17:50:49 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/finch/plugins/gnthistory.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/finch/plugins/gnthistory.c	Sat Jun 27 17:50:49 2009 +0000
@@ -31,6 +31,7 @@
 #include "util.h"
 #include "version.h"
 
+#include "gntconv.h"
 #include "gntplugin.h"
 #include "gntrequest.h"
 
@@ -54,6 +55,9 @@
 	if (convtype == PURPLE_CONV_TYPE_IM) {
 		GSList *buddies;
 		GSList *cur;
+		FinchConv *fc = FINCH_CONV(c);
+		if (fc->list && fc->list->next) /* We were already in the middle of a conversation. */
+			return;
 
 		/* If we're not logging, don't show anything.
 		 * Otherwise, we might show a very old log. */
--- a/libpurple/cipher.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/cipher.c	Sat Jun 27 17:50:49 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/connection.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/connection.c	Sat Jun 27 17:50:49 2009 +0000
@@ -309,7 +309,7 @@
 	g_free(gc->password);
 	g_free(gc->display_name);
 
-	if (gc->disconnect_timeout)
+	if (gc->disconnect_timeout > 0)
 		purple_timeout_remove(gc->disconnect_timeout);
 
 	PURPLE_DBUS_UNREGISTER_POINTER(gc);
@@ -515,11 +515,20 @@
 static gboolean
 purple_connection_disconnect_cb(gpointer data)
 {
-	PurpleAccount *account = data;
-	char *password = g_strdup(purple_account_get_password(account));
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	char *password;
+
+	account = data;
+	gc = purple_account_get_connection(account);
+
+	gc->disconnect_timeout = 0;
+
+	password = g_strdup(purple_account_get_password(account));
 	purple_account_disconnect(account);
 	purple_account_set_password(account, password);
 	g_free(password);
+
 	return FALSE;
 }
 
@@ -564,7 +573,7 @@
 	}
 
 	/* If we've already got one error, we don't need any more */
-	if (gc->disconnect_timeout)
+	if (gc->disconnect_timeout > 0)
 		return;
 
 	gc->wants_to_die = purple_connection_error_is_fatal (reason);
--- a/libpurple/connection.h	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/connection.h	Sat Jun 27 17:50:49 2009 +0000
@@ -560,6 +560,9 @@
  * Checks if gc is still a valid pointer to a gc.
  *
  * @return @c TRUE if gc is valid.
+ *
+ * @deprecated Do not use this.  Instead, cancel your asynchronous request
+ *             when the PurpleConnection is destroyed.
  */
 /*
  * TODO: Eventually this bad boy will be removed, because it is
--- a/libpurple/core.h	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/core.h	Sat Jun 27 17:50:49 2009 +0000
@@ -188,7 +188,9 @@
  *   <dd>the UI's development/support website, such as http://developer.pidgin.im.</dd>
  *
  *   <dt><tt>client_type</tt></dt>
- *   <dd>the type of UI (pc, console, phone, handheld, web, bot)</dd>
+ *   <dd>the type of UI. Possible values include 'pc', 'console', 'phone',
+ *       'handheld', 'web', and 'bot'. These values are compared
+ *       programmatically and should not be localized.</dd>
  *   
  * </dl>
  *
--- a/libpurple/network.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/network.c	Sat Jun 27 17:50:49 2009 +0000
@@ -105,6 +105,10 @@
 static gchar *stun_ip = NULL;
 static gchar *turn_ip = NULL;
 
+/* Keep track of port mappings done with UPnP and NAT-PMP */
+static GHashTable *upnp_port_mappings = NULL;
+static GHashTable *nat_pmp_port_mappings = NULL;
+
 const unsigned char *
 purple_network_ip_atoi(const char *ip)
 {
@@ -257,6 +261,15 @@
 		return;
 	}
 
+	if (success) {
+		/* add port mapping to hash table */
+		gint *key = g_new(gint, 1);
+		gint *value = g_new(gint, 1);
+		*key = purple_network_get_port_from_fd(listen_data->listenfd);
+		*value = listen_data->socket_type;
+		g_hash_table_insert(upnp_port_mappings, key, value);
+	}
+
 	if (listen_data->cb)
 		listen_data->cb(listen_data->listenfd, listen_data->cb_data);
 
@@ -270,9 +283,16 @@
 purple_network_finish_pmp_map_cb(gpointer data)
 {
 	PurpleNetworkListenData *listen_data;
+	gint *key = g_new(gint, 1);
+	gint *value = g_new(gint, 1);
 
 	listen_data = data;
 
+	/* add port mapping to hash table */
+	*key = purple_network_get_port_from_fd(listen_data->listenfd);
+	*value = listen_data->socket_type;
+	g_hash_table_insert(nat_pmp_port_mappings, key, value);
+
 	if (listen_data->cb)
 		listen_data->cb(listen_data->listenfd, listen_data->cb_data);
 
@@ -892,6 +912,61 @@
 	return &handle;
 }
 
+static void
+purple_network_upnp_mapping_remove_cb(gboolean sucess, gpointer data)
+{
+	purple_debug_info("network", "done removing UPnP port mapping\n");
+}
+
+/* the reason for these functions to have these signatures is to be able to
+ use them for g_hash_table_foreach to clean remaining port mappings, which is
+ not yet done */
+static void
+purple_network_upnp_mapping_remove(gpointer key, gpointer value,
+	gpointer user_data)
+{
+	gint port = (gint) *((gint *) key);
+	gint protocol = (gint) *((gint *) value);
+	purple_debug_info("network", "removing UPnP port mapping for port %d\n",
+		port);
+	purple_upnp_remove_port_mapping(port, 
+		protocol == SOCK_STREAM ? "TCP" : "UDP", 
+		purple_network_upnp_mapping_remove_cb, NULL);
+	g_hash_table_remove(upnp_port_mappings, key);
+}
+
+static void
+purple_network_nat_pmp_mapping_remove(gpointer key, gpointer value,
+	gpointer user_data)
+{
+	gint port = (gint) *((gint *) key);
+	gint protocol = (gint) *((gint *) value);
+	purple_debug_info("network", "removing NAT-PMP port mapping for port %d\n",
+		port);
+	purple_pmp_destroy_map(
+		protocol == SOCK_STREAM ? PURPLE_PMP_TYPE_TCP : PURPLE_PMP_TYPE_UDP, 
+		port);
+	g_hash_table_remove(nat_pmp_port_mappings, key);
+}
+
+void
+purple_network_remove_port_mapping(gint fd)
+{
+	int port = purple_network_get_port_from_fd(fd);
+	gint *protocol = g_hash_table_lookup(upnp_port_mappings, &port);
+
+	if (protocol) {
+		purple_network_upnp_mapping_remove(&port, protocol, NULL);
+		g_hash_table_remove(upnp_port_mappings, protocol);
+	} else {
+		protocol = g_hash_table_lookup(nat_pmp_port_mappings, &port);
+		if (protocol) {
+			purple_network_nat_pmp_mapping_remove(&port, protocol, NULL);
+			g_hash_table_remove(nat_pmp_port_mappings, protocol);
+		}
+	}
+}
+	
 void
 purple_network_init(void)
 {
@@ -964,8 +1039,15 @@
 		purple_prefs_get_string("/purple/network/stun_server"));
 	purple_network_set_turn_server(
 		purple_prefs_get_string("/purple/network/turn_server"));
+
+	upnp_port_mappings = 
+		g_hash_table_new_full(g_int_hash, g_int_equal, g_free, g_free);
+	nat_pmp_port_mappings =
+		g_hash_table_new_full(g_int_hash, g_int_equal, g_free, g_free);
 }
 
+
+
 void
 purple_network_uninit(void)
 {
@@ -1008,4 +1090,10 @@
 	
 	if (stun_ip)
 		g_free(stun_ip);
+
+	g_hash_table_destroy(upnp_port_mappings);
+	g_hash_table_destroy(nat_pmp_port_mappings);
+
+	/* TODO: clean up remaining port mappings, note calling 
+	 purple_upnp_remove_port_mapping from here doesn't quite work... */
 }
--- a/libpurple/network.h	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/network.h	Sat Jun 27 17:50:49 2009 +0000
@@ -246,10 +246,10 @@
  * Update the TURN server IP given the host name
  * Will result in a DNS query being executed asynchronous
  * 
- * @param stun_server The host name of the STUN server to set
+ * @param turn_server The host name of the STUN server to set
  * @since 2.6.0
  */
-void purple_network_set_turn_server(const gchar *stun_server);
+void purple_network_set_turn_server(const gchar *turn_server);
 	
 /**
  * Get the IP address of the STUN server as a string representation
@@ -259,7 +259,14 @@
  */
 const gchar *purple_network_get_turn_ip(void);
 		
-	
+/**
+ * Remove a port mapping (UPnP or NAT-PMP) associated with listening socket
+ *
+ * @param fd Socket to remove the port mapping for
+ * @since 2.6.0
+ */
+void purple_network_remove_port_mapping(gint fd);	
+
 /**
  * Initializes the network subsystem.
  */
--- a/libpurple/notify.h	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/notify.h	Sat Jun 27 17:50:49 2009 +0000
@@ -427,7 +427,9 @@
  * Displays a notification for multiple emails to the user.
  *
  * @param handle    The plugin or connection handle.
- * @param count     The number of emails.
+ * @param count     The number of emails.  '0' can be used to signify that
+ *                  the user has no unread emails and the UI should remove
+ *                  the mail notification.
  * @param detailed  @c TRUE if there is information for each email in the
  *                  arrays.
  * @param subjects  The array of subjects.
--- a/libpurple/plugins/ssl/ssl-gnutls.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c	Sat Jun 27 17:50:49 2009 +0000
@@ -43,8 +43,17 @@
 static gnutls_certificate_client_credentials xcred;
 
 static void
+ssl_gnutls_log(int level, const char *str)
+{
+	/* GnuTLS log messages include the '\n' */
+	purple_debug_misc("gnutls", "lvl %d: %s", level, str);
+}
+
+static void
 ssl_gnutls_init_gnutls(void)
 {
+	const char *debug_level;
+
 	/* Configure GnuTLS to use glib memory management */
 	/* I expect that this isn't really necessary, but it may prevent
 	   some bugs */
@@ -59,6 +68,20 @@
 		(gnutls_free_function)    g_free     /* free */
 		);
 
+	debug_level = g_getenv("PURPLE_GNUTLS_DEBUG");
+	if (debug_level) {
+		int level = atoi(debug_level);
+		if (level < 0) {
+			purple_debug_warning("gnutls", "Assuming log level 0 instead of %d\n",
+			                     level);
+			level = 0;
+		}
+
+		/* "The level is an integer between 0 and 9. Higher values mean more verbosity." */
+		gnutls_global_set_log_level(level);
+		gnutls_global_set_log_function(ssl_gnutls_log);
+	}
+
 	gnutls_global_init();
 
 	gnutls_certificate_allocate_credentials(&xcred);
@@ -576,7 +599,6 @@
 							  out_buf, out_size);
 
 	g_free(out_buf);
-	g_return_val_if_fail(success, FALSE);
 	return success;
 }
 
--- a/libpurple/protocols/bonjour/bonjour_ft.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/bonjour/bonjour_ft.c	Sat Jun 27 17:50:49 2009 +0000
@@ -880,7 +880,9 @@
 	purple_proxy_info_set_type(xf->proxy_info, PURPLE_PROXY_SOCKS5);
 	purple_proxy_info_set_host(xf->proxy_info, xf->proxy_host);
 	purple_proxy_info_set_port(xf->proxy_info, xf->proxy_port);
-	xf->proxy_connection = purple_proxy_connect_socks5(NULL, xf->proxy_info,
+	xf->proxy_connection = purple_proxy_connect_socks5(
+							   purple_account_get_connection(account),
+							   xf->proxy_info,
 							   dstaddr, 0,
 							   bonjour_bytestreams_connect_cb, xfer);
 
--- a/libpurple/protocols/bonjour/jabber.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Sat Jun 27 17:50:49 2009 +0000
@@ -926,7 +926,9 @@
 		}
 		purple_proxy_info_set_type(proxy_info, PURPLE_PROXY_NONE);
 
-		connect_data = purple_proxy_connect(NULL, jdata->account,
+		connect_data = purple_proxy_connect(
+						    purple_account_get_connection(jdata->account),
+						    jdata->account,
 						    ip, bb->port_p2pj, _connected_to_buddy, pb);
 
 		if (connect_data == NULL) {
--- a/libpurple/protocols/gg/gg.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/gg/gg.c	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/jabber/auth.c	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/jabber/bosh.c	Sat Jun 27 17:50:49 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/buddy.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Sat Jun 27 17:50:49 2009 +0000
@@ -1102,8 +1102,12 @@
 	char *txt, *vcard_hash = NULL;
 
 	if (type == JABBER_IQ_ERROR) {
-		purple_debug_warning("jabber", "Server returned error while retrieving vCard");
-		return;
+		xmlnode *error;
+		purple_debug_warning("jabber", "Server returned error while retrieving vCard\n");
+
+		error = xmlnode_get_child(packet, "error");
+		if (!error || !xmlnode_get_child(error, "item-not-found"))
+			return;
 	}
 
 	if((vcard = xmlnode_get_child(packet, "vCard")) ||
--- a/libpurple/protocols/jabber/caps.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/jabber/caps.c	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sat Jun 27 17:50:49 2009 +0000
@@ -758,6 +758,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;
@@ -824,21 +826,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/jabber.h	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Sat Jun 27 17:50:49 2009 +0000
@@ -325,8 +325,8 @@
 void jabber_add_feature(const gchar *namespace, JabberFeatureEnabled cb); /* cb may be NULL */
 void jabber_remove_feature(const gchar *namespace);
 
-/** Adds an identitiy to this jabber library instance. For list of valid values vistit the
- *	webiste of the XMPP Registrar ( http://www.xmpp.org/registrar/disco-categories.html#client ).
+/** Adds an identity to this jabber library instance. For list of valid values visit the
+ *	website of the XMPP Registrar ( http://www.xmpp.org/registrar/disco-categories.html#client ).
  *  @param category the category of the identity.
  *  @param type the type of the identity.
  *  @param language the language localization of the name. Can be NULL.
--- a/libpurple/protocols/jabber/libxmpp.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Sat Jun 27 17:50:49 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/presence.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Sat Jun 27 17:50:49 2009 +0000
@@ -245,7 +245,9 @@
 {
 	xmlnode *show, *status, *presence, *pri, *c;
 	const char *show_string = NULL;
+#ifdef USE_VV
 	gboolean audio_enabled, video_enabled;
+#endif
 
 	presence = xmlnode_new("presence");
 
--- a/libpurple/protocols/jabber/si.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/jabber/si.c	Sat Jun 27 17:50:49 2009 +0000
@@ -1321,6 +1321,11 @@
 			jabber_iq_remove_callback_by_id(js, jsx->iq_id);
 		if (jsx->local_streamhost_fd >= 0)
 			close(jsx->local_streamhost_fd);
+		if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND &&
+			xfer->fd >= 0) {
+			purple_debug_info("jabber", "remove port mapping\n");
+			purple_network_remove_port_mapping(xfer->fd);
+		}
 		if (jsx->connect_timeout > 0)
 			purple_timeout_remove(jsx->connect_timeout);
 		if (jsx->ibb_timeout_handle > 0)
--- a/libpurple/protocols/jabber/useravatar.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/jabber/useravatar.c	Sat Jun 27 17:50:49 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/msn/cmdproc.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/msn/cmdproc.c	Sat Jun 27 17:50:49 2009 +0000
@@ -210,6 +210,7 @@
 
 	trans = g_new0(MsnTransaction, 1);
 
+	trans->cmdproc = cmdproc;
 	trans->command = g_strdup(command);
 
 	if (format != NULL)
--- a/libpurple/protocols/msn/history.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/msn/history.c	Sat Jun 27 17:50:49 2009 +0000
@@ -68,6 +68,7 @@
 msn_history_add(MsnHistory *history, MsnTransaction *trans)
 {
 	GQueue *queue;
+	int max_elems;
 
 	g_return_if_fail(history != NULL);
 	g_return_if_fail(trans   != NULL);
@@ -78,7 +79,12 @@
 
 	g_queue_push_tail(queue, trans);
 
-	if (queue->length > MSN_HIST_ELEMS)
+	if (trans->cmdproc->servconn->type == MSN_SERVCONN_NS)
+		max_elems = MSN_NS_HIST_ELEMS;
+	else
+		max_elems = MSN_SB_HIST_ELEMS;
+
+	if (queue->length > max_elems)
 	{
 		trans = g_queue_pop_head(queue);
 		msn_transaction_destroy(trans);
--- a/libpurple/protocols/msn/history.h	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/msn/history.h	Sat Jun 27 17:50:49 2009 +0000
@@ -24,7 +24,8 @@
 #ifndef _MSN_HISTORY_H
 #define _MSN_HISTORY_H
 
-#define MSN_HIST_ELEMS 0x30
+#define MSN_NS_HIST_ELEMS 0x300
+#define MSN_SB_HIST_ELEMS 0x30
 
 typedef struct _MsnHistory MsnHistory;
 
--- a/libpurple/protocols/msn/notification.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/msn/notification.c	Sat Jun 27 17:50:49 2009 +0000
@@ -327,14 +327,13 @@
 	/* NOTE: cmd is not always cmdproc->last_cmd, sometimes cmd is a queued
 	 * command and we are processing it */
 	if (cmd->payload == NULL) {
-		cmdproc->last_cmd->payload_cb  = msg_cmd_post;
+		cmdproc->last_cmd->payload_cb = msg_cmd_post;
 		cmd->payload_len = atoi(cmd->params[2]);
-
 	} else {
 		g_return_if_fail(cmd->payload_cb != NULL);
 
 #if 0 /* glib on win32 doesn't correctly support precision modifiers for a string */
-		purple_debug_info("msn", "MSG payload:{%.*s}\n", cmd->payload_len, cmd->payload);
+		purple_debug_info("msn", "MSG payload:{%.*s}\n", (guint)cmd->payload_len, cmd->payload);
 #endif
 		cmd->payload_cb(cmdproc, cmd, cmd->payload, cmd->payload_len);
 	}
--- a/libpurple/protocols/myspace/markup.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/myspace/markup.c	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Sat Jun 27 17:50:49 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);
@@ -1824,17 +1848,19 @@
 					gchar *suggestion;
 
 					suggestion = g_strdup_printf(_("%s Your password is "
-							"%d characters, greater than the "
-							"expected maximum length of %d for "
-							"MySpaceIM. Please shorten your "
+							"%zu characters, which is longer than the "
+							"maximum length of %d.  Please shorten your "
 							"password at http://profileedit.myspace.com/index.cfm?fuseaction=accountSettings.changePassword and try again."),
-							full_errmsg, (int)
+							full_errmsg,
 							strlen(session->account->password),
 							MSIM_MAX_PASSWORD_LENGTH);
 
 					/* Replace full_errmsg. */
 					g_free(full_errmsg);
 					full_errmsg = suggestion;
+				} else {
+					g_free(full_errmsg);
+					full_errmsg = g_strdup(_("Incorrect username or password"));
 				}
 #endif
 				break;
@@ -2285,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
@@ -2338,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	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/myspace/myspace.h	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/myspace/user.c	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/myspace/zap.c	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/oscar/Makefile.am	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/oscar/Makefile.mingw	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:49 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: %" G_GSIZE_FORMAT "\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	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/oscar/family_auth.c	Sat Jun 27 17:50:49 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 */
 
@@ -129,6 +129,8 @@
 	GSList *tlvlist = NULL;
 	int passwdlen;
 	guint8 *password_encoded;
+	const char *clientstring;
+	guint32 distrib;
 
 	passwdlen = strlen(password);
 	password_encoded = (guint8 *)g_malloc(passwdlen+1);
@@ -139,18 +141,25 @@
 
 	aim_encode_password(password, password_encoded);
 
+	clientstring = purple_prefs_get_string("/plugins/prpl/oscar/clientstring");
+	if (clientstring == NULL)
+		clientstring = ci->clientstring;
+	distrib = purple_prefs_get_int("/plugins/prpl/oscar/distid");
+	if ((gint32)distrib == -1)
+		distrib = ci->distrib;
+
 	byte_stream_put32(&frame->data, 0x00000001); /* FLAP Version */
 	aim_tlvlist_add_str(&tlvlist, 0x0001, sn);
 	aim_tlvlist_add_raw(&tlvlist, 0x0002, passwdlen, password_encoded);
 
-	if (ci->clientstring)
-		aim_tlvlist_add_str(&tlvlist, 0x0003, ci->clientstring);
+	if (clientstring)
+		aim_tlvlist_add_str(&tlvlist, 0x0003, clientstring);
 	aim_tlvlist_add_16(&tlvlist, 0x0016, (guint16)ci->clientid);
 	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_32(&tlvlist, 0x0014, (guint32)ci->distrib); /* distribution chan */
+	aim_tlvlist_add_32(&tlvlist, 0x0014, distrib); /* distribution chan */
 	aim_tlvlist_add_str(&tlvlist, 0x000f, ci->lang);
 	aim_tlvlist_add_str(&tlvlist, 0x000e, ci->country);
 
@@ -210,6 +219,8 @@
 	guint8 digest[16];
 	aim_snacid_t snacid;
 	size_t password_len;
+	const char *clientstring;
+	guint32 distrib;
 
 	if (!ci || !sn || !password)
 		return -EINVAL;
@@ -236,20 +247,27 @@
 
 	aim_encode_password_md5(password, password_len, key, digest);
 
+	clientstring = purple_prefs_get_string("/plugins/prpl/oscar/clientstring");
+	if (clientstring == NULL)
+		clientstring = ci->clientstring;
+	distrib = purple_prefs_get_int("/plugins/prpl/oscar/distid");
+	if ((gint32)distrib == -1)
+		distrib = ci->distrib;
+
 	aim_tlvlist_add_raw(&tlvlist, 0x0025, 16, digest);
 
 #ifndef USE_OLD_MD5
 	aim_tlvlist_add_noval(&tlvlist, 0x004c);
 #endif
 
-	if (ci->clientstring)
-		aim_tlvlist_add_str(&tlvlist, 0x0003, ci->clientstring);
+	if (clientstring)
+		aim_tlvlist_add_str(&tlvlist, 0x0003, clientstring);
 	aim_tlvlist_add_16(&tlvlist, 0x0016, (guint16)ci->clientid);
 	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_32(&tlvlist, 0x0014, (guint32)ci->distrib);
+	aim_tlvlist_add_32(&tlvlist, 0x0014, distrib);
 	aim_tlvlist_add_str(&tlvlist, 0x000f, ci->lang);
 	aim_tlvlist_add_str(&tlvlist, 0x000e, ci->country);
 
--- a/libpurple/protocols/oscar/flap_connection.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Sat Jun 27 17:50:49 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) {
@@ -1107,6 +1109,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);
@@ -1152,14 +1155,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);
@@ -1430,7 +1445,6 @@
 {
 	PurpleConnection *gc;
 	OscarData *od;
-	FlapConnection *newconn;
 
 	gc = purple_account_get_connection(account);
 	od = oscar_data_new();
@@ -1445,9 +1459,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);
@@ -1523,10 +1540,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
@@ -1544,32 +1585,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);
@@ -1604,165 +1642,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;
@@ -1833,13 +1712,6 @@
 	gchar *buf;
 	gssize result;
 
-	if (!PURPLE_CONNECTION_IS_VALID(pos->gc))
-	{
-		g_free(pos->modname);
-		g_free(pos);
-		return;
-	}
-
 	pos->fd = source;
 
 	if (source < 0) {
@@ -1879,7 +1751,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;
@@ -1937,8 +1810,7 @@
 	pos->len = len;
 	pos->modname = g_strdup(modname);
 
-	/* TODO: Keep track of this return value. */
-	if (purple_proxy_connect(NULL, pos->gc->account, "pidgin.im", 80,
+	if (purple_proxy_connect(pos->gc, pos->gc->account, "pidgin.im", 80,
 			straight_to_hell, pos) == NULL)
 	{
 		char buf[256];
@@ -1957,6 +1829,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, ...)
 {
@@ -2002,7 +2072,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)
 	{
@@ -3920,20 +3990,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;
 }
 
@@ -4436,7 +4495,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);
 }
 
@@ -6384,6 +6444,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))
@@ -6463,7 +6527,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)) {
@@ -6624,6 +6688,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;
@@ -6764,12 +6831,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);
@@ -7006,6 +7077,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);
@@ -7022,6 +7097,16 @@
 	/* Preferences */
 	purple_prefs_add_none("/plugins/prpl/oscar");
 	purple_prefs_add_bool("/plugins/prpl/oscar/recent_buddies", FALSE);
+
+	/*
+	 * These two preferences will normally not be changed.  UIs can optionally
+	 * use them to override these two version fields which are sent to the
+	 * server when logging in.  AOL requested this change to allow clients to
+	 * use custom values.
+	 */
+	purple_prefs_add_string("/plugins/prpl/oscar/clientstring", NULL);
+	purple_prefs_add_int("/plugins/prpl/oscar/distid", -1);
+
 	purple_prefs_remove("/plugins/prpl/oscar/show_idle");
 	purple_prefs_remove("/plugins/prpl/oscar/always_use_rv_proxy");
 
--- a/libpurple/protocols/oscar/oscar.h	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/oscar/oscar_data.c	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/oscar/oscarcommon.h	Sat Jun 27 17:50:49 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/sametime/sametime.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Sat Jun 27 17:50:49 2009 +0000
@@ -1466,7 +1466,7 @@
 
   if(purple_account_get_bool(account, MW_KEY_FORCE, FALSE) ||
      !host || (! strcmp(current_host, host)) ||
-     (purple_proxy_connect(NULL, account, host, port, connect_cb, pd) == NULL)) {
+     (purple_proxy_connect(gc, account, host, port, connect_cb, pd) == NULL)) {
 
     /* if we're configured to force logins, or if we're being
        redirected to the already configured host, or if we couldn't
--- a/libpurple/protocols/simple/simple.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/simple/simple.c	Sat Jun 27 17:50:49 2009 +0000
@@ -446,13 +446,6 @@
 	struct simple_account_data *sip;
 	struct sip_connection *conn;
 
-	if (!PURPLE_CONNECTION_IS_VALID(gc))
-	{
-		if (source >= 0)
-			close(source);
-		return;
-	}
-
 	if(source < 0) {
 		purple_connection_error_reason(gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
@@ -1735,13 +1728,6 @@
 	struct simple_account_data *sip;
 	struct sip_connection *conn;
 
-	if (!PURPLE_CONNECTION_IS_VALID(gc))
-	{
-		if (source >= 0)
-			close(source);
-		return;
-	}
-
 	if(source < 0) {
 		purple_connection_error_reason(gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
--- a/libpurple/protocols/yahoo/util.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/yahoo/util.c	Sat Jun 27 17:50:49 2009 +0000
@@ -22,7 +22,7 @@
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
+#endif /* HAVE_CONFIG_H */
 
 #include "debug.h"
 #include "internal.h"
@@ -42,7 +42,7 @@
 /*
  * Returns cookies formatted as a null terminated string for the given connection.
  * Must g_free return value.
- * 
+ *
  * TODO:will work, but must test for strict correctness
  */
 gchar* yahoo_get_cookies(PurpleConnection *gc)
@@ -191,7 +191,7 @@
 /*
  * I found these on some website but i don't know that they actually
  * work (or are supposed to work). I didn't implement them yet.
- * 
+ *
      * [0;30m ---black
      * [1;37m ---white
      * [0;37m ---tan
--- a/libpurple/protocols/yahoo/yahoo.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Sat Jun 27 17:50:49 2009 +0000
@@ -64,7 +64,7 @@
 static void yahoo_add_buddy(PurpleConnection *gc, PurpleBuddy *, PurpleGroup *);
 #ifdef TRY_WEBMESSENGER_LOGIN
 static void yahoo_login_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message);
-#endif
+#endif /* TRY_WEBMESSENGER_LOGIN */
 static void yahoo_set_status(PurpleAccount *account, PurpleStatus *status);
 
 static void yahoo_update_status(PurpleConnection *gc, const char *name, YahooFriend *f)
@@ -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,30 +519,30 @@
 						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);
 					purple_privacy_deny_add(account, norm_bud, 1);
 				}
-			
+
 				protocol = 0;
 				stealth = 0;
 				norm_bud = NULL;
@@ -554,7 +573,7 @@
 	}
 
 	g_hash_table_foreach(ht, yahoo_do_group_cleanup, NULL);
-	
+
 	/* Now that we have processed the buddy list, we can say yahoo has connected */
 	purple_connection_set_display_name(gc, purple_normalize(account, purple_account_get_username(account)));
 	purple_connection_set_state(gc, PURPLE_CONNECTED);
@@ -726,7 +745,6 @@
 	gint val_11 = 0;
 	struct yahoo_data *yd = gc->proto_data;
 	gboolean msn = FALSE;
-	char *msn_from = NULL;
 
 	account = purple_connection_get_account(gc);
 
@@ -759,17 +777,16 @@
 		return;
 	}
 
-	if(msn)
-		msn_from = g_strconcat("msn/", from, NULL);
-
 	if (!g_ascii_strncasecmp(msg, "TYPING", strlen("TYPING"))
 		&& (purple_privacy_check(account, from)))
 	{
 		if(msn) {
+			char *msn_from = g_strconcat("msn/", from, NULL);
 			if (*stat == '1')
 				serv_got_typing(gc, msn_from, 0, PURPLE_TYPING);
 			else
 				serv_got_typing_stopped(gc, msn_from);
+			g_free(msn_from);
 		}
 		else	{
 			if (*stat == '1')
@@ -803,8 +820,6 @@
 		purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, time(NULL));
 		g_free(buf);
 	}
-
-	g_free(msn_from);
 }
 
 
@@ -813,6 +828,7 @@
 	int time;
 	int utf8;
 	int buddy_icon;
+	char *id;
 	char *msg;
 };
 
@@ -824,8 +840,8 @@
 	struct yahoo_data *yd;
 	char *server_msg = NULL;
 	char *m;
-	
-	yd = gc->proto_data;	
+
+	yd = gc->proto_data;
 	account = purple_connection_get_account(gc);
 
 	while (l != NULL) {
@@ -858,7 +874,7 @@
 		}
 		else
 			purple_notify_error(gc, NULL, _("Your SMS was not delivered"), NULL);
-		
+
 		g_free(sms->from);
 		g_free(sms);
 		return ;
@@ -871,7 +887,7 @@
 
 	m = yahoo_string_decode(gc, sms->msg, sms->utf8);
 	serv_got_im(gc, sms->from, m, 0, sms->time);
-		
+
 	g_free(m);
 	g_free(sms->from);
 	g_free(sms);
@@ -930,6 +946,9 @@
 			{
 				imv = pair->value;
 			}
+			if (pair->key == 429)
+				if (im)
+					im->id = pair->value;
 			l = l->next;
 		}
 	} else if (pkt->status == 2) {
@@ -997,6 +1016,28 @@
 			return;
 		}
 
+		/*
+		 * TODO: Is there anything else we should check when determining whether
+		 *       we should send an acknowledgement?
+		 */
+		if (im->id != NULL) {
+			/* Send acknowledgement.  If we don't do this then the official
+			 * Yahoo Messenger client for Windows will send us the same
+			 * message 7 seconds later as an offline message.  This is true
+			 * for at least version 9.0.0.2162 on Windows XP. */
+			struct yahoo_packet *pkt2;
+			pkt2 = yahoo_packet_new(YAHOO_SERVICE_MESSAGE_ACK,
+					YAHOO_STATUS_AVAILABLE, pkt->id);
+			yahoo_packet_hash(pkt2, "ssisii",
+					1, purple_connection_get_display_name(gc),
+					5, im->from,
+					302, 430,
+					430, im->id,
+					303, 430,
+					450, 0);
+			yahoo_packet_send_and_free(pkt2, yd);
+		}
+
 		m = yahoo_string_decode(gc, im->msg, im->utf8);
 		/* This may actually not be necessary, but it appears
 		 * that at least at one point some clients were sending
@@ -1024,7 +1065,7 @@
 				username = g_markup_escape_text(msn_from, -1);
 			else
 				username = g_markup_escape_text(im->from, -1);
-			
+
 			purple_prpl_got_attention(gc, username, YAHOO_BUZZ);
 			g_free(username);
 			g_free(m);
@@ -1043,7 +1084,7 @@
 
 		g_free(m2);
 
-		/* laters : implement buddy icon for msn friends */ 
+		/* laters : implement buddy icon for msn friends */
 		if(!msn) {
 			if ((f = yahoo_friend_find(gc, im->from)) && im->buddy_icon == 2) {
 				if (yahoo_friend_get_buddy_icon_need_request(f)) {
@@ -1097,11 +1138,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);
@@ -1625,16 +1670,21 @@
 		purple_debug_error("yahoo", "Login Failed, unable to retrieve stage 2 url: %s\n", error_message);
 		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_message);
 		g_free(auth_data->seed);
-		g_free(auth_data);	
+		g_free(auth_data);
 		return;
 	}
 	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="));
@@ -1712,10 +1762,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="));
@@ -1972,7 +2027,7 @@
 {
 #ifdef TRY_WEBMESSENGER_LOGIN
 	struct yahoo_data *yd = gc->proto_data;
-#endif
+#endif /* TRY_WEBMESSENGER_LOGIN */
 	GSList *l = pkt->hash;
 	int err = 0;
 	char *msg;
@@ -2016,7 +2071,7 @@
 				yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
 			return;
 		}
-#endif
+#endif /* TRY_WEBMESSENGER_LOGIN */
 		if (!purple_account_get_remember_password(account))
 			purple_account_set_password(account, NULL);
 
@@ -2082,7 +2137,7 @@
 		return;
 	if (!group)
 		group = "";
-	
+
 	if(msn)
 		who = g_strconcat("msn/", temp, NULL);
 	else
@@ -2123,7 +2178,7 @@
 {
 	size_t pkt_len;
 	guchar *raw_packet;
-	
+
 	/*build the raw packet and send it to the host*/
 	pkt_len = yahoo_packet_build(pkt, 0, 0, 0, &raw_packet);
 	if(write(source, raw_packet, pkt_len) != pkt_len)
@@ -2302,7 +2357,7 @@
 			yahoo_p2p_disconnect_destroy_data(data);
 		return;
 	}
-	
+
 	if(len < YAHOO_PACKET_HDRLEN)
 		return;
 
@@ -2311,13 +2366,11 @@
 		purple_debug_warning("yahoo","p2p: Got something other than YMSG packet\n");
 
 		start = memchr(buf + 1, 'Y', len - 1);
-		if(start) {
-			g_memmove(buf, start, len - (start - buf));
-			len -= start - buf;
-		} else {
-			g_free(buf);
+		if (start == NULL)
 			return;
-		}
+
+		g_memmove(buf, start, len - (start - buf));
+		len -= start - buf;
 	}
 
 	pos += 4;	/* YMSG */
@@ -2442,7 +2495,7 @@
 void yahoo_send_p2p_pkt(PurpleConnection *gc, const char *who, int val_13)
 {
 	const char *public_ip;
-	guint32 temp[4];	
+	guint32 temp[4];
 	guint32 ip;
 	char temp_str[100];
 	gchar *base64_ip = NULL;
@@ -2463,7 +2516,7 @@
 	if( strcmp(purple_normalize(account, purple_account_get_username(account)), who) == 0)
 		return;
 
-	/* send packet to only those friends who arent p2p connected and to whom we havent already sent. Do not send if this condition doesn't hold good */ 
+	/* send packet to only those friends who arent p2p connected and to whom we havent already sent. Do not send if this condition doesn't hold good */
 	if( !( f && (yahoo_friend_get_p2p_status(f) == YAHOO_P2PSTATUS_NOT_CONNECTED) && (f->p2p_packet_sent == 0)) )
 		return;
 
@@ -2526,7 +2579,7 @@
 	if(error_message != NULL) {
 		purple_debug_warning("yahoo","p2p: %s\n",error_message);
 		yahoo_send_p2p_pkt(p2p_data->gc, p2p_data->host_username, 2);/* send p2p init packet with val_13=2 */
-		
+
 		yahoo_p2p_disconnect_destroy_data(p2p_data);
 		return;
 	}
@@ -2647,7 +2700,7 @@
 		p2p_data->source = -1;
 
 		/* connect to host */
-		if((purple_proxy_connect(NULL, account, host_ip, YAHOO_PAGER_PORT_P2P, yahoo_p2p_init_cb, p2p_data))==NULL) {
+		if((purple_proxy_connect(gc, account, host_ip, YAHOO_PAGER_PORT_P2P, yahoo_p2p_init_cb, p2p_data))==NULL) {
 			purple_debug_info("yahoo","p2p: Connection to %s failed\n", host_ip);
 			g_free(p2p_data->host_ip);
 			g_free(p2p_data->host_username);
@@ -2965,11 +3018,6 @@
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
 
-	if (!PURPLE_CONNECTION_IS_VALID(gc)) {
-		close(source);
-		return;
-	}
-
 	if (source < 0) {
 		gchar *tmp;
 		tmp = g_strdup_printf(_("Could not establish a connection with the server:\n%s"),
@@ -2997,11 +3045,6 @@
 	struct yahoo_data *yd;
 	struct yahoo_packet *pkt;
 
-	if (!PURPLE_CONNECTION_IS_VALID(gc)) {
-		close(source);
-		return;
-	}
-
 	if (source < 0) {
 		gchar *tmp;
 		tmp = g_strdup_printf(_("Could not establish a connection with the server:\n%s"),
@@ -3291,7 +3334,7 @@
 
 	purple_cipher_context_destroy(context);
 }
-#endif
+#endif /* TRY_WEBMESSENGER_LOGIN */
 
 static void yahoo_server_check(PurpleAccount *account)
 {
@@ -3299,7 +3342,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);
 }
 
@@ -3898,7 +3942,7 @@
 	struct yahoo_data *yd = gc->proto_data;
 
 	g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
-	
+
 	yd->url_datas = g_slist_remove(yd->url_datas, url_data);
 
 	if (error_message != NULL)
@@ -4039,7 +4083,7 @@
 		xmlnode *validate_data_root = xmlnode_from_str(webdata, -1);
 		xmlnode *validate_data_child = xmlnode_get_child(validate_data_root, "mobile_no");
 		mobile_no = (char *)xmlnode_get_attrib(validate_data_child, "msisdn");
-		
+
 		validate_data_root = xmlnode_copy(validate_data_child);
 		validate_data_child = xmlnode_get_child(validate_data_root, "status");
 		status = xmlnode_get_data(validate_data_child);
@@ -4142,7 +4186,7 @@
 	struct yahoo_p2p_data *p2p_data;
 	gboolean msn = FALSE;
 	msg2 = yahoo_string_encode(gc, msg, &utf8);
-	
+
 	if(msg2) {
 		lenb = strlen(msg2);
 		lenc = g_utf8_strlen(msg2, -1);
@@ -4172,13 +4216,11 @@
 			struct yahoo_sms_carrier_cb_data *sms_cb_data;
 			sms_cb_data = g_malloc(sizeof(struct yahoo_sms_carrier_cb_data));
 			sms_cb_data->gc = gc;
-			sms_cb_data->who = g_malloc(strlen(who));
-			sms_cb_data->what = g_malloc(strlen(what));
-			strcpy(sms_cb_data->who, who);
-			strcpy(sms_cb_data->what, what);
+			sms_cb_data->who = g_strdup(who);
+			sms_cb_data->what = g_strdup(what);
 
 			purple_conversation_write(conv, NULL, "Getting mobile carrier to send the sms", PURPLE_MESSAGE_SYSTEM, time(NULL));
-			
+
 			yahoo_get_sms_carrier(gc, sms_cb_data);
 
 			g_free(msg);
@@ -4297,7 +4339,7 @@
 	if( (p2p_data = g_hash_table_lookup(yd->peers, who)) && !msn ) {
 		yahoo_packet_hash(pkt, "sssssis", 49, "TYPING", 1, purple_connection_get_display_name(gc),
 	                  14, " ", 13, state == PURPLE_TYPING ? "1" : "0",
-	                  5, who, 11, p2p_data->session_id, 1002, "1");	/* To-do: key 15 to be sent in case of p2p */	
+	                  5, who, 11, p2p_data->session_id, 1002, "1");	/* To-do: key 15 to be sent in case of p2p */
 		yahoo_p2p_write_pkt(p2p_data->source, pkt);
 		yahoo_packet_free(pkt);
 	}
--- a/libpurple/protocols/yahoo/yahoo.h	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo.h	Sat Jun 27 17:50:49 2009 +0000
@@ -53,7 +53,7 @@
 #define YAHOOJP_XFER_HOST "filetransfer.msg.yahoo.co.jp"
 #define YAHOOJP_WEBCAM_HOST "wc.yahoo.co.jp"
 /* not sure, must test: */
-#define YAHOOJP_XFER_RELAY_HOST "relay.msg.yahoo.co.jp" 
+#define YAHOOJP_XFER_RELAY_HOST "relay.msg.yahoo.co.jp"
 #define YAHOOJP_XFER_RELAY_PORT 80
 #define YAHOOJP_ROOMLIST_URL "http://insider.msg.yahoo.co.jp/ycontent/"
 #define YAHOOJP_ROOMLIST_LOCALE "ja"
@@ -89,7 +89,7 @@
 #define YAHOOJP_CLIENT_VERSION_ID "4194239"
 #define YAHOOJP_CLIENT_VERSION "9.0.0.2152"
 
-#define YAHOO_CLIENT_USERAGENT "Mozilla/4.0 (compatible; MSIE 5.5)"
+#define YAHOO_CLIENT_USERAGENT "Mozilla/5.0"
 
 /* Index into attention types list. */
 #define YAHOO_BUZZ 0
@@ -201,7 +201,7 @@
 	GSList *url_datas;
 	GHashTable *xfer_peer_idstring_map;/* Hey, i dont know, but putting this HashTable next to friends gives a run time fault... */
 	GSList *cookies;/* contains all cookies, including _y and _t */
-	
+
 	/**
 	 * We may receive a list15 in multiple packets with no prior warning as to how many we'll be getting;
 	 * the server expects us to keep track of the group for which it is sending us contact names.
--- a/libpurple/protocols/yahoo/yahoo_aliases.h	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_aliases.h	Sat Jun 27 17:50:49 2009 +0000
@@ -1,38 +1,38 @@
-/*
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- *
- */
-
-
-#include "internal.h"
-
-#include "account.h"
-#include "accountopt.h"
-#include "blist.h"
-#include "debug.h"
-#include "util.h"
-#include "version.h"
-#include "yahoo.h"
-#include "yahoo_packet.h"
-
-void yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias);
-void yahoo_fetch_aliases(PurpleConnection *gc);
-
+/*
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ *
+ */
+
+
+#include "internal.h"
+
+#include "account.h"
+#include "accountopt.h"
+#include "blist.h"
+#include "debug.h"
+#include "util.h"
+#include "version.h"
+#include "yahoo.h"
+#include "yahoo_packet.h"
+
+void yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias);
+void yahoo_fetch_aliases(PurpleConnection *gc);
+
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.c	Sat Jun 27 17:50:49 2009 +0000
@@ -320,7 +320,7 @@
 
 	if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) {
 		if (yd->jp) {
-			if (purple_proxy_connect(NULL, account, purple_account_get_string(account, "xferjp_host",  YAHOOJP_XFER_HOST),
+			if (purple_proxy_connect(gc, account, purple_account_get_string(account, "xferjp_host",  YAHOOJP_XFER_HOST),
 			                       purple_account_get_int(account, "xfer_port", YAHOO_XFER_PORT),
 			                       yahoo_sendfile_connected, xfer) == NULL)
 			{
@@ -329,7 +329,7 @@
 				purple_xfer_cancel_remote(xfer);
 			}
 		} else {
-			if (purple_proxy_connect(NULL, account, purple_account_get_string(account, "xfer_host",  YAHOO_XFER_HOST),
+			if (purple_proxy_connect(gc, account, purple_account_get_string(account, "xfer_host",  YAHOO_XFER_HOST),
 			                       purple_account_get_int(account, "xfer_port", YAHOO_XFER_PORT),
 			                       yahoo_sendfile_connected, xfer) == NULL)
 			{
@@ -340,7 +340,7 @@
 		}
 	} else {
 		xfer->fd = -1;
-		if (purple_proxy_connect(NULL, account, xfer_data->host, xfer_data->port,
+		if (purple_proxy_connect(gc, account, xfer_data->host, xfer_data->port,
 		                              yahoo_receivefile_connected, xfer) == NULL) {
 			purple_notify_error(gc, NULL, _("File Transfer Failed"),
 			             _("Unable to establish file descriptor."));
@@ -380,12 +380,12 @@
 			28,  xfer->size,
 			301, 268,
 			303, 268);
-		g_free(filename); 
+		g_free(filename);
 	} else {
 		if(xfer_data->firstoflist == TRUE) {
 			pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANS_15,
 				YAHOO_STATUS_AVAILABLE, yd->session_id);
-	
+
 			yahoo_packet_hash(pkt, "sssi",
 				1, purple_normalize(account, purple_account_get_username(account)),
 				5, xfer->who,
@@ -394,7 +394,7 @@
 		} else {
 			pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANS_ACC_15,
 				YAHOO_STATUS_AVAILABLE, yd->session_id);
-	
+
 			yahoo_packet_hash(pkt, "sssi",
 				1, purple_normalize(account, purple_account_get_username(account)),
 				5, xfer->who,
@@ -623,7 +623,7 @@
 	else if (written <= 0)
 		purple_debug_info("yahoo", "p2p filetransfer: Unable to write HTTP OK");
 
-	/* close connection */	
+	/* close connection */
 	close(xfer->fd);
 	xfer->fd = -1;
 	g_free(tx);
@@ -644,14 +644,14 @@
 		/* Send HTTP OK in case of p2p transfer, when we act as server */
 		if((xfer_data->xfer_url != NULL) && (xfer_old->fd >=0) && (purple_xfer_get_status(xfer_old) == PURPLE_XFER_STATUS_DONE))
 			yahoo_p2p_ft_server_send_OK(xfer_old);
-		
+
 		/* removing top of filename & size list completely */
 		g_free( xfer_data->filename_list->data );
 		g_free( xfer_data->size_list->data );
-		   
+
 		xfer_data->filename_list->data = NULL;
 		xfer_data->size_list->data = NULL;
-		   
+
 		xfer_data->filename_list = g_slist_delete_link(xfer_data->filename_list, xfer_data->filename_list);
 		xfer_data->size_list = g_slist_delete_link(xfer_data->size_list, xfer_data->size_list);
 
@@ -697,16 +697,16 @@
 			/* Build the file transfer handle. */
 			xfer = purple_xfer_new(gc->account, PURPLE_XFER_RECEIVE, xfer_old->who);
 
-			
+
 			if (xfer) {
 				/* Set the info about the incoming file. */
 				char *utf8_filename = yahoo_string_decode(gc, filename, TRUE);
 				purple_xfer_set_filename(xfer, utf8_filename);
 				g_free(utf8_filename);
 				purple_xfer_set_size(xfer, filesize);
-		
+
 				xfer->data = xfer_data;
-	
+
 				/* Setup our I/O op functions */
 				purple_xfer_set_init_fnc(xfer,        yahoo_xfer_init_15);
 				purple_xfer_set_start_fnc(xfer,       yahoo_xfer_start);
@@ -1003,7 +1003,7 @@
 		purple_xfer_cancel_remote(xfer);
 		return;
 	}
-	
+
 	/* Discard the length... */
 	hosts = g_slist_remove(hosts, hosts->data);
 	if(!hosts)
@@ -1012,7 +1012,7 @@
 		purple_xfer_cancel_remote(xfer);
 		return;
 	}
-	
+
 	/* TODO:actually, u must try with addr no.1 , if its not working addr no.2 ..... */
 	addr = hosts->data;
 	actaddr = addr->sin_addr.s_addr;
@@ -1113,14 +1113,15 @@
 	while((did = read(source, buf, 998)) > 0)
 	{
 		xd->txbuflen += did;
-		buf[did] = '\0';  
+		buf[did] = '\0';
 		t = xd->txbuf;
 		xd->txbuf = g_strconcat(t,buf,NULL);
 		g_free(t);
 	}
 	g_free(buf);
 
-	if (did < 0 && errno == EAGAIN) return;
+	if (did < 0 && errno == EAGAIN)
+		return;
 	else if (did < 0) {
 		purple_debug_error("yahoo", "Unable to write in order to start ft errno = %d\n", errno);
 		purple_xfer_cancel_remote(xfer);
@@ -1136,7 +1137,7 @@
 		close(source);/* Is this required? */
 		g_free(xd->txbuf);
 		xd->txbuf = NULL;
-		if (purple_proxy_connect(NULL, account, xd->host, xd->port, yahoo_xfer_connected_15, xfer) == NULL)
+		if (purple_proxy_connect(gc, account, xd->host, xd->port, yahoo_xfer_connected_15, xfer) == NULL)
 		{
 			purple_notify_error(gc, NULL, _("File Transfer Failed"),
 				_("Unable to establish file descriptor."));
@@ -1276,7 +1277,7 @@
 				}
 		}
 		else if(purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE && xd->status_15 == STARTED)
-		{	
+		{
 			if(xd->info_val_249 == 1)
 				{
 				/* receiving file via p2p, connected as client */
@@ -1592,7 +1593,7 @@
 	GSList *filename_list = NULL;
 	GSList *size_list = NULL;
 	int nooffiles = 0;
-	
+
 	yd = gc->proto_data;
 
 	for (l = pkt->hash; l; l = l->next) {
@@ -1617,7 +1618,7 @@
 			break;
 		case 222:
 			val_222 = atol(pair->value);
-			/* 1=send, 2=cancel, 3=accept, 4=reject */ 
+			/* 1=send, 2=cancel, 3=accept, 4=reject */
 			break;
 
 		/* check for p2p and imviron .... not sure it comes by this service packet. Since it was bundled with filexfer in old ymsg version, still keeping it. */
@@ -1655,7 +1656,7 @@
 		*	so, purple dnsquery is used... but retries, trying with next ip
 		*	address etc. is not implemented..TODO
 		*/
-		
+
 		/* To send through p2p */
 		if( g_hash_table_lookup(yd->peers, from) )	{
 			/* send p2p file transfer information */
@@ -1685,7 +1686,7 @@
 		g_hash_table_replace(yd->imvironments, g_strdup(from), g_strdup(imv));
 		return;
 	}
-	
+
 	if (pkt->service == YAHOO_SERVICE_P2PFILEXFER) {
 		if (service && (strcmp("FILEXFER", service) != 0)) {
 			purple_debug_misc("yahoo", "unhandled service 0x%02x\n", pkt->service);
@@ -1710,7 +1711,7 @@
 	xfer_data->xfer_peer_idstring = g_strdup(xfer_peer_idstring);
 	xfer_data->filename_list = filename_list;
 	xfer_data->size_list = size_list;
-	
+
 	/* Build the file transfer handle. */
 	xfer = purple_xfer_new(gc->account, PURPLE_XFER_RECEIVE, from);
 	xfer->message = NULL;
@@ -1739,7 +1740,7 @@
 		g_hash_table_insert(yd->xfer_peer_idstring_map,
 							xfer_data->xfer_peer_idstring,
 							xfer);
-		
+
 		if(nooffiles > 1) {
 			gchar* message;
 			message = g_strdup_printf(_("%s is trying to send you a group of %d files.\n"), xfer->who, nooffiles);
@@ -1825,23 +1826,22 @@
 			purple_xfer_cancel_remote(xfer);
 			return;
 		}
-		
+
 		account = purple_connection_get_account(xfer_data->gc);
 
 		pkt_to_send = yahoo_packet_new(YAHOO_SERVICE_FILETRANS_ACC_15,
 			YAHOO_STATUS_AVAILABLE, yd->session_id);
-		yahoo_packet_hash(pkt_to_send, "ssssisi",
+		yahoo_packet_hash(pkt_to_send, "ssssis",
 			1, purple_normalize(account, purple_account_get_username(account)),
 			5, xfer->who,
 			265, xfer_data->xfer_peer_idstring,
 			27, xfer->filename,
 			249, xfer_data->info_val_249,
-			251, xfer_data->xfer_idstring_for_relay,
-			222, 3);
+			251, xfer_data->xfer_idstring_for_relay);
 
 		yahoo_packet_send_and_free(pkt_to_send, yd);
 
-		if (purple_proxy_connect(NULL, account, xfer_data->host, xfer_data->port,
+		if (purple_proxy_connect(gc, account, xfer_data->host, xfer_data->port,
 			yahoo_xfer_connected_15, xfer) == NULL) {
 			purple_notify_error(gc, NULL, _("File Transfer Failed"),
 				_("Unable to establish file descriptor."));
@@ -1916,12 +1916,12 @@
 	xfer_data = xfer->data;
 	if(url)
 		purple_url_parse(url, &(xfer_data->host), &(xfer_data->port), &(xfer_data->path), NULL, NULL);
-		
+
 	xfer_data->xfer_idstring_for_relay = g_strdup(xfer_idstring_for_relay);
 	xfer_data->status_15 = ACCEPTED;
 	account = purple_connection_get_account(gc);
 
-	if (purple_proxy_connect(NULL, account, xfer_data->host, xfer_data->port,
+	if (purple_proxy_connect(gc, account, xfer_data->host, xfer_data->port,
 		yahoo_xfer_connected_15, xfer) == NULL)
 	{
 		purple_notify_error(gc, NULL, _("File Transfer Failed"),_("Unable to connect"));
--- a/libpurple/protocols/yahoo/yahoo_friend.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_friend.c	Sat Jun 27 17:50:49 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_packet.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_packet.c	Sat Jun 27 17:50:49 2009 +0000
@@ -195,7 +195,7 @@
 						   "Key: %d  \tValue: %s\n", pair->key, esc);
 				g_free(esc);
 			}
-#endif
+#endif /* DEBUG */
 		} else {
 			g_free(pair);
 		}
@@ -282,7 +282,7 @@
 	}
 
 	purple_debug(PURPLE_DEBUG_MISC, NULL, "\n");
-#endif
+#endif /* YAHOO_DEBUG */
 }
 
 static void
@@ -329,7 +329,7 @@
 	if (wm)
 		pos += yahoo_put16(data + pos, YAHOO_WEBMESSENGER_PROTO_VER);
 	else if (jp)
-		pos += yahoo_put16(data + pos, YAHOO_PROTO_VER_JAPAN);		
+		pos += yahoo_put16(data + pos, YAHOO_PROTO_VER_JAPAN);
 	else
 		pos += yahoo_put16(data + pos, YAHOO_PROTO_VER);
 	pos += yahoo_put16(data + pos, 0x0000);
--- a/libpurple/protocols/yahoo/yahoo_packet.h	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_packet.h	Sat Jun 27 17:50:49 2009 +0000
@@ -107,6 +107,7 @@
 	YAHOO_SERVICE_CHGRP_15 = 0xe7,
 	YAHOO_SERVICE_STATUS_15 = 0xf0,
 	YAHOO_SERVICE_LIST_15 = 0xf1,
+	YAHOO_SERVICE_MESSAGE_ACK = 0xfb,
 	YAHOO_SERVICE_WEBLOGIN = 0x0226,
 	YAHOO_SERVICE_SMS_MSG = 0x02ea
 	/* YAHOO_SERVICE_DISCONNECT = 0x07d1 Server forces us to disconnect. Is sent with TCP FIN flag set */
--- a/libpurple/protocols/yahoo/yahoo_picture.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_picture.c	Sat Jun 27 17:50:49 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;
@@ -126,7 +129,7 @@
 		gboolean use_whole_url = yahoo_account_use_http_proxy(gc);
 
 		/* FIXME: Cleanup this strtol() stuff if possible. */
-		if (b && (locksum = purple_buddy_icons_get_checksum_for_user(b)) != NULL && 
+		if (b && (locksum = purple_buddy_icons_get_checksum_for_user(b)) != NULL &&
 				(checksum == strtol(locksum, NULL, 10)))
 			return;
 
@@ -136,7 +139,7 @@
 		data->checksum = checksum;
 		/* TODO: Does this need to be MSIE 5.0? */
 		url_data = purple_util_fetch_url(url, use_whole_url,
-				"Mozilla/4.0 (compatible; MSIE 5.0)", FALSE,
+				"Mozilla/4.0 (compatible; MSIE 5.5)", FALSE,
 				yahoo_fetch_picture_cb, data);
 		if (url_data != NULL) {
 			yd = gc->proto_data;
@@ -312,8 +315,8 @@
 	}
 
 	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YAHOO_STATUS_AVAILABLE, 0);
-	yahoo_packet_hash(pkt, "sssssi", 1, purple_connection_get_display_name(gc),
-	                  4, purple_connection_get_display_name(gc), 5, who,
+	yahoo_packet_hash(pkt, "ssssi", 1, purple_connection_get_display_name(gc),
+	                  5, who,
 	                  13, "2", 20, yd->picture_url, 192, yd->picture_checksum);
 	yahoo_packet_send_and_free(pkt, yd);
 }
@@ -324,7 +327,7 @@
 	struct yahoo_packet *pkt;
 
 	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YAHOO_STATUS_AVAILABLE, 0);
-	yahoo_packet_hash_str(pkt, 4, purple_connection_get_display_name(gc)); /* me */
+	yahoo_packet_hash_str(pkt, 1, purple_connection_get_display_name(gc)); /* me */
 	yahoo_packet_hash_str(pkt, 5, who); /* the other guy */
 	yahoo_packet_hash_str(pkt, 13, "1"); /* 1 = request, 2 = reply */
 	yahoo_packet_send_and_free(pkt, yd);
@@ -506,7 +509,7 @@
 		"Content-Length: %" G_GSIZE_FORMAT "\r\n"
 		"Cache-Control: no-cache\r\n\r\n",
 		use_whole_url ? "http://" : "", use_whole_url ? tmp : "",
-		yd->cookie_t, yd->cookie_y, 
+		yd->cookie_t, yd->cookie_y,
 		tmp,
 		pkt_buf_len + 4 + d->str->len);
 	g_free(tmp);
@@ -573,7 +576,7 @@
 	purple_debug_misc("yahoo", "Calculated buddy icon checksum: %d\n", checksum);
 
 	return checksum;
-} 
+}
 
 void yahoo_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img)
 {
--- a/libpurple/protocols/yahoo/yahoo_profile.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_profile.c	Sat Jun 27 17:50:49 2009 +0000
@@ -29,7 +29,7 @@
 #include "util.h"
 #if PHOTO_SUPPORT
 #include "imgstore.h"
-#endif
+#endif /* PHOTO_SUPPORT */
 
 #include "yahoo.h"
 #include "yahoo_friend.h"
@@ -40,11 +40,11 @@
 } YahooGetInfoData;
 
 typedef enum profile_lang_id {
-	XX, DA, DE, EL, 
-	EN, EN_GB, 
+	XX, DA, DE, EL,
+	EN, EN_GB,
 	ES_AR, ES_ES, ES_MX, ES_US,
-	FR_CA, FR_FR, 
-	IT, JA, KO, NO, PT, SV, 
+	FR_CA, FR_FR,
+	IT, JA, KO, NO, PT, SV,
 	ZH_CN, ZH_HK, ZH_TW, ZH_US, PT_BR
 } profile_lang_id_t;
 
@@ -702,7 +702,7 @@
 		const char *balias = purple_buddy_get_local_buddy_alias(b);
 		if(balias && balias[0]) {
 			char *aliastext = g_markup_escape_text(balias, -1);
-			purple_notify_user_info_add_pair(user_info, _("Alias"), aliastext); 
+			purple_notify_user_info_add_pair(user_info, _("Alias"), aliastext);
 			g_free(aliastext);
 		}
 		#if 0
@@ -777,7 +777,7 @@
 	char *stripped;
 	int stripped_len;
 	char *last_updated_utf8_string = NULL;
-#endif
+#endif /* !PHOTO_SUPPORT */
 	const char *last_updated_string = NULL;
 	char *url_buffer;
 	GString *s;
@@ -899,7 +899,7 @@
 
 #if PHOTO_SUPPORT
 	photo_url_text = yahoo_get_photo_url(url_text, info_data->name);
-#endif
+#endif /* PHOTO_SUPPORT */
 
 	url_buffer = g_strdup(url_text);
 
@@ -1048,7 +1048,7 @@
 			purple_debug_info("yahoo", "%s is %" G_GSIZE_FORMAT
 					" bytes\n", photo_url_text, len);
 			id = purple_imgstore_add_with_id(g_memdup(url_text, len), len, NULL);
-			
+
 			tmp = g_strdup_printf("<img id=\"%d\"><br>", id);
 			purple_notify_user_info_add_pair(user_info, NULL, tmp);
 			g_free(tmp);
@@ -1259,7 +1259,7 @@
 	g_free(info2_data);
 	if (id != -1)
 		purple_imgstore_unref_by_id(id);
-#endif
+#endif /* PHOTO_SUPPORT */
 }
 
 void yahoo_get_info(PurpleConnection *gc, const char *name)
--- a/libpurple/protocols/yahoo/yahoochat.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoochat.c	Sat Jun 27 17:50:49 2009 +0000
@@ -30,7 +30,7 @@
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
-#endif
+#endif /* HAVE_CONFIG_H */
 
 #include "debug.h"
 #include "privacy.h"
@@ -1519,7 +1519,7 @@
 
 	purple_roomlist_set_fields(rl, fields);
 
-	if (purple_proxy_connect(NULL, account, yrl->host, 80,
+	if (purple_proxy_connect(gc, account, yrl->host, 80,
 	                       yahoo_roomlist_got_connected, yrl) == NULL)
 	{
 		purple_notify_error(gc, NULL, _("Connection problem"), _("Unable to fetch room list."));
@@ -1588,8 +1588,9 @@
 	yrl->ucat = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_CATEGORY, _("User Rooms"), yrl->cat);
 	purple_roomlist_room_add(list, yrl->ucat);
 
-	if (purple_proxy_connect(NULL, list->account, yrl->host, 80,
-	                       yahoo_roomlist_got_connected, yrl) == NULL)
+	if (purple_proxy_connect(purple_account_get_connection(list->account),
+			list->account, yrl->host, 80,
+			yahoo_roomlist_got_connected, yrl) == NULL)
 	{
 		purple_notify_error(purple_account_get_connection(list->account),
 		                  NULL, _("Connection problem"), _("Unable to fetch room list."));
--- a/libpurple/protocols/yahoo/ycht.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/protocols/yahoo/ycht.c	Sat Jun 27 17:50:49 2009 +0000
@@ -225,7 +225,7 @@
 	}
 
 	purple_debug(PURPLE_DEBUG_MISC, NULL, "\n");
-#endif
+#endif /* YAHOO_YCHT_DEBUG */
 }
 
 static YchtPkt *ycht_packet_new(guint version, guint service, int status)
@@ -578,7 +578,7 @@
 
 	yd->ycht = ycht;
 
-	if (purple_proxy_connect(NULL, account,
+	if (purple_proxy_connect(gc, account,
 	                       purple_account_get_string(account, "ycht-server",  YAHOO_YCHT_HOST),
 	                       purple_account_get_int(account, "ycht-port", YAHOO_YCHT_PORT),
 	                       ycht_got_connected, ycht) == NULL)
--- a/libpurple/util.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/util.c	Sat Jun 27 17:50:49 2009 +0000
@@ -3898,7 +3898,7 @@
 	url_fetch_recv_cb(data, -1, cond);
 }
 
-/*
+/**
  * This function is called when the socket is available to be written
  * to.
  *
--- a/libpurple/xmlnode.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/xmlnode.c	Sat Jun 27 17:50:49 2009 +0000
@@ -382,7 +382,7 @@
 }
 
 char *
-xmlnode_get_data(xmlnode *node)
+xmlnode_get_data(const xmlnode *node)
 {
 	GString *str = NULL;
 	xmlnode *c;
@@ -405,7 +405,7 @@
 }
 
 char *
-xmlnode_get_data_unescaped(xmlnode *node)
+xmlnode_get_data_unescaped(const xmlnode *node)
 {
 	char *escaped = xmlnode_get_data(node);
 
--- a/libpurple/xmlnode.h	Sat Jun 27 17:50:35 2009 +0000
+++ b/libpurple/xmlnode.h	Sat Jun 27 17:50:49 2009 +0000
@@ -136,7 +136,7 @@
  * @return The data from the node or NULL. This data is in raw escaped format.
  *         You must g_free this string when finished using it.
  */
-char *xmlnode_get_data(xmlnode *node);
+char *xmlnode_get_data(const xmlnode *node);
 
 /**
  * Gets unescaped data from a node.
@@ -146,7 +146,7 @@
  * @return The data from the node, in unescaped form.   You must g_free
  *         this string when finished using it.
  */
-char *xmlnode_get_data_unescaped(xmlnode *node);
+char *xmlnode_get_data_unescaped(const xmlnode *node);
 
 /**
  * Sets an attribute for a node.
--- a/pidgin/eggtrayicon.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/eggtrayicon.c	Sat Jun 27 17:50:49 2009 +0000
@@ -400,9 +400,35 @@
 static gboolean
 transparent_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
 {
-	gdk_window_clear_area (widget->window, event->area.x, event->area.y,
-	                      event->area.width, event->area.height);
-	return FALSE;
+  GtkWidget *focus_child = NULL;
+  gint border_width, x, y, width, height;
+  gboolean retval = FALSE;
+
+  gdk_window_clear_area (widget->window, event->area.x, event->area.y,
+                         event->area.width, event->area.height);
+
+  if (GTK_WIDGET_CLASS (parent_class)->expose_event)
+    retval = GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
+
+  if (GTK_CONTAINER (widget)->focus_child)
+    focus_child = GTK_CONTAINER (GTK_CONTAINER (widget)->focus_child)->focus_child;
+  if (focus_child && GTK_WIDGET_HAS_FOCUS (focus_child))
+    {
+      border_width = GTK_CONTAINER (widget)->border_width;
+
+      x = widget->allocation.x + border_width;
+      y = widget->allocation.y + border_width;
+
+      width  = widget->allocation.width  - 2 * border_width;
+      height = widget->allocation.height - 2 * border_width;
+
+      gtk_paint_focus (widget->style, widget->window,
+                       GTK_WIDGET_STATE (widget),
+                       &event->area, widget, "tray_icon",
+                       x, y, width, height);
+    }
+
+  return retval;
 }
 
 static void
--- a/pidgin/gtkdocklet-x11.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/gtkdocklet-x11.c	Sat Jun 27 17:50:49 2009 +0000
@@ -32,6 +32,7 @@
 
 #include "eggtrayicon.h"
 #include "gtkdocklet.h"
+#include <gdk/gdkkeysyms.h>
 
 #define SHORT_EMBED_TIMEOUT 5000
 #define LONG_EMBED_TIMEOUT 15000
@@ -89,6 +90,33 @@
 	return TRUE;
 }
 
+static gboolean
+docklet_x11_pressed_cb(GtkWidget *button, GdkEventKey *event)
+{
+	guint state, keyval;
+
+	state = event->state & gtk_accelerator_get_default_mod_mask();
+	keyval = event->keyval;
+	if (state == 0 &&
+	    (keyval == GDK_Return ||
+	     keyval == GDK_KP_Enter ||
+	     keyval == GDK_ISO_Enter ||
+	     keyval == GDK_space ||
+	     keyval == GDK_KP_Space))
+	{
+		pidgin_docklet_clicked(1);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void
+docklet_x11_popup_cb(GtkWidget *button)
+{
+	pidgin_docklet_clicked(3);
+}
+
 static void
 docklet_x11_update_icon(PurpleStatusPrimitive status, gboolean connecting, gboolean pending)
 {
@@ -259,11 +287,14 @@
 	docklet = egg_tray_icon_new(PIDGIN_NAME);
 	box = gtk_event_box_new();
 	image = gtk_image_new();
+	GTK_WIDGET_SET_FLAGS (image, GTK_CAN_FOCUS);
 
 	g_signal_connect(G_OBJECT(docklet), "embedded", G_CALLBACK(docklet_x11_embedded_cb), NULL);
 	g_signal_connect(G_OBJECT(docklet), "destroy", G_CALLBACK(docklet_x11_destroyed_cb), NULL);
 	g_signal_connect(G_OBJECT(docklet), "size-allocate", G_CALLBACK(docklet_x11_resize_icon), NULL);
 	g_signal_connect(G_OBJECT(box), "button-press-event", G_CALLBACK(docklet_x11_clicked_cb), NULL);
+	g_signal_connect(G_OBJECT(box), "key-press-event", G_CALLBACK(docklet_x11_pressed_cb), NULL);
+	g_signal_connect(G_OBJECT(box), "popup-menu", G_CALLBACK(docklet_x11_popup_cb), NULL);
 	gtk_container_add(GTK_CONTAINER(box), image);
 	gtk_container_add(GTK_CONTAINER(docklet), box);
 
--- a/pidgin/gtkft.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/gtkft.c	Sat Jun 27 17:50:49 2009 +0000
@@ -705,12 +705,12 @@
 	};
 
 	/* Setup the initial table */
-	dialog->table = table = gtk_table_new(9, 2, FALSE);
+	dialog->table = table = gtk_table_new(G_N_ELEMENTS(labels) + 1, 2, FALSE);
 	gtk_table_set_row_spacings(GTK_TABLE(table), PIDGIN_HIG_BOX_SPACE);
 	gtk_table_set_col_spacings(GTK_TABLE(table), PIDGIN_HIG_BOX_SPACE);
 
 	/* Setup the labels */
-	for (i = 0; i < sizeof(labels) / sizeof(*labels); i++) {
+	for (i = 0; i < G_N_ELEMENTS(labels); i++) {
 		GtkWidget *label;
 		char buf[256];
 
@@ -734,7 +734,9 @@
 
 	/* Setup the progress bar */
 	dialog->progress = gtk_progress_bar_new();
-	gtk_table_attach(GTK_TABLE(table), dialog->progress, 0, 2, 8, 9,
+	gtk_table_attach(GTK_TABLE(table), dialog->progress,
+					 0, 2,
+					 G_N_ELEMENTS(labels), G_N_ELEMENTS(labels) + 1,
 					 GTK_FILL, GTK_FILL, 0, 0);
 	gtk_widget_show(dialog->progress);
 
--- a/pidgin/gtkicon-theme-loader.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/gtkicon-theme-loader.c	Sat Jun 27 17:50:49 2009 +0000
@@ -41,7 +41,7 @@
 	filename_full = g_build_filename(dir, "theme.xml", NULL);
 
 	if (g_file_test(filename_full, G_FILE_TEST_IS_REGULAR))
-		root_node = xmlnode_from_file(dir, "theme.xml", "sound themes", "sound-theme-loader");
+		root_node = xmlnode_from_file(dir, "theme.xml", "icon themes", "icon-theme-loader");
 
 	g_free(filename_full);
 	g_return_val_if_fail(root_node != NULL, NULL);
--- a/pidgin/gtkimhtml.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/gtkimhtml.c	Sat Jun 27 17:50:49 2009 +0000
@@ -2996,10 +2996,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/gtknotify.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/gtknotify.c	Sat Jun 27 17:50:49 2009 +0000
@@ -557,7 +557,10 @@
 						gtk_tree_store_remove(treemodel, &iter);
 						advanced = (iter.stamp == 0) ? FALSE : TRUE;
 #endif
-						purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
+						if (data->purple_has_handle)
+							purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
+						else
+							pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
 						/* We're completely done if we've processed all entries */
 						if (!advanced)
 							return NULL;
@@ -612,7 +615,7 @@
 	char *notification;
 	PurpleAccount *account;
 	PidginNotifyMailData *data = NULL, *data2;
-	gboolean new_data;
+	gboolean new_data = FALSE;
 
 	/* Don't bother updating if there aren't new emails and we don't have any displayed currently */
 	if (count == 0 && mail_dialog == NULL)
@@ -660,7 +663,7 @@
 
 			/* If we don't keep track of this, will leak "data" for each of the notifications except the last */
 			data2 = pidgin_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, 0, FALSE, &new_data);
-			if (new_data) {
+			if (data2 && new_data) {
 				if (data)
 					data->purple_has_handle = FALSE;
 				data = data2;
@@ -677,7 +680,7 @@
 							   (int)count),
 							   *tos, (int)count);
 			data2 = pidgin_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, count, FALSE, &new_data);
-			if (new_data) {
+			if (data2 && new_data) {
 				if (data)
 					data->purple_has_handle = FALSE;
 				data = data2;
--- a/pidgin/gtkstatusbox.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/gtkstatusbox.c	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/plugins/Makefile.mingw	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/plugins/disco/Makefile.am	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:49 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	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/plugins/disco/gtkdisco.c	Sat Jun 27 17:50:49 2009 +0000
@@ -29,17 +29,13 @@
 #include "gtkutils.h"
 #include "pidgin.h"
 #include "request.h"
+#include "pidgintooltip.h"
 
 #include "gtkdisco.h"
 #include "xmppdisco.h"
 
 GList *dialogs = NULL;
 
-struct _menu_cb_info {
-	PidginDiscoList *list;
-	XmppDiscoService *service;
-};
-
 enum {
 	PIXBUF_COLUMN = 0,
 	NAME_COLUMN,
@@ -69,7 +65,7 @@
 	g_return_val_if_fail(list != NULL, NULL);
 
 	++list->ref;
-    purple_debug_misc("xmppdisco", "reffing list, ref count now %d\n", list->ref);
+	purple_debug_misc("xmppdisco", "reffing list, ref count now %d\n", list->ref);
 
 	return list;
 }
@@ -112,20 +108,46 @@
 	}
 }
 
+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,
-				     PidginDiscoDialog *dialog)
+                                     PidginDiscoDialog *dialog)
 {
 	dialog->account = account;
 	gtk_widget_set_sensitive(dialog->browse_button, account != NULL);
 }
 
-static void register_button_cb(GtkButton *button, PidginDiscoDialog *dialog)
+static void register_button_cb(GtkWidget *unused, PidginDiscoDialog *dialog)
 {
-	struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "disco-info");
-
-	xmpp_disco_service_register(info->service);
+	xmpp_disco_service_register(dialog->selected);
 }
 
 static void discolist_cancel_cb(PidginDiscoList *pdl, const char *server)
@@ -152,7 +174,7 @@
 	xmpp_disco_start(pdl);
 }
 
-static void browse_button_cb(GtkButton *button, PidginDiscoDialog *dialog)
+static void browse_button_cb(GtkWidget *button, PidginDiscoDialog *dialog)
 {
 	PurpleConnection *pc;
 	PidginDiscoList *pdl;
@@ -212,52 +234,91 @@
 	g_free(server);
 }
 
-static void add_room_to_blist_cb(GtkButton *button, PidginDiscoDialog *dialog)
+static void add_to_blist_cb(GtkWidget *unused, PidginDiscoDialog *dialog)
 {
-	struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "disco-info");
+	XmppDiscoService *service = dialog->selected;
 	PurpleAccount *account;
-	const char *name;
+	const char *jid;
+
+	g_return_if_fail(service != NULL);
+
+	account = purple_connection_get_account(service->list->pc);
+	jid = service->jid;
+
+	if (service->type == XMPP_DISCO_SERVICE_TYPE_CHAT)
+		purple_blist_request_add_chat(account, NULL, NULL, jid);
+	else
+		purple_blist_request_add_buddy(account, jid, NULL, NULL);
+}
 
-	g_return_if_fail(info != NULL);
+static gboolean
+service_click_cb(GtkTreeView *tree, GdkEventButton *event, gpointer user_data)
+{
+	PidginDiscoList *pdl;
+	XmppDiscoService *service;
+	GtkWidget *menu;
+
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	GValue val;
+
+	if (event->button != 3 || event->type != GDK_BUTTON_PRESS)
+		return FALSE;
 
-	account = purple_connection_get_account(info->list->pc);
-	name = info->service->name;
+	pdl = user_data;
+
+	/* Figure out what was clicked */
+	if (!gtk_tree_view_get_path_at_pos(tree, event->x, event->y, &path,
+		                               NULL, NULL, NULL))
+		return FALSE;
+	gtk_tree_model_get_iter(GTK_TREE_MODEL(pdl->model), &iter, path);
+	gtk_tree_path_free(path);
+	val.g_type = 0;
+	gtk_tree_model_get_value(GTK_TREE_MODEL(pdl->model), &iter, SERVICE_COLUMN,
+	                         &val);
+	service = g_value_get_pointer(&val);
+
+	if (!service)
+		return FALSE;
 
-	if (info->service->type == XMPP_DISCO_SERVICE_TYPE_CHAT)
-		purple_blist_request_add_chat(account, NULL, NULL, name);
-	else
-		purple_blist_request_add_buddy(account, name, NULL, NULL);
+	menu = gtk_menu_new();
+
+	if (service->flags & XMPP_DISCO_ADD)
+		pidgin_new_item_from_stock(menu, _("Add to Buddy List"), GTK_STOCK_ADD,
+		                           G_CALLBACK(add_to_blist_cb), pdl->dialog,
+		                           0, 0, NULL);
+
+	if (service->flags & XMPP_DISCO_REGISTER) {
+		GtkWidget *item = pidgin_new_item(menu, _("Register"));
+		g_signal_connect(G_OBJECT(item), "activate",
+		                 G_CALLBACK(register_button_cb), pdl->dialog);
+	}
+
+	gtk_widget_show_all(menu);
+	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button,
+	               event->time);
+	return FALSE;
 }
 
 static void
 selection_changed_cb(GtkTreeSelection *selection, PidginDiscoList *pdl)
 {
-	XmppDiscoService *service;
 	GtkTreeIter iter;
 	GValue val;
-	static struct _menu_cb_info *info;
 	PidginDiscoDialog *dialog = pdl->dialog;
 
 	if (gtk_tree_selection_get_selected(selection, NULL, &iter)) {
 		val.g_type = 0;
 		gtk_tree_model_get_value(GTK_TREE_MODEL(pdl->model), &iter, SERVICE_COLUMN, &val);
-		service = g_value_get_pointer(&val);
-		if (!service) {
+		dialog->selected = g_value_get_pointer(&val);
+		if (!dialog->selected) {
 			gtk_widget_set_sensitive(dialog->add_button, FALSE);
 			gtk_widget_set_sensitive(dialog->register_button, FALSE);
 			return;
 		}
 
-		info = g_new0(struct _menu_cb_info, 1);
-		info->list = dialog->discolist;
-		info->service = service;
-
-		g_object_set_data_full(G_OBJECT(dialog->add_button), "disco-info",
-		                       info, g_free);
-		g_object_set_data(G_OBJECT(dialog->register_button), "disco-info", info);
-
-		gtk_widget_set_sensitive(dialog->add_button, service->flags & XMPP_DISCO_ADD);
-		gtk_widget_set_sensitive(dialog->register_button, service->flags & XMPP_DISCO_REGISTER);
+		gtk_widget_set_sensitive(dialog->add_button, dialog->selected->flags & XMPP_DISCO_ADD);
+		gtk_widget_set_sensitive(dialog->register_button, dialog->selected->flags & XMPP_DISCO_REGISTER);
 	} else {
 		gtk_widget_set_sensitive(dialog->add_button, FALSE);
 		gtk_widget_set_sensitive(dialog->register_button, FALSE);
@@ -282,6 +343,36 @@
 }
 
 static void
+row_activated_cb(GtkTreeView       *tree_view,
+                 GtkTreePath       *path,
+                 GtkTreeViewColumn *column,
+                 gpointer           user_data)
+{
+	PidginDiscoList *pdl = user_data;
+	GtkTreeIter iter;
+	XmppDiscoService *service;
+	GValue val;
+
+	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(pdl->model), &iter, path))
+		return;
+
+	val.g_type = 0;
+	gtk_tree_model_get_value(GTK_TREE_MODEL(pdl->model), &iter, SERVICE_COLUMN,
+	                         &val);
+	service = g_value_get_pointer(&val);
+
+	if (service->flags & XMPP_DISCO_BROWSE)
+		if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(pdl->tree), path))
+			gtk_tree_view_collapse_row(GTK_TREE_VIEW(pdl->tree), path);
+		else
+			gtk_tree_view_expand_row(GTK_TREE_VIEW(pdl->tree), path, FALSE);
+	else if (service->flags & XMPP_DISCO_REGISTER)
+		register_button_cb(NULL, pdl->dialog);
+	else if (service->flags & XMPP_DISCO_ADD)
+		add_to_blist_cb(NULL, pdl->dialog);
+}
+
+static void
 destroy_win_cb(GtkWidget *window, gpointer d)
 {
 	PidginDiscoDialog *dialog = d;
@@ -317,6 +408,94 @@
 	return purple_strequal(purple_account_get_protocol_id(account), XMPP_PLUGIN_ID);
 }
 
+static gboolean
+disco_paint_tooltip(GtkWidget *tipwindow, gpointer data)
+{
+	PangoLayout *layout = g_object_get_data(G_OBJECT(tipwindow), "tooltip-plugin");
+	gtk_paint_layout(tipwindow->style, tipwindow->window, GTK_STATE_NORMAL, FALSE,
+			NULL, tipwindow, "tooltip",
+			6, 6, layout);
+	return TRUE;
+}
+
+static gboolean
+disco_create_tooltip(GtkWidget *tipwindow, GtkTreePath *path,
+		gpointer data, int *w, int *h)
+{
+	PidginDiscoList *pdl = data;
+	GtkTreeIter iter;
+	PangoLayout *layout;
+	int width, height;
+	XmppDiscoService *service;
+	GValue val;
+	const char *type = NULL;
+	char *markup, *jid, *name, *desc = NULL;
+
+	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(pdl->model), &iter, path))
+		return FALSE;
+
+	val.g_type = 0;
+	gtk_tree_model_get_value(GTK_TREE_MODEL(pdl->model), &iter, SERVICE_COLUMN,
+	                         &val);
+	service = g_value_get_pointer(&val);
+
+	switch (service->type) {
+		case XMPP_DISCO_SERVICE_TYPE_UNSET:
+			type = _("Unknown");
+			break;
+
+		case XMPP_DISCO_SERVICE_TYPE_GATEWAY:
+			type = _("Gateway");
+			break;
+
+		case XMPP_DISCO_SERVICE_TYPE_DIRECTORY:
+			type = _("Directory");
+			break;
+
+		case XMPP_DISCO_SERVICE_TYPE_CHAT:
+			type = _("Chat");
+			break;
+
+		case XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION:
+			type = _("PubSub Collection");
+			break;
+
+		case XMPP_DISCO_SERVICE_TYPE_PUBSUB_LEAF:
+			type = _("PubSub Leaf");
+			break;
+
+		case XMPP_DISCO_SERVICE_TYPE_OTHER:
+			type = _("Other");
+			break;
+	}
+
+	markup = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>\n<b>%s:</b> %s%s%s",
+	                         name = g_markup_escape_text(service->name, -1),
+	                         type,
+	                         jid = g_markup_escape_text(service->jid, -1),
+	                         service->description ? _("\n<b>Description:</b> ") : "",
+	                         service->description ? desc = g_markup_escape_text(service->description, -1) : "");
+
+	layout = gtk_widget_create_pango_layout(tipwindow, NULL);
+	pango_layout_set_markup(layout, markup, -1);
+	pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
+	pango_layout_set_width(layout, 500000);
+	pango_layout_get_size(layout, &width, &height);
+	g_object_set_data_full(G_OBJECT(tipwindow), "tooltip-plugin", layout, g_object_unref);
+
+	if (w)
+		*w = PANGO_PIXELS(width) + 12;
+	if (h)
+		*h = PANGO_PIXELS(height) + 12;
+
+	g_free(markup);
+	g_free(jid);
+	g_free(name);
+	g_free(desc);
+
+	return TRUE;
+}
+
 static void pidgin_disco_create_tree(PidginDiscoList *pdl)
 {
 	GtkCellRenderer *text_renderer, *pixbuf_renderer;
@@ -348,7 +527,7 @@
 	column = gtk_tree_view_column_new();
 	gtk_tree_view_column_set_title(column, _("Name"));
 
-	gtk_tree_view_column_pack_start(column,  pixbuf_renderer, FALSE);
+	gtk_tree_view_column_pack_start(column, pixbuf_renderer, FALSE);
 	gtk_tree_view_column_set_attributes(column, pixbuf_renderer,
 			"pixbuf", PIXBUF_COLUMN, NULL);
 
@@ -372,7 +551,13 @@
 	gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE);
 	gtk_tree_view_append_column(GTK_TREE_VIEW(pdl->tree), column);
 
+	g_signal_connect(G_OBJECT(pdl->tree), "button-press-event", G_CALLBACK(service_click_cb), pdl);
 	g_signal_connect(G_OBJECT(pdl->tree), "row-expanded", G_CALLBACK(row_expanded_cb), pdl);
+	g_signal_connect(G_OBJECT(pdl->tree), "row-activated", G_CALLBACK(row_activated_cb), pdl);
+
+	pidgin_tooltip_setup_for_treeview(pdl->tree, pdl,
+	                                  disco_create_tooltip,
+	                                  disco_paint_tooltip);
 }
 
 void pidgin_disco_signed_off_cb(PurpleConnection *pc)
@@ -464,13 +649,15 @@
 	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
 
 	/* stop button */
-	dialog->stop_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP,
-	                 G_CALLBACK(stop_button_cb), dialog);
+	dialog->stop_button =
+		pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP,
+		                         G_CALLBACK(stop_button_cb), dialog);
 	gtk_widget_set_sensitive(dialog->stop_button, FALSE);
 
 	/* browse button */
-	dialog->browse_button = pidgin_pixbuf_button_from_stock(_("_Browse"), GTK_STOCK_REFRESH,
-	                                                    PIDGIN_BUTTON_HORIZONTAL);
+	dialog->browse_button =
+		pidgin_pixbuf_button_from_stock(_("_Browse"), GTK_STOCK_REFRESH,
+		                                PIDGIN_BUTTON_HORIZONTAL);
 	gtk_box_pack_start(GTK_BOX(bbox), dialog->browse_button, FALSE, FALSE, 0);
 	g_signal_connect(G_OBJECT(dialog->browse_button), "clicked",
 	                 G_CALLBACK(browse_button_cb), dialog);
@@ -478,22 +665,25 @@
 	gtk_widget_show(dialog->browse_button);
 
 	/* register button */
-	dialog->register_button = pidgin_dialog_add_button(GTK_DIALOG(dialog->window), _("Register"),
-	                 G_CALLBACK(register_button_cb), dialog);
+	dialog->register_button =
+		pidgin_dialog_add_button(GTK_DIALOG(dialog->window), _("Register"),
+		                         G_CALLBACK(register_button_cb), dialog);
 	gtk_widget_set_sensitive(dialog->register_button, FALSE);
 
 	/* add button */
-	dialog->add_button = pidgin_pixbuf_button_from_stock(_("_Add"), GTK_STOCK_ADD,
-	                                                    PIDGIN_BUTTON_HORIZONTAL);
+	dialog->add_button =
+		pidgin_pixbuf_button_from_stock(_("_Add"), GTK_STOCK_ADD,
+	                                    PIDGIN_BUTTON_HORIZONTAL);
 	gtk_box_pack_start(GTK_BOX(bbox), dialog->add_button, FALSE, FALSE, 0);
 	g_signal_connect(G_OBJECT(dialog->add_button), "clicked",
-	                 G_CALLBACK(add_room_to_blist_cb), dialog);
+	                 G_CALLBACK(add_to_blist_cb), dialog);
 	gtk_widget_set_sensitive(dialog->add_button, FALSE);
 	gtk_widget_show(dialog->add_button);
 
 	/* close button */
-	dialog->close_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE,
-					 G_CALLBACK(close_button_cb), dialog);
+	dialog->close_button =
+		pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE,
+		                         G_CALLBACK(close_button_cb), dialog);
 
 	/* show the dialog window and return the dialog */
 	gtk_widget_show(dialog->window);
@@ -505,7 +695,6 @@
 {
 	PidginDiscoDialog *dialog;
 	GtkTreeIter iter, parent_iter, child;
-	char *filename = NULL;
 	GdkPixbuf *pixbuf = NULL;
 	gboolean append = TRUE;
 
@@ -563,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/plugins/disco/gtkdisco.h	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/plugins/disco/gtkdisco.h	Sat Jun 27 17:50:49 2009 +0000
@@ -39,6 +39,7 @@
 	GtkWidget *register_button;
 	GtkWidget *add_button;
 	GtkWidget *close_button;
+	XmppDiscoService *selected;
 
 	PurpleAccount *account;
 	PidginDiscoList *discolist;
--- a/pidgin/plugins/disco/xmppdisco.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/plugins/disco/xmppdisco.c	Sat Jun 27 17:50:49 2009 +0000
@@ -56,8 +56,8 @@
 static gboolean iq_listening = FALSE;
 
 typedef void (*XmppIqCallback)(PurpleConnection *pc, const char *type,
-                              const char *id, const char *from, xmlnode *iq,
-							  gpointer data);
+                               const char *id, const char *from, xmlnode *iq,
+                               gpointer data);
 
 struct xmpp_iq_cb_data
 {
@@ -149,7 +149,7 @@
 	g_hash_table_insert(iq_callbacks, id, cbdata);
 
 	if (!iq_listening) {
-    	PurplePlugin *prpl = purple_plugins_find_with_id(XMPP_PLUGIN_ID);
+		PurplePlugin *prpl = purple_plugins_find_with_id(XMPP_PLUGIN_ID);
 		iq_listening = TRUE;
 		purple_signal_connect(prpl, "jabber-receiving-iq", my_plugin,
 		                      PURPLE_CALLBACK(xmpp_iq_received), NULL);
@@ -245,30 +245,30 @@
 }
 
 static const struct {
-    const char *from;
-    const char *to;
+	const char *from;
+	const char *to;
 } disco_type_mappings[] = {
-    { "gadu-gadu", "gadu-gadu" }, /* the prpl is prpl-gg, but list_icon returns "gadu-gadu" */
-    { "sametime",  "meanwhile" },
-    { "myspaceim", "myspace" },
-    { "xmpp",      "jabber" }, /* prpl-jabber (mentioned in case the prpl is renamed so this line will match) */
-    { NULL,        NULL }
+	{ "gadu-gadu", "gadu-gadu" }, /* the prpl is prpl-gg, but list_icon returns "gadu-gadu" */
+	{ "sametime",  "meanwhile" },
+	{ "myspaceim", "myspace" },
+	{ "xmpp",      "jabber" }, /* prpl-jabber (mentioned in case the prpl is renamed so this line will match) */
+	{ NULL,        NULL }
 };
 
 static const gchar *
 disco_type_from_string(const gchar *str)
 {
-    int i = 0;
+	int i = 0;
 
-    g_return_val_if_fail(str != NULL, "");
+	g_return_val_if_fail(str != NULL, "");
 
-    for ( ; disco_type_mappings[i].from; ++i) {
-        if (!strcasecmp(str, disco_type_mappings[i].from))
-            return disco_type_mappings[i].to;
-    }
+	for ( ; disco_type_mappings[i].from; ++i) {
+		if (!strcasecmp(str, disco_type_mappings[i].from))
+			return disco_type_mappings[i].to;
+	}
 
-    /* fallback to the string itself */
-    return str;
+	/* fallback to the string itself */
+	return str;
 }
 
 static void
@@ -294,7 +294,7 @@
 		service->list = item_data->list;
 		purple_debug_info("xmppdisco", "parent for %s is %p\n", from, item_data->parent);
 		service->parent = item_data->parent;
-		service->flags = XMPP_DISCO_ADD;
+		service->flags = 0;
 		service->type = disco_service_type_from_identity(identity);
 
 		if (item_data->node) {
@@ -312,6 +312,10 @@
 		} else
 			service->name = g_strdup(from);
 
+		if (!service->node)
+			/* Only support adding JIDs, not JID+node combos */
+			service->flags |= XMPP_DISCO_ADD;
+
 		if (item_data->name) {
 			service->description = item_data->name;
 			item_data->name = NULL;
@@ -607,13 +611,13 @@
 static gboolean
 plugin_load(PurplePlugin *plugin)
 {
-    PurplePlugin *xmpp_prpl;
+	PurplePlugin *xmpp_prpl;
 
 	my_plugin = plugin;
 
-    xmpp_prpl = purple_plugins_find_with_id(XMPP_PLUGIN_ID);
-    if (NULL == xmpp_prpl)
-        return FALSE;
+	xmpp_prpl = purple_plugins_find_with_id(XMPP_PLUGIN_ID);
+	if (NULL == xmpp_prpl)
+		return FALSE;
 
 	purple_signal_connect(purple_connections_get_handle(), "signing-off",
 	                      plugin, PURPLE_CALLBACK(signed_off_cb), NULL);
--- a/pidgin/plugins/disco/xmppdisco.h	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/plugins/disco/xmppdisco.h	Sat Jun 27 17:50:49 2009 +0000
@@ -40,22 +40,22 @@
  */
 typedef enum
 {
-    XMPP_DISCO_SERVICE_TYPE_UNSET,
-    /**
-     * A registerable gateway to another protocol. An example would be
-     * XMPP legacy transports.
-     */
-    XMPP_DISCO_SERVICE_TYPE_GATEWAY,
+	XMPP_DISCO_SERVICE_TYPE_UNSET,
+	/**
+	 * A registerable gateway to another protocol. An example would be
+	 * XMPP legacy transports.
+	 */
+	XMPP_DISCO_SERVICE_TYPE_GATEWAY,
 
-    /**
-     * A directory (e.g. allows the user to search for other users).
-     */
-    XMPP_DISCO_SERVICE_TYPE_DIRECTORY,
+	/**
+	 * A directory (e.g. allows the user to search for other users).
+	 */
+	XMPP_DISCO_SERVICE_TYPE_DIRECTORY,
 
-    /**
-     * A chat (multi-user conversation).
-     */
-    XMPP_DISCO_SERVICE_TYPE_CHAT,
+	/**
+	 * A chat (multi-user conversation).
+	 */
+	XMPP_DISCO_SERVICE_TYPE_CHAT,
 
 	/**
 	 * A pubsub collection (contains nodes)
@@ -68,9 +68,9 @@
 	XMPP_DISCO_SERVICE_TYPE_PUBSUB_LEAF,
 
 	/**
-     * Something else. Do we need more categories?
-     */
-    XMPP_DISCO_SERVICE_TYPE_OTHER
+	 * Something else. Do we need more categories?
+	 */
+	XMPP_DISCO_SERVICE_TYPE_OTHER
 } XmppDiscoServiceType;
 
 /**
@@ -78,10 +78,10 @@
  */
 typedef enum
 {
-    XMPP_DISCO_NONE          = 0x0000,
-    XMPP_DISCO_ADD           = 0x0001, /**< Supports an 'add' operation */
-    XMPP_DISCO_BROWSE        = 0x0002, /**< Supports browsing */
-    XMPP_DISCO_REGISTER      = 0x0004  /**< Supports a 'register' operation */
+	XMPP_DISCO_NONE          = 0x0000,
+	XMPP_DISCO_ADD           = 0x0001, /**< Supports an 'add' operation */
+	XMPP_DISCO_BROWSE        = 0x0002, /**< Supports browsing */
+	XMPP_DISCO_REGISTER      = 0x0004  /**< Supports a 'register' operation */
 } XmppDiscoServiceFlags;
 
 struct _XmppDiscoService {
--- a/pidgin/plugins/gevolution/gevolution.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/plugins/gevolution/gevolution.c	Sat Jun 27 17:50:49 2009 +0000
@@ -39,6 +39,7 @@
 #include <libedata-book/Evolution-DataServer-Addressbook.h>
 
 #include <libedata-book/e-data-book-factory.h>
+/* TODO: bonobo is going away eventually, we'll need to find an alternative */
 #include <bonobo/bonobo-main.h>
 
 #include <glib.h>
--- a/pidgin/plugins/pidginrc.c	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/plugins/pidginrc.c	Sat Jun 27 17:50:49 2009 +0000
@@ -34,7 +34,7 @@
 	"/plugins/gtk/purplerc/color/GtkIMHtml::receive-name-color",
 	"/plugins/gtk/purplerc/color/GtkIMHtml::highlight-name-color",
 	"/plugins/gtk/purplerc/color/GtkIMHtml::action-name-color",
-	"/plugins/gtk/purplerc/color/GtkIMHtml::typing-ntofication-color"
+	"/plugins/gtk/purplerc/color/GtkIMHtml::typing-notification-color"
 };
 static const gchar *color_prefs_set[] = {
 	"/plugins/gtk/purplerc/set/color/GtkIMHtml::hyperlink-color",
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Sat Jun 27 17:50:35 2009 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Sat Jun 27 17:50:49 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"
--- a/po/ca.po	Sat Jun 27 17:50:35 2009 +0000
+++ b/po/ca.po	Sat Jun 27 17:50:49 2009 +0000
@@ -33,8 +33,8 @@
 msgstr ""
 "Project-Id-Version: Pidgin\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-06-06 22:44+0200\n"
-"PO-Revision-Date: 2009-06-06 22:51+0200\n"
+"POT-Creation-Date: 2009-06-25 21:33+0200\n"
+"PO-Revision-Date: 2009-06-25 21:44+0200\n"
 "Last-Translator: Josep Puigdemont i Casamajó <josep.puigdemont@gmail.com>\n"
 "Language-Team: Catalan <tradgnome@softcatala.net>\n"
 "MIME-Version: 1.0\n"
@@ -4254,8 +4254,9 @@
 msgid "Invalid XMPP ID. Domain must be set."
 msgstr "L'ID de l'XMPP no és vàlid. Cal especificar un domini."
 
-msgid "Malformed BOSH Connect Server"
-msgstr ""
+# FIX
+msgid "Malformed BOSH URL"
+msgstr "L'URL BOSH està malmès"
 
 #, c-format
 msgid "Registration of %s@%s successful"
@@ -4323,6 +4324,9 @@
 msgid "Change Registration"
 msgstr "Canvia el registre"
 
+msgid "Malformed BOSH Connect Server"
+msgstr "La connexió al servidor BOSH està malmesa"
+
 msgid "Error unregistering account"
 msgstr "S'ha produït un error en cancel·lar el registre"
 
@@ -4460,7 +4464,7 @@
 
 # FIX
 msgid "Malformed XMPP ID"
-msgstr "l'ID de l'XMPP està malmès"
+msgstr "L'ID de l'XMPP està malmès"
 
 msgid "Not Acceptable"
 msgstr "No és acceptable"
@@ -4767,6 +4771,9 @@
 msgid "File transfer proxies"
 msgstr "Servidors intermediari per a la transferència de fitxers"
 
+msgid "BOSH URL"
+msgstr "URL BOSH"
+
 #. this should probably be part of global smiley theme settings later on,
 #. shared with MSN
 msgid "Show Custom Smileys"
@@ -5524,32 +5531,6 @@
 msgid "The following users are missing from your addressbook"
 msgstr "Manquen aquests usuaris a la vostra llista d'amics"
 
-#, c-format
-msgid "Unable to add user on %s (%s)"
-msgstr "No s'ha pogut afegir l'usuari a %s (%s)"
-
-#, c-format
-msgid "Unable to block user on %s (%s)"
-msgstr "No s'ha pogut blocar l'usuari %s (%s)"
-
-#, c-format
-msgid "Unable to permit user on %s (%s)"
-msgstr "No s'ha pogut permetre l'usuari %s (%s)"
-
-#, c-format
-msgid "%s could not be added because your buddy list is full."
-msgstr "No s'ha pogut afegir %s perquè la llista és plena."
-
-#, c-format
-msgid "%s is not a valid passport account."
-msgstr "%s no és un compte de passaport vàlid."
-
-msgid "Service Temporarily Unavailable."
-msgstr "El servei no està disponible temporalment."
-
-msgid "Unknown error."
-msgstr "Error desconegut."
-
 msgid "Mobile message was not sent because it was too long."
 msgstr "No s'ha enviat el missatge al mòbil perquè era massa llarg."
 
@@ -5645,6 +5626,9 @@
 "La llista d'amics MSN està temporalment no disponible. Espereu i proveu-ho "
 "més tard."
 
+msgid "Unknown error."
+msgstr "Error desconegut."
+
 msgid "Handshaking"
 msgstr "S'està comprovant la conformitat de connexió"
 
@@ -5746,6 +5730,29 @@
 msgid "%s on %s (%s)"
 msgstr "%s a %s (%s)"
 
+#, c-format
+msgid "Unable to add user on %s (%s)"
+msgstr "No s'ha pogut afegir l'usuari a %s (%s)"
+
+#, c-format
+msgid "Unable to block user on %s (%s)"
+msgstr "No s'ha pogut blocar l'usuari %s (%s)"
+
+#, c-format
+msgid "Unable to permit user on %s (%s)"
+msgstr "No s'ha pogut permetre l'usuari %s (%s)"
+
+#, c-format
+msgid "%s could not be added because your buddy list is full."
+msgstr "No s'ha pogut afegir %s perquè la llista és plena."
+
+#, c-format
+msgid "%s is not a valid passport account."
+msgstr "%s no és un compte de passaport vàlid."
+
+msgid "Service Temporarily Unavailable."
+msgstr "El servei no està disponible temporalment."
+
 msgid "Unable to rename group"
 msgstr "No s'ha pogut canviar el nom del grup"
 
@@ -5843,14 +5850,16 @@
 
 #, c-format
 msgid ""
-"%s Your password is %d characters, greater than the expected maximum length "
-"of %d for MySpaceIM. Please shorten your password at http://profileedit."
-"myspace.com/index.cfm?fuseaction=accountSettings.changePassword and try "
-"again."
-msgstr ""
-"%s La vostra contrasenya conté %d caràcters, més del màxim de %d que permet "
-"MySpaceIM. Escolliu una contrasenya més curta a http://profileedit.myspace."
-"com/index.cfm?fuseaction=accountSettings.changePassword i proveu-ho de nou."
+"%s Your password is %zu characters, which is longer than the maximum length "
+"of %d.  Please shorten your password at http://profileedit.myspace.com/index."
+"cfm?fuseaction=accountSettings.changePassword and try again."
+msgstr ""
+"%s La vostra contrasenya conté %zu caràcters, més %d que és el màxim permès. "
+"Escolliu una contrasenya més curta a http://profileedit.myspace.com/index."
+"cfm?fuseaction=accountSettings.changePassword i proveu-ho de nou."
+
+msgid "Incorrect username or password"
+msgstr "El sobrenom o la contrasenya no són correctes"
 
 msgid "MySpaceIM Error"
 msgstr "S'ha produït un error en el MySpaceIM"
@@ -6174,9 +6183,6 @@
 msgid "Master archive is misconfigured"
 msgstr "L'arxiu mestre està desconfigurat"
 
-msgid "Incorrect username or password"
-msgstr "El sobrenom o la contrasenya no són correctes"
-
 msgid "Could not recognize the host of the username you entered"
 msgstr "No s'ha pogut reconèixer l'ordinador del nom d'usuari que heu entrat"
 
@@ -6707,6 +6713,31 @@
 "començar amb una lletra i contenir només lletres, nombres o espais, o només "
 "nombres."
 
+#, c-format
+msgid "You may be disconnected shortly.  If so, check %s for updates."
+msgstr ""
+"Pot ser que es desconnecti d'aquí a poc. Si això passés, comproveu si hi ha "
+"actualitzacions a %s."
+
+# FIXME: hash (josep)
+msgid "Unable to get a valid AIM login hash."
+msgstr "No s'ha pogut obtenir un hash d'AIM d'entrada vàlid."
+
+#, c-format
+msgid "You may be disconnected shortly.  Check %s for updates."
+msgstr ""
+"Se us pot desconnectar d'aquí a poc temps. Comproveu si hi ha "
+"actualitzacions a %s."
+
+msgid "Unable to get a valid login hash."
+msgstr "No s'ha pogut obtenir un hash d'entrada vàlid."
+
+msgid "Could Not Connect"
+msgstr "No s'ha pogut connectar"
+
+msgid "Received authorization"
+msgstr "S'ha rebut l'autorització"
+
 #. Unregistered username
 #. uid is not exist
 msgid "Invalid username."
@@ -6725,7 +6756,6 @@
 "El servei de missatges instantanis d'AOL no està disponible temporalment."
 
 #. username connecting too frequently
-#. IP address connecting too frequently
 msgid ""
 "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."
@@ -6738,11 +6768,14 @@
 msgid "The client version you are using is too old. Please upgrade at %s"
 msgstr "La versió del client que useu és massa antiga, actualitzeu-la a %s"
 
-msgid "Could Not Connect"
-msgstr "No s'ha pogut connectar"
-
-msgid "Received authorization"
-msgstr "S'ha rebut l'autorització"
+#. IP address connecting too frequently
+msgid ""
+"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."
+msgstr ""
+"Heu estat connectant-vos i desconnectat-vos amb massa freqüència. Espereu un "
+"minut i intenteu-ho de nou. Si continueu intentant-ho, haureu d'esperar "
+"encara més temps."
 
 msgid "The SecurID key entered is invalid."
 msgstr "La clau SecurID que heu entrat no és vàlida."
@@ -6759,25 +6792,6 @@
 msgid "_OK"
 msgstr "_D'acord"
 
-#, c-format
-msgid "You may be disconnected shortly.  If so, check %s for updates."
-msgstr ""
-"Pot ser que es desconnecti d'aquí a poc. Si això passés, comproveu si hi ha "
-"actualitzacions a %s."
-
-# FIXME: hash (josep)
-msgid "Unable to get a valid AIM login hash."
-msgstr "No s'ha pogut obtenir un hash d'AIM d'entrada vàlid."
-
-#, c-format
-msgid "You may be disconnected shortly.  Check %s for updates."
-msgstr ""
-"Se us pot desconnectar d'aquí a poc temps. Comproveu si hi ha "
-"actualitzacions a %s."
-
-msgid "Unable to get a valid login hash."
-msgstr "No s'ha pogut obtenir un hash d'entrada vàlid."
-
 msgid "Password sent"
 msgstr "S'ha enviat la contrasenya"
 
@@ -7282,6 +7296,7 @@
 msgid "Set User Info (web)..."
 msgstr "Estableix informació d'usuari (web)..."
 
+#. This only happens when connecting with the old-style BUCP login
 msgid "Change Password (web)"
 msgstr "Canvia la contrasenya (web)"
 
@@ -7311,6 +7326,9 @@
 msgid "Search for Buddy by Information"
 msgstr "Cerca un amic per la informació"
 
+msgid "Use clientLogin"
+msgstr "Empra clientLogin"
+
 msgid ""
 "Always use AIM/ICQ proxy server for\n"
 "file transfers and direct IM (slower,\n"
--- a/po/de.po	Sat Jun 27 17:50:35 2009 +0000
+++ b/po/de.po	Sat Jun 27 17:50:49 2009 +0000
@@ -11,9 +11,9 @@
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-06-01 12:31+0200\n"
-"PO-Revision-Date: 2009-06-01 12:31+0200\n"
-"Last-Translator: Björn Voigt <bjoern@cs.tu-berlin.de>\n"
+"POT-Creation-Date: 2009-06-11 12:42+0200\n"
+"PO-Revision-Date: 2009-06-11 12:40+0200\n"
+"Last-Translator: Bjoern Voigt <bjoern@cs.tu-berlin.de>\n"
 "Language-Team: Deutsch <de@li.org>\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
@@ -877,7 +877,7 @@
 msgstr "System-Mitschnitt"
 
 msgid "Calling ... "
-msgstr "Anrufen..."
+msgstr "Anrufen ... "
 
 msgid "Hangup"
 msgstr "Auflegen"
@@ -3169,6 +3169,10 @@
 msgid "Chat _name:"
 msgstr "Chat_name:"
 
+#. should this be a settings error?
+msgid "Unable to resolve server"
+msgstr "Verbindung zum Server nicht möglich"
+
 msgid "Chat error"
 msgstr "Chatfehler"
 
@@ -3217,6 +3221,10 @@
 msgid "Gadu-Gadu User"
 msgstr "Gadu-Gadu-Benutzer"
 
+#, fuzzy
+msgid "GG server"
+msgstr "Hole server"
+
 #, c-format
 msgid "Unknown command: %s"
 msgstr "Unbekanntes Kommando: %s"
@@ -3463,7 +3471,7 @@
 #. notify the user that their /nick command didn't go.
 #, c-format
 msgid "The nickname \"%s\" is already being used."
-msgstr "Der Spitzname \"%s\" existiert bereits."
+msgstr "Der Spitzname „%s“ existiert bereits."
 
 #, fuzzy
 msgid "Nickname in use"
@@ -4215,9 +4223,8 @@
 msgid "Invalid XMPP ID. Domain must be set."
 msgstr "Falsche XMPP-ID. Die Domain muss gesetzt werden."
 
-#, fuzzy
 msgid "Malformed BOSH Connect Server"
-msgstr "Verbindung zum Server nicht möglich."
+msgstr ""
 
 #, c-format
 msgid "Registration of %s@%s successful"
@@ -4243,9 +4250,6 @@
 msgid "Unregistration Failed"
 msgstr "Aufheben der Registrierung gescheitert"
 
-msgid "Already Registered"
-msgstr "Schon registriert"
-
 msgid "State"
 msgstr "Provinz/Bundesland"
 
@@ -4258,6 +4262,9 @@
 msgid "Date"
 msgstr "Datum"
 
+msgid "Already Registered"
+msgstr "Schon registriert"
+
 msgid "Unregister"
 msgstr "Aufheben der Registrierung"
 
@@ -4631,7 +4638,7 @@
 "session."
 msgstr ""
 "Bitte wählen Sie die Ressource von %s, mir der Sie eine Medien-Sitzung "
-"starten möchten"
+"starten möchten."
 
 msgid "Select a Resource"
 msgstr "Wählen Sie eine Ressource"
@@ -4795,12 +4802,11 @@
 msgid "Error in chat %s"
 msgstr "Fehler im Chat %s"
 
-#, fuzzy
 msgid "An error occured on the in-band bytestream transfer\n"
-msgstr "Beim Öffnen der Datei trat ein Fehler auf."
+msgstr "Beim Übertragen der Datei trat ein Fehler auf\n"
 
 msgid "Transfer was closed."
-msgstr "Übertragung wurde geschlossen"
+msgstr "Übertragung wurde geschlossen."
 
 msgid "Failed to open the file"
 msgstr "Öffnen der Datei fehlgeschlagen"
@@ -5441,32 +5447,6 @@
 msgid "The following users are missing from your addressbook"
 msgstr "Die folgenden Benutzer fehlen in Ihrem Adressbuch"
 
-#, c-format
-msgid "Unable to add user on %s (%s)"
-msgstr "Kann den Benutzer nicht zu %s (%s) hinzufügen"
-
-#, c-format
-msgid "Unable to block user on %s (%s)"
-msgstr "Kann den Benutzer nicht für %s (%s) blockieren"
-
-#, c-format
-msgid "Unable to permit user on %s (%s)"
-msgstr "Kann den Benutzer nicht für %s (%s) erlauben"
-
-#, c-format
-msgid "%s could not be added because your buddy list is full."
-msgstr "%s konnte nicht hinzugefügt werden, da Ihre Buddy-Liste voll ist."
-
-#, c-format
-msgid "%s is not a valid passport account."
-msgstr "%s ist kein gültiges Passport-Konto."
-
-msgid "Service Temporarily Unavailable."
-msgstr "Dienst momentan nicht verfügbar."
-
-msgid "Unknown error."
-msgstr "Unbekannter Fehler."
-
 msgid "Mobile message was not sent because it was too long."
 msgstr "Mobile Nachricht wurde nicht gesendet, da sie zu lang war."
 
@@ -5566,6 +5546,9 @@
 "Ihre MSN-Buddy-Liste ist temporär nicht verfügbar. Bitte warten Sie und "
 "versuchen Sie es später nochmal."
 
+msgid "Unknown error."
+msgstr "Unbekannter Fehler."
+
 msgid "Handshaking"
 msgstr "Abgleich"
 
@@ -5669,6 +5652,29 @@
 msgid "%s on %s (%s)"
 msgstr "%s auf %s (%s)"
 
+#, c-format
+msgid "Unable to add user on %s (%s)"
+msgstr "Kann den Benutzer nicht zu %s (%s) hinzufügen"
+
+#, c-format
+msgid "Unable to block user on %s (%s)"
+msgstr "Kann den Benutzer nicht für %s (%s) blockieren"
+
+#, c-format
+msgid "Unable to permit user on %s (%s)"
+msgstr "Kann den Benutzer nicht für %s (%s) erlauben"
+
+#, c-format
+msgid "%s could not be added because your buddy list is full."
+msgstr "%s konnte nicht hinzugefügt werden, da Ihre Buddy-Liste voll ist."
+
+#, c-format
+msgid "%s is not a valid passport account."
+msgstr "%s ist kein gültiges Passport-Konto."
+
+msgid "Service Temporarily Unavailable."
+msgstr "Dienst momentan nicht verfügbar."
+
 msgid "Unable to rename group"
 msgstr "Kann die Gruppe nicht umbenennen"
 
@@ -6783,7 +6789,7 @@
 msgstr "Möchten Sie diesen Buddy zu Ihrer Buddy-Liste hinzufügen?"
 
 msgid "_Add"
-msgstr "_Hinzufügen"
+msgstr "Hinzu_fügen"
 
 msgid "_Decline"
 msgstr "_Ablehnen"
@@ -9568,16 +9574,26 @@
 msgstr "Falsches Passwort"
 
 #. security lock from too many failed login attempts
-msgid "Account locked: Too many failed login attempts"
-msgstr "Konto gesperrt: Zu viele erfolglose Login-Versuche"
+#, fuzzy
+msgid ""
+"Account locked: Too many failed login attempts.\n"
+"Logging into the Yahoo! website may fix this."
+msgstr ""
+"Unbekannte Fehlernummer %d. Vielleicht kann dies repariert werden, wenn Sie "
+"sich auf der Yahoo! Webseite anmelden."
 
 #. the username does not exist
 msgid "Username does not exist"
 msgstr "Benutzername existiert nicht"
 
 #. indicates a lock of some description
-msgid "Account locked: See the debug log"
-msgstr "Konto gesperrt: Sehen Sie in den Debug-Mitschnitt"
+#, fuzzy
+msgid ""
+"Account locked: Unknown reason.\n"
+"Logging into the Yahoo! website may fix this."
+msgstr ""
+"Unbekannte Fehlernummer %d. Vielleicht kann dies repariert werden, wenn Sie "
+"sich auf der Yahoo! Webseite anmelden."
 
 #. username or password missing
 msgid "Username or password missing"
@@ -10492,9 +10508,8 @@
 msgid "Please update the necessary fields."
 msgstr "Bitte aktualisieren Sie die erforderlichen Felder."
 
-#, fuzzy
 msgid "A_ccount"
-msgstr "Konto"
+msgstr "K_onto"
 
 msgid ""
 "Please enter the appropriate information about the chat you would like to "
@@ -11646,7 +11661,7 @@
 msgstr "_Name"
 
 msgid "_Account"
-msgstr "_Konto"
+msgstr "K_onto"
 
 msgid "Get User Info"
 msgstr "Benutzer-Info abrufen"
@@ -12400,19 +12415,19 @@
 msgstr "Bei wem alarmieren"
 
 msgid "_Account:"
-msgstr "_Konto:"
+msgstr "K_onto:"
 
 msgid "_Buddy name:"
-msgstr "_Buddy-Name:"
+msgstr "Budd_y-Name:"
 
 msgid "Si_gns on"
-msgstr "_sich anmeldet"
+msgstr "si_ch anmeldet"
 
 msgid "Signs o_ff"
 msgstr "sich abmel_det"
 
 msgid "Goes a_way"
-msgstr "_hinausgeht"
+msgstr "hinausgeh_t"
 
 msgid "Ret_urns from away"
 msgstr "wi_eder anwesend ist"
@@ -12421,22 +12436,22 @@
 msgstr "_untätig wird"
 
 msgid "Is no longer i_dle"
-msgstr "nicht meh_r untätig ist"
+msgstr "nicht _mehr untätig ist"
 
 msgid "Starts _typing"
-msgstr "zu _tippen beginnt"
+msgstr "zu tippen _beginnt"
 
 msgid "P_auses while typing"
-msgstr "beim Tippen an_hält"
+msgstr "beim Tippen anh_ält"
 
 msgid "Stops t_yping"
 msgstr "aufhört _zu tippen"
 
 msgid "Sends a _message"
-msgstr "eine _Nachricht sendet"
+msgstr "eine Nachr_icht sendet"
 
 msgid "Ope_n an IM window"
-msgstr "Gesprächsfenster ö_ffnen"
+msgstr "_Gesprächsfenster öffnen"
 
 msgid "_Pop up a notification"
 msgstr "_Popup-Benachrichtigung"
@@ -12445,22 +12460,22 @@
 msgstr "_Nachricht senden"
 
 msgid "E_xecute a command"
-msgstr "Befeh_l ausführen"
+msgstr "Befehl ausfüh_ren"
 
 msgid "P_lay a sound"
-msgstr "einen _Klang abspielen"
+msgstr "Einen _Klang abspielen"
 
 msgid "Brows_e..."
-msgstr "Aus_wählen..."
+msgstr "Au_swählen..."
 
 msgid "Br_owse..."
-msgstr "Aus_wählen..."
+msgstr "Auswäh_len..."
 
 msgid "Pre_view"
 msgstr "_Vorschau"
 
 msgid "P_ounce only when my status is not Available"
-msgstr "Nur _alarmieren, wenn ich nicht verfügbar bin"
+msgstr "Nur alarmieren, wenn ich nicht ver_fügbar bin"
 
 msgid "_Recurring"
 msgstr "_Wiederkehrend"
@@ -13802,12 +13817,6 @@
 "- Es sendet eine Nachricht zu Leuten in Ihrer Buddy Liste, direkt wenn Sie "
 "sich angemeldet haben"
 
-msgid "Cursor Color"
-msgstr "Cursor-Farbe"
-
-msgid "Secondary Cursor Color"
-msgstr "Sekundäre Cursor-Farbe"
-
 msgid "Hyperlink Color"
 msgstr "Hyperlink-Farbe"
 
@@ -13817,6 +13826,10 @@
 msgid "Highlighted Message Name Color"
 msgstr "Farbe des Absendernamens für hervorgehobene Nachrichten"
 
+#, fuzzy
+msgid "Typing Notification Color"
+msgstr "Farbe der Tipp-Benachrichtigung"
+
 msgid "GtkTreeView Horizontal Separation"
 msgstr "GtkTreeview horizontaler Abstand"
 
@@ -13845,35 +13858,24 @@
 msgid "GTK+ Text Shortcut Theme"
 msgstr "GTK+ Text Shortcut-Thema"
 
-#.
-#. for (i = 0; i < G_N_ELEMENTS(widget_bool_prefs); i++) {
-#. hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
-#. gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0);
-#.
-#. check = pidgin_prefs_checkbox(_(widget_bool_names[i]),
-#. widget_bool_prefs_set[i], hbox);
-#. gtk_size_group_add_widget(labelsg, check);
-#.
-#. widget_bool_widgets[i] = pidgin_prefs_checkbox("", widget_bool_prefs[i], hbox);
-#. *
-#. gtk_size_group_add_widget(widgetsb, widget_bool_widgets[i]);
-#. *
-#. gtk_widget_set_sensitive(widget_bool_widgets[i],
-#. purple_prefs_get_bool(widget_bool_prefs_set[i]));
-#. g_signal_connect(G_OBJECT(check), "toggled",
-#. G_CALLBACK(pidgin_toggle_sensitive),
-#. widget_bool_widgets[i]);
-#. }
-#.
-msgid "Interface colors"
-msgstr "UI-Farben"
-
-msgid "Widget Sizes"
-msgstr "Widget-Größen"
+#, fuzzy
+msgid "Disable Typing Notification Text"
+msgstr "Tipp-Benachrichtigung aktivieren"
+
+#, fuzzy
+msgid "GTK+ Theme Control Settings"
+msgstr "Pidgin GTK+ Themenkontrolle"
+
+#, fuzzy
+msgid "Colors"
+msgstr "Schließen"
 
 msgid "Fonts"
 msgstr "Schrift"
 
+msgid "Miscellaneous"
+msgstr ""
+
 msgid "Gtkrc File Tools"
 msgstr "Gtkrc-Datei-Werkzeuge"