changeset 27245:420850f3236e

merge of '02b9e2a56048af7440e6752b0a4d65df7c519ae7' and '5cffa941d7a297ad42b518e4c97e930a0ad54cd5'
author Paul Aurich <paul@darkrain42.org>
date Tue, 23 Jun 2009 19:06:28 +0000
parents 8f405df1652d (current diff) 7054f810b0f9 (diff)
children 959e86c022f7
files
diffstat 9 files changed, 1164 insertions(+), 233 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/cipher.c	Tue Jun 23 19:05:49 2009 +0000
+++ b/libpurple/cipher.c	Tue Jun 23 19:06:28 2009 +0000
@@ -1862,6 +1862,261 @@
 };
 
 /*******************************************************************************
+ * SHA-256
+ ******************************************************************************/
+#define SHA256_HMAC_BLOCK_SIZE	64
+#define SHA256_ROTR(X,n) ((((X) >> (n)) | ((X) << (32-(n)))) & 0xFFFFFFFF)
+
+static const guint32 sha256_K[64] =
+{
+	0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+	0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+	0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+	0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+	0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+	0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+	0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+	0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+struct SHA256Context {
+	guint32 H[8];
+	guint32 W[64];
+
+	gint lenW;
+
+	guint32 sizeHi;
+	guint32 sizeLo;
+};
+
+static void
+sha256_hash_block(struct SHA256Context *sha256_ctx) {
+	gint i;
+	guint32 A, B, C, D, E, F, G, H, T1, T2;
+
+	for(i = 16; i < 64; i++) {
+		sha256_ctx->W[i] =
+			  (SHA256_ROTR(sha256_ctx->W[i-2], 17) ^ SHA256_ROTR(sha256_ctx->W[i-2],  19) ^ (sha256_ctx->W[i-2] >> 10))
+			+ sha256_ctx->W[i-7]
+			+ (SHA256_ROTR(sha256_ctx->W[i-15], 7) ^ SHA256_ROTR(sha256_ctx->W[i-15], 18) ^ (sha256_ctx->W[i-15] >> 3))
+			+ sha256_ctx->W[i-16];
+	}
+
+	A = sha256_ctx->H[0];
+	B = sha256_ctx->H[1];
+	C = sha256_ctx->H[2];
+	D = sha256_ctx->H[3];
+	E = sha256_ctx->H[4];
+	F = sha256_ctx->H[5];
+	G = sha256_ctx->H[6];
+	H = sha256_ctx->H[7];
+
+	for(i = 0; i < 64; i++) {
+        T1 = H
+			+ (SHA256_ROTR(E, 6) ^ SHA256_ROTR(E, 11) ^ SHA256_ROTR(E, 25))
+			+ ((E & F) ^ ((~E) & G))
+			+ sha256_K[i] + sha256_ctx->W[i];
+        T2 = (SHA256_ROTR(A, 2) ^ SHA256_ROTR(A, 13) ^ SHA256_ROTR(A, 22))
+			+ ((A & B) ^ (A & C) ^ (B & C));
+		H = G;
+		G = F;
+		F = E;
+		E = D + T1;
+		D = C;
+		C = B;
+		B = A;
+		A = T1 + T2;
+	}
+
+	sha256_ctx->H[0] += A;
+	sha256_ctx->H[1] += B;
+	sha256_ctx->H[2] += C;
+	sha256_ctx->H[3] += D;
+	sha256_ctx->H[4] += E;
+	sha256_ctx->H[5] += F;
+	sha256_ctx->H[6] += G;
+	sha256_ctx->H[7] += H;
+}
+
+static void
+sha256_set_opt(PurpleCipherContext *context, const gchar *name, void *value) {
+	struct SHA256Context *ctx;
+
+	ctx = purple_cipher_context_get_data(context);
+
+	if(!strcmp(name, "sizeHi")) {
+		ctx->sizeHi = GPOINTER_TO_INT(value);
+	} else if(!strcmp(name, "sizeLo")) {
+		ctx->sizeLo = GPOINTER_TO_INT(value);
+	} else if(!strcmp(name, "lenW")) {
+		ctx->lenW = GPOINTER_TO_INT(value);
+	}
+}
+
+static void *
+sha256_get_opt(PurpleCipherContext *context, const gchar *name) {
+	struct SHA256Context *ctx;
+
+	ctx = purple_cipher_context_get_data(context);
+
+	if(!strcmp(name, "sizeHi")) {
+		return GINT_TO_POINTER(ctx->sizeHi);
+	} else if(!strcmp(name, "sizeLo")) {
+		return GINT_TO_POINTER(ctx->sizeLo);
+	} else if(!strcmp(name, "lenW")) {
+		return GINT_TO_POINTER(ctx->lenW);
+	}
+
+	return NULL;
+}
+
+static void
+sha256_init(PurpleCipherContext *context, void *extra) {
+	struct SHA256Context *sha256_ctx;
+
+	sha256_ctx = g_new0(struct SHA256Context, 1);
+
+	purple_cipher_context_set_data(context, sha256_ctx);
+
+	purple_cipher_context_reset(context, extra);
+}
+
+static void
+sha256_reset(PurpleCipherContext *context, void *extra) {
+	struct SHA256Context *sha256_ctx;
+	gint i;
+
+	sha256_ctx = purple_cipher_context_get_data(context);
+
+	g_return_if_fail(sha256_ctx);
+
+	sha256_ctx->lenW = 0;
+	sha256_ctx->sizeHi = 0;
+	sha256_ctx->sizeLo = 0;
+
+	sha256_ctx->H[0] = 0x6a09e667;
+	sha256_ctx->H[1] = 0xbb67ae85;
+	sha256_ctx->H[2] = 0x3c6ef372;
+	sha256_ctx->H[3] = 0xa54ff53a;
+	sha256_ctx->H[4] = 0x510e527f;
+	sha256_ctx->H[5] = 0x9b05688c;
+	sha256_ctx->H[6] = 0x1f83d9ab;
+	sha256_ctx->H[7] = 0x5be0cd19;
+
+	for(i = 0; i < 64; i++)
+		sha256_ctx->W[i] = 0;
+}
+
+static void
+sha256_uninit(PurpleCipherContext *context) {
+	struct SHA256Context *sha256_ctx;
+
+	purple_cipher_context_reset(context, NULL);
+
+	sha256_ctx = purple_cipher_context_get_data(context);
+
+	memset(sha256_ctx, 0, sizeof(struct SHA256Context));
+
+	g_free(sha256_ctx);
+	sha256_ctx = NULL;
+}
+
+
+static void
+sha256_append(PurpleCipherContext *context, const guchar *data, size_t len) {
+	struct SHA256Context *sha256_ctx;
+	gint i;
+
+	sha256_ctx = purple_cipher_context_get_data(context);
+
+	g_return_if_fail(sha256_ctx);
+
+	for(i = 0; i < len; i++) {
+		sha256_ctx->W[sha256_ctx->lenW / 4] <<= 8;
+		sha256_ctx->W[sha256_ctx->lenW / 4] |= data[i];
+
+		if((++sha256_ctx->lenW) % 64 == 0) {
+			sha256_hash_block(sha256_ctx);
+			sha256_ctx->lenW = 0;
+		}
+
+		sha256_ctx->sizeLo += 8;
+		sha256_ctx->sizeHi += (sha256_ctx->sizeLo < 8);
+	}
+}
+
+static gboolean
+sha256_digest(PurpleCipherContext *context, size_t in_len, guchar digest[32],
+			size_t *out_len)
+{
+	struct SHA256Context *sha256_ctx;
+	guchar pad0x80 = 0x80, pad0x00 = 0x00;
+	guchar padlen[8];
+	gint i;
+
+	g_return_val_if_fail(in_len >= 32, FALSE);
+
+	sha256_ctx = purple_cipher_context_get_data(context);
+
+	g_return_val_if_fail(sha256_ctx, FALSE);
+
+	padlen[0] = (guchar)((sha256_ctx->sizeHi >> 24) & 255);
+	padlen[1] = (guchar)((sha256_ctx->sizeHi >> 16) & 255);
+	padlen[2] = (guchar)((sha256_ctx->sizeHi >> 8) & 255);
+	padlen[3] = (guchar)((sha256_ctx->sizeHi >> 0) & 255);
+	padlen[4] = (guchar)((sha256_ctx->sizeLo >> 24) & 255);
+	padlen[5] = (guchar)((sha256_ctx->sizeLo >> 16) & 255);
+	padlen[6] = (guchar)((sha256_ctx->sizeLo >> 8) & 255);
+	padlen[7] = (guchar)((sha256_ctx->sizeLo >> 0) & 255);
+
+	/* pad with a 1, then zeroes, then length */
+	purple_cipher_context_append(context, &pad0x80, 1);
+	while(sha256_ctx->lenW != 56)
+		purple_cipher_context_append(context, &pad0x00, 1);
+	purple_cipher_context_append(context, padlen, 8);
+
+	for(i = 0; i < 32; i++) {
+		digest[i] = (guchar)(sha256_ctx->H[i / 4] >> 24);
+		sha256_ctx->H[i / 4] <<= 8;
+	}
+
+	purple_cipher_context_reset(context, NULL);
+
+	if(out_len)
+		*out_len = 32;
+
+	return TRUE;
+}
+
+static size_t
+sha256_get_block_size(PurpleCipherContext *context)
+{
+	/* This does not change (in this case) */
+	return SHA256_HMAC_BLOCK_SIZE;
+}
+
+static PurpleCipherOps SHA256Ops = {
+	sha256_set_opt,	/* Set Option		*/
+	sha256_get_opt,	/* Get Option		*/
+	sha256_init,	/* init				*/
+	sha256_reset,	/* reset			*/
+	sha256_uninit,	/* uninit			*/
+	NULL,			/* set iv			*/
+	sha256_append,	/* append			*/
+	sha256_digest,	/* digest			*/
+	NULL,			/* encrypt			*/
+	NULL,			/* decrypt			*/
+	NULL,			/* set salt			*/
+	NULL,			/* get salt size	*/
+	NULL,			/* set key			*/
+	NULL,			/* get key size		*/
+	NULL,			/* set batch mode */
+	NULL,			/* get batch mode */
+	sha256_get_block_size,	/* get block size */
+	NULL			/* set key with len */
+};
+
+/*******************************************************************************
  * RC4
  ******************************************************************************/
 
@@ -2228,6 +2483,7 @@
 
 	purple_ciphers_register_cipher("md5", &MD5Ops);
 	purple_ciphers_register_cipher("sha1", &SHA1Ops);
+	purple_ciphers_register_cipher("sha256", &SHA256Ops);
 	purple_ciphers_register_cipher("md4", &MD4Ops);
 	purple_ciphers_register_cipher("hmac", &HMACOps);
 	purple_ciphers_register_cipher("des", &DESOps);
--- a/libpurple/protocols/oscar/Makefile.am	Tue Jun 23 19:05:49 2009 +0000
+++ b/libpurple/protocols/oscar/Makefile.am	Tue Jun 23 19:06:28 2009 +0000
@@ -7,6 +7,7 @@
 
 OSCARSOURCES = \
 	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	Tue Jun 23 19:06:28 2009 +0000
@@ -0,0 +1,530 @@
+/*
+ * Purple's oscar protocol plugin
+ * This file is the legal property of its developers.
+ * Please see the AUTHORS file distributed alongside this file.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+*/
+
+/**
+ * This file implements AIM's clientLogin procedure for authenticating
+ * users.  This replaces the older MD5-based and XOR-based
+ * authentication methods that use SNAC family 0x0017.
+ *
+ * This doesn't use SNACs or FLAPs at all.  It makes http and https
+ * POSTs to AOL to validate the user based on the password they
+ * provided to us.  Upon successful authentication we request a
+ * connection to the BOS server by calling startOSCARsession.  The
+ * AOL server gives us the hostname and port number to use, as well
+ * as the cookie to use to authenticate to the BOS server.  And then
+ * everything else is the same as with BUCP.
+ *
+ * For details, see:
+ * http://dev.aol.com/aim/oscar/#AUTH
+ * http://dev.aol.com/authentication_for_clients
+ */
+
+#include "cipher.h"
+#include "core.h"
+
+#include "oscar.h"
+
+#define URL_CLIENT_LOGIN "https://api.screenname.aol.com/auth/clientLogin"
+#define URL_START_OSCAR_SESSION "http://api.oscar.aol.com/aim/startOSCARSession"
+
+/*
+ * Using clientLogin requires a developer ID.  This dev ID is owned by
+ * the AIM account "markdoliner"
+ */
+#define CLIENT_KEY "ma15d7JTxbmVG-RP"
+
+/**
+ * This is similar to purple_url_encode() except that it follows
+ * RFC3986 a little more closely by not encoding - . _ and ~
+ * It also uses capital letters as hex characters because capital
+ * letters are required by AOL.  The RFC says that capital letters
+ * are a SHOULD and that URLs that use capital letters are
+ * equivalent to URLs that use small letters.
+ *
+ * TODO: Check if purple_url_encode() can be replaced with this
+ *       version without breaking anything.
+ */
+static const char *oscar_auth_url_encode(const char *str)
+{
+	const char *iter;
+	static char buf[BUF_LEN];
+	char utf_char[6];
+	guint i, j = 0;
+
+	g_return_val_if_fail(str != NULL, NULL);
+	g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
+
+	iter = str;
+	for (; *iter && j < (BUF_LEN - 1) ; iter = g_utf8_next_char(iter)) {
+		gunichar c = g_utf8_get_char(iter);
+		/* If the character is an ASCII character and is alphanumeric
+		 * no need to escape */
+		if ((c < 128 && isalnum(c)) || c =='-' || c == '.' || c == '_' || c == '~') {
+			buf[j++] = c;
+		} else {
+			int bytes = g_unichar_to_utf8(c, utf_char);
+			for (i = 0; i < bytes; i++) {
+				if (j > (BUF_LEN - 4))
+					break;
+				sprintf(buf + j, "%%%02X", utf_char[i] & 0xff);
+				j += 3;
+			}
+		}
+	}
+
+	buf[j] = '\0';
+
+	return buf;
+}
+
+/**
+ * @return A null-terminated base64 encoded version of the HMAC
+ *         calculated using the given key and data.
+ */
+static gchar *hmac_sha256(const char *key, const char *message)
+{
+	PurpleCipherContext *context;
+	guchar digest[32];
+
+	context = purple_cipher_context_new_by_name("hmac", NULL);
+	purple_cipher_context_set_option(context, "hash", "sha256");
+	purple_cipher_context_set_key(context, (guchar *)key);
+	purple_cipher_context_append(context, (guchar *)message, strlen(message));
+	purple_cipher_context_digest(context, sizeof(digest), digest, NULL);
+	purple_cipher_context_destroy(context);
+
+	return purple_base64_encode(digest, sizeof(digest));
+}
+
+/**
+ * @return A base-64 encoded HMAC-SHA256 signature created using the
+ *         technique documented at
+ *         http://dev.aol.com/authentication_for_clients#signing
+ */
+static gchar *generate_signature(const char *method, const char *url, const char *parameters, const char *session_key)
+{
+	char *encoded_url, *signature_base_string, *signature;
+	const char *encoded_parameters;
+
+	encoded_url = g_strdup(oscar_auth_url_encode(url));
+	encoded_parameters = oscar_auth_url_encode(parameters);
+	signature_base_string = g_strdup_printf("%s&%s&%s",
+			method, encoded_url, encoded_parameters);
+	g_free(encoded_url);
+
+	signature = hmac_sha256(session_key, signature_base_string);
+	g_free(signature_base_string);
+
+	return signature;
+}
+
+static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **host, unsigned short *port, char **cookie)
+{
+	xmlnode *response_node, *tmp_node, *data_node;
+	xmlnode *host_node, *port_node, *cookie_node;
+	char *tmp;
+
+	/* Parse the response as XML */
+	response_node = xmlnode_from_str(response, response_len);
+	if (response_node == NULL)
+	{
+		purple_debug_error("oscar", "startOSCARSession could not parse "
+				"response as XML: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_START_OSCAR_SESSION));
+		return FALSE;
+	}
+
+	/* Grab the necessary XML nodes */
+	tmp_node = xmlnode_get_child(response_node, "statusCode");
+	data_node = xmlnode_get_child(response_node, "data");
+	if (data_node != NULL) {
+		host_node = xmlnode_get_child(data_node, "host");
+		port_node = xmlnode_get_child(data_node, "port");
+		cookie_node = xmlnode_get_child(data_node, "cookie");
+	}
+
+	/* Make sure we have a status code */
+	if (tmp_node == NULL || (tmp = xmlnode_get_data_unescaped(tmp_node)) == NULL) {
+		purple_debug_error("oscar", "startOSCARSession response was "
+				"missing statusCode: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_START_OSCAR_SESSION));
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+
+	/* Make sure the status code was 200 */
+	if (strcmp(tmp, "200") != 0)
+	{
+		purple_debug_error("oscar", "startOSCARSession response statusCode "
+				"was %s: %s\n", tmp, response);
+
+		if (strcmp(tmp, "401") == 0)
+			purple_connection_error_reason(gc,
+					PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+					_("You have been connecting and disconnecting too "
+					  "frequently. Wait ten minutes and try again. If "
+					  "you continue to try, you will need to wait even "
+					  "longer."));
+		else
+			purple_connection_error_reason(gc,
+					PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+					_("Received unexpected response from " URL_START_OSCAR_SESSION));
+
+		g_free(tmp);
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+	g_free(tmp);
+
+	/* Make sure we have everything else */
+	if (data_node == NULL || host_node == NULL ||
+		port_node == NULL || cookie_node == NULL)
+	{
+		purple_debug_error("oscar", "startOSCARSession response was missing "
+				"something: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_START_OSCAR_SESSION));
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+
+	/* Extract data from the XML */
+	*host = xmlnode_get_data_unescaped(host_node);
+	tmp = xmlnode_get_data_unescaped(port_node);
+	*cookie = xmlnode_get_data_unescaped(cookie_node);
+	if (*host == NULL || **host == '\0' || tmp == NULL || *tmp == '\0' || cookie == NULL || *cookie == '\0')
+	{
+		purple_debug_error("oscar", "startOSCARSession response was missing "
+				"something: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_START_OSCAR_SESSION));
+		g_free(*host);
+		g_free(tmp);
+		g_free(*cookie);
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+
+	*port = atoi(tmp);
+	g_free(tmp);
+
+	return TRUE;
+}
+
+static void start_oscar_session_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
+{
+	OscarData *od;
+	PurpleConnection *gc;
+	char *host, *cookie;
+	unsigned short port;
+	guint8 *cookiedata;
+	gsize cookiedata_len;
+
+	od = user_data;
+	gc = od->gc;
+
+	od->url_data = NULL;
+
+	if (error_message != NULL || len == 0) {
+		gchar *tmp;
+		tmp = g_strdup_printf(_("Error requesting " URL_START_OSCAR_SESSION
+				": %s"), error_message);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
+		g_free(tmp);
+		return;
+	}
+
+	if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie))
+		return;
+
+	cookiedata = purple_base64_decode(cookie, &cookiedata_len);
+	oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len);
+	g_free(cookiedata);
+
+	g_free(host);
+	g_free(cookie);
+}
+
+static void send_start_oscar_session(OscarData *od, const char *token, const char *session_key, time_t hosttime)
+{
+	char *query_string, *signature, *url;
+
+	/* Construct the GET parameters */
+	query_string = g_strdup_printf("a=%s"
+			"&f=xml"
+			"&k=" CLIENT_KEY
+			"&ts=%zu"
+			"&useTLS=0",
+			oscar_auth_url_encode(token), hosttime);
+	signature = generate_signature("GET", URL_START_OSCAR_SESSION,
+			query_string, session_key);
+	url = g_strdup_printf(URL_START_OSCAR_SESSION "?%s&sig_sha256=%s",
+			query_string, signature);
+	g_free(query_string);
+	g_free(signature);
+
+	/* Make the request */
+	od->url_data = purple_util_fetch_url(url, TRUE, NULL, FALSE,
+			start_oscar_session_cb, od);
+	g_free(url);
+}
+
+/**
+ * This function parses the given response from a clientLogin request
+ * and extracts the useful information.
+ *
+ * @param gc           The PurpleConnection.  If the response data does
+ *                     not indicate then purple_connection_error_reason()
+ *                     will be called to close this connection.
+ * @param response     The response data from the clientLogin request.
+ * @param response_len The length of the above response, or -1 if
+ *                     @response is NUL terminated.
+ * @param token        If parsing was successful then this will be set to
+ *                     a newly allocated string containing the token.  The
+ *                     caller should g_free this string when it is finished
+ *                     with it.  On failure this value will be untouched.
+ * @param secret       If parsing was successful then this will be set to
+ *                     a newly allocated string containing the secret.  The
+ *                     caller should g_free this string when it is finished
+ *                     with it.  On failure this value will be untouched.
+ * @param hosttime     If parsing was successful then this will be set to
+ *                     the time on the OpenAuth Server in seconds since the
+ *                     Unix epoch.  On failure this value will be untouched.
+ *
+ * @return TRUE if the request was successful and we were able to
+ *         extract all info we need.  Otherwise FALSE.
+ */
+static gboolean parse_client_login_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **token, char **secret, time_t *hosttime)
+{
+	xmlnode *response_node, *tmp_node, *data_node;
+	xmlnode *secret_node, *hosttime_node, *token_node, *tokena_node;
+	char *tmp;
+
+	/* Parse the response as XML */
+	response_node = xmlnode_from_str(response, response_len);
+	if (response_node == NULL)
+	{
+		purple_debug_error("oscar", "clientLogin could not parse "
+				"response as XML: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_CLIENT_LOGIN));
+		return FALSE;
+	}
+
+	/* Grab the necessary XML nodes */
+	tmp_node = xmlnode_get_child(response_node, "statusCode");
+	data_node = xmlnode_get_child(response_node, "data");
+	if (data_node != NULL) {
+		secret_node = xmlnode_get_child(data_node, "sessionSecret");
+		hosttime_node = xmlnode_get_child(data_node, "hostTime");
+		token_node = xmlnode_get_child(data_node, "token");
+		if (token_node != NULL)
+			tokena_node = xmlnode_get_child(token_node, "a");
+	}
+
+	/* Make sure we have a status code */
+	if (tmp_node == NULL || (tmp = xmlnode_get_data_unescaped(tmp_node)) == NULL) {
+		purple_debug_error("oscar", "clientLogin response was "
+				"missing statusCode: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_CLIENT_LOGIN));
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+
+	/* Make sure the status code was 200 */
+	if (strcmp(tmp, "200") != 0)
+	{
+		int status_code, status_detail_code = 0;
+
+		status_code = atoi(tmp);
+		g_free(tmp);
+		tmp_node = xmlnode_get_child(response_node, "statusDetailCode");
+		if (tmp_node != NULL && (tmp = xmlnode_get_data_unescaped(tmp_node)) != NULL) {
+			status_detail_code = atoi(tmp);
+			g_free(tmp);
+		}
+
+		purple_debug_error("oscar", "clientLogin response statusCode "
+				"was %d (%d): %s\n", status_code, status_detail_code, response);
+
+		if (status_code == 330 && status_detail_code == 3011) {
+			purple_connection_error_reason(gc,
+					PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
+					_("Incorrect password."));
+		} else if (status_code == 401 && status_detail_code == 3019) {
+			purple_connection_error_reason(gc,
+					PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+					_("AOL does not allow your screen name to authenticate via this site."));
+		} else
+			purple_connection_error_reason(gc,
+					PURPLE_CONNECTION_ERROR_OTHER_ERROR,
+					_("Received unexpected response from " URL_CLIENT_LOGIN));
+
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+	g_free(tmp);
+
+	/* Make sure we have everything else */
+	if (data_node == NULL || secret_node == NULL ||
+		token_node == NULL || tokena_node == NULL)
+	{
+		purple_debug_error("oscar", "clientLogin response was missing "
+				"something: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_CLIENT_LOGIN));
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+
+	/* Extract data from the XML */
+	*token = xmlnode_get_data_unescaped(tokena_node);
+	*secret = xmlnode_get_data_unescaped(secret_node);
+	tmp = xmlnode_get_data_unescaped(hosttime_node);
+	if (*token == NULL || **token == '\0' || *secret == NULL || **secret == '\0' || tmp == NULL || *tmp == '\0')
+	{
+		purple_debug_error("oscar", "clientLogin response was missing "
+				"something: %s\n", response);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Received unexpected response from " URL_CLIENT_LOGIN));
+		g_free(*token);
+		g_free(*secret);
+		g_free(tmp);
+		xmlnode_free(response_node);
+		return FALSE;
+	}
+
+	*hosttime = strtol(tmp, NULL, 10);
+	g_free(tmp);
+
+	xmlnode_free(response_node);
+
+	return TRUE;
+}
+
+static void client_login_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
+{
+	OscarData *od;
+	PurpleConnection *gc;
+	char *token, *secret, *session_key;
+	time_t hosttime;
+	int password_len;
+	char *password;
+
+	od = user_data;
+	gc = od->gc;
+
+	od->url_data = NULL;
+
+	if (error_message != NULL || len == 0) {
+		gchar *tmp;
+		tmp = g_strdup_printf(_("Error requesting " URL_CLIENT_LOGIN
+				": %s"), error_message);
+		purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
+		g_free(tmp);
+		return;
+	}
+
+	if (!parse_client_login_response(gc, url_text, len, &token, &secret, &hosttime))
+		return;
+
+	password_len = strlen(purple_connection_get_password(gc));
+	password = g_strdup_printf("%.*s",
+			od->icq ? MIN(password_len, MAXICQPASSLEN) : password_len,
+			purple_connection_get_password(gc));
+	session_key = hmac_sha256(password, secret);
+	g_free(password);
+	g_free(secret);
+
+	send_start_oscar_session(od, token, session_key, hosttime);
+
+	g_free(token);
+	g_free(session_key);
+}
+
+/**
+ * This function sends a request to
+ * https://api.screenname.aol.com/auth/clientLogin with the user's
+ * username and password and receives the user's session key, which is
+ * used to request a connection to the BOSS server.
+ */
+void send_client_login(OscarData *od, const char *username)
+{
+	PurpleConnection *gc;
+	GString *request, *body;
+	const char *tmp;
+	char *password;
+	int password_len;
+
+	gc = od->gc;
+
+	/*
+	 * We truncate ICQ passwords to 8 characters.  There is probably a
+	 * limit for AIM passwords, too, but we really only need to do
+	 * this for ICQ because older ICQ clients let you enter a password
+	 * as long as you wanted and then they truncated it silently.
+	 *
+	 * And we can truncate based on the number of bytes and not the
+	 * number of characters because passwords for AIM and ICQ are
+	 * supposed to be plain ASCII (I don't know if this has always been
+	 * the case, though).
+	 */
+	tmp = purple_connection_get_password(gc);
+	password_len = strlen(tmp);
+	password = g_strndup(tmp, od->icq ? MIN(password_len, MAXICQPASSLEN) : password_len);
+
+	/* Construct the body of the HTTP POST request */
+	body = g_string_new("");
+	g_string_append_printf(body, "devId=" CLIENT_KEY);
+	g_string_append_printf(body, "&f=xml");
+	g_string_append_printf(body, "&pwd=%s", oscar_auth_url_encode(password));
+	g_string_append_printf(body, "&s=%s", oscar_auth_url_encode(username));
+	g_free(password);
+
+	/* Construct an HTTP POST request */
+	request = g_string_new("POST /auth/clientLogin HTTP/1.0\r\n"
+			"Connection: close\r\n"
+			"Accept: */*\r\n");
+
+	/* Tack on the body */
+	g_string_append_printf(request, "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n");
+	g_string_append_printf(request, "Content-Length: %lu\r\n\r\n", body->len);
+	g_string_append_len(request, body->str, body->len);
+	g_string_free(body, TRUE);
+
+	/* Send the POST request  */
+	od->url_data = purple_util_fetch_url_request(URL_CLIENT_LOGIN,
+			TRUE, NULL, FALSE, request->str, FALSE,
+			client_login_cb, od);
+	g_string_free(request, TRUE);
+}
--- a/libpurple/protocols/oscar/family_auth.c	Tue Jun 23 19:05:49 2009 +0000
+++ b/libpurple/protocols/oscar/family_auth.c	Tue Jun 23 19:06:28 2009 +0000
@@ -26,11 +26,11 @@
  *
  */
 
-#include "oscar.h"
+#include <ctype.h>
 
 #include "cipher.h"
 
-#include <ctype.h>
+#include "oscar.h"
 
 /* #define USE_XOR_FOR_ICQ */
 
--- a/libpurple/protocols/oscar/flap_connection.c	Tue Jun 23 19:05:49 2009 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Tue Jun 23 19:06:28 2009 +0000
@@ -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	Tue Jun 23 19:05:49 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Tue Jun 23 19:06:28 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;
@@ -1950,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, ...)
 {
@@ -1995,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)
 	{
@@ -3913,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;
 }
 
@@ -4429,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);
 }
 
@@ -6377,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))
@@ -6456,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)) {
@@ -6617,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;
@@ -6757,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);
@@ -6999,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);
--- a/libpurple/protocols/oscar/oscar.h	Tue Jun 23 19:05:49 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Tue Jun 23 19:06:28 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	Tue Jun 23 19:05:49 2009 +0000
+++ b/libpurple/protocols/oscar/oscar_data.c	Tue Jun 23 19:06:28 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	Tue Jun 23 19:05:49 2009 +0000
+++ b/libpurple/protocols/oscar/oscarcommon.h	Tue Jun 23 19:06:28 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);