# HG changeset patch # User Paul Aurich # Date 1245783988 0 # Node ID 420850f3236ef26a52b6435ff6ad2dbe3eda5047 # Parent 8f405df1652d4cdf996c22034d8f626cfc42bad3# Parent 7054f810b0f94f03cd1f30eb9109aefa7adb3b0f merge of '02b9e2a56048af7440e6752b0a4d65df7c519ae7' and '5cffa941d7a297ad42b518e4c97e930a0ad54cd5' diff -r 8f405df1652d -r 420850f3236e libpurple/cipher.c --- 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); diff -r 8f405df1652d -r 420850f3236e libpurple/protocols/oscar/Makefile.am --- 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 \ diff -r 8f405df1652d -r 420850f3236e libpurple/protocols/oscar/clientlogin.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); +} diff -r 8f405df1652d -r 420850f3236e libpurple/protocols/oscar/family_auth.c --- 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 #include "cipher.h" -#include +#include "oscar.h" /* #define USE_XOR_FOR_ICQ */ diff -r 8f405df1652d -r 420850f3236e libpurple/protocols/oscar/flap_connection.c --- 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; diff -r 8f405df1652d -r 420850f3236e libpurple/protocols/oscar/oscar.c --- 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); diff -r 8f405df1652d -r 420850f3236e libpurple/protocols/oscar/oscar.h --- 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 diff -r 8f405df1652d -r 420850f3236e libpurple/protocols/oscar/oscar_data.c --- 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); diff -r 8f405df1652d -r 420850f3236e libpurple/protocols/oscar/oscarcommon.h --- 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);