pidgin/pidgin

Added tag v2.14.2 for changeset 2eb25613d054
release-2.x.y
2021-04-01, Gary Kramlich
1dd6e5170860
Added tag v2.14.2 for changeset 2eb25613d054
/*
* 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 kerberos 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 https
* POSTs to AOL KDC server to validate the user based on the password they
* provided to us. Upon successful authentication we receive two tokens
* in the response. One is assumed to be the kerberos ticket for authentication
* on the various AOL websites, while the other contains BOSS information, such
* as the hostname and port number to use, the TLS certificate name as well as
* the cookie to use to authenticate to the BOS server.
* And then everything else is the same as with BUCP.
*
*/
#include "oscar.h"
#include "oscarcommon.h"
#include "core.h"
#define MAXAIMPASSLEN 16
/*
* Incomplete X-SNAC format taken from reverse engineering doen by digsby:
* https://github.com/ifwe/digsby/blob/master/digsby/src/oscar/login2.py
*/
typedef struct {
aim_tlv_t *main_tlv;
gchar *principal1;
gchar *service;
gchar *principal1_again;
gchar *principal2;
gchar unknown;
guint8 *footer;
struct {
guint32 unknown1;
guint32 unknown2;
guint32 epoch_now;
guint32 epoch_valid;
guint32 epoch_renew;
guint32 epoch_expire;
guint32 unknown3;
guint32 unknown4;
guint32 unknown5;
} dates;
GSList *tlvlist;
} aim_xsnac_token_t;
typedef struct {
guint16 family;
guint16 subtype;
guint8 flags[8];
guint16 request_id;
guint32 epoch;
guint32 unknown;
gchar *principal1;
gchar *principal2;
guint16 num_tokens;
aim_xsnac_token_t *tokens;
GSList *tlvlist;
} aim_xsnac_t;
static gchar *get_kdc_url(OscarData *od)
{
PurpleAccount *account = purple_connection_get_account(od->gc);
const gchar *server;
gchar *url;
gchar *port_str = NULL;
gint port;
server = purple_account_get_string(account, "server", AIM_DEFAULT_KDC_SERVER);
port = purple_account_get_int(account, "port", AIM_DEFAULT_KDC_PORT);
if (port != 443)
port_str = g_strdup_printf(":%d", port);
url = g_strdup_printf("https://%s%s/", server, port_str ? port_str : "");
g_free(port_str);
return url;
}
static const char *get_client_key(OscarData *od)
{
return oscar_get_ui_info_string(
od->icq ? "prpl-icq-clientkey" : "prpl-aim-clientkey",
od->icq ? ICQ_DEFAULT_CLIENT_KEY : AIM_DEFAULT_CLIENT_KEY);
}
static void
aim_encode_password(const char *password, gchar *encoded)
{
guint8 encoding_table[] = {
0x76, 0x91, 0xc5, 0xe7,
0xd0, 0xd9, 0x95, 0xdd,
0x9e, 0x2F, 0xea, 0xd8,
0x6B, 0x21, 0xc2, 0xbc,
};
guint i;
/*
* We truncate AIM passwords to 16 characters since that's what
* the official client does as well.
*/
for (i = 0; i < strlen(password) && i < MAXAIMPASSLEN; i++)
encoded[i] = (password[i] ^ encoding_table[i]);
}
static void
aim_xsnac_free(aim_xsnac_t *xsnac)
{
gint i;
g_free(xsnac->principal1);
g_free(xsnac->principal2);
aim_tlvlist_free(xsnac->tlvlist);
for (i = 0; i < xsnac->num_tokens; i++) {
g_free(xsnac->tokens[i].main_tlv->value);
g_free(xsnac->tokens[i].main_tlv);
g_free(xsnac->tokens[i].principal1);
g_free(xsnac->tokens[i].service);
g_free(xsnac->tokens[i].principal1_again);
g_free(xsnac->tokens[i].principal2);
g_free(xsnac->tokens[i].footer);
aim_tlvlist_free(xsnac->tokens[i].tlvlist);
}
g_free(xsnac->tokens);
}
static void
kerberos_login_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
const gchar *got_data, gsize got_len, const gchar *error_message)
{
OscarData *od = user_data;
PurpleConnection *gc;
ByteStream bs;
aim_xsnac_t xsnac = {0};
guint16 len;
gchar *bosip = NULL;
gchar *tlsCertName = NULL;
guint8 *cookie = NULL;
guint32 cookie_len = 0;
char *host; int port;
gsize i;
gc = od->gc;
od->url_data = NULL;
if (error_message != NULL || got_len == 0) {
gchar *tmp;
gchar *url;
url = get_kdc_url(od);
tmp = g_strdup_printf(_("Error requesting %s: %s"),
url, error_message ?
error_message : _("The server returned an empty response"));
purple_connection_error_reason(gc,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
g_free(tmp);
g_free(url);
return;
}
purple_debug_info("oscar", "Received kerberos login HTTP response %lu : ", got_len);
byte_stream_init(&bs, (guint8 *)got_data, got_len);
xsnac.family = byte_stream_get16(&bs);
xsnac.subtype = byte_stream_get16(&bs);
byte_stream_getrawbuf(&bs, (guint8 *) xsnac.flags, 8);
if (xsnac.family == 0x50C && xsnac.subtype == 0x0005) {
purple_connection_error_reason(gc,
PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
_("Incorrect password"));
return;
}
if (xsnac.family != 0x50C || xsnac.subtype != 0x0003) {
purple_connection_error_reason(gc,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
_("Error parsing response from authentication server"));
return;
}
xsnac.request_id = byte_stream_get16(&bs);
xsnac.epoch = byte_stream_get32(&bs);
xsnac.unknown = byte_stream_get32(&bs);
len = byte_stream_get16(&bs);
xsnac.principal1 = byte_stream_getstr(&bs, len);
len = byte_stream_get16(&bs);
xsnac.principal2 = byte_stream_getstr(&bs, len);
xsnac.num_tokens = byte_stream_get16(&bs);
purple_debug_info("oscar", "KDC: %d tokens between '%s' and '%s'\n",
xsnac.num_tokens, xsnac.principal1, xsnac.principal2);
xsnac.tokens = g_new0(aim_xsnac_token_t, xsnac.num_tokens);
for (i = 0; i < xsnac.num_tokens; i++) {
GSList *tlv;
tlv = aim_tlvlist_readnum(&bs, 1);
if (tlv)
xsnac.tokens[i].main_tlv = tlv->data;
g_slist_free(tlv);
len = byte_stream_get16(&bs);
xsnac.tokens[i].principal1 = byte_stream_getstr(&bs, len);
len = byte_stream_get16(&bs);
xsnac.tokens[i].service = byte_stream_getstr(&bs, len);
len = byte_stream_get16(&bs);
xsnac.tokens[i].principal1_again = byte_stream_getstr(&bs, len);
len = byte_stream_get16(&bs);
xsnac.tokens[i].principal2 = byte_stream_getstr(&bs, len);
xsnac.tokens[i].unknown = byte_stream_get8(&bs);
len = byte_stream_get16(&bs);
xsnac.tokens[i].footer = byte_stream_getraw(&bs, len);
xsnac.tokens[i].dates.unknown1 = byte_stream_get32(&bs);
xsnac.tokens[i].dates.unknown2 = byte_stream_get32(&bs);
xsnac.tokens[i].dates.epoch_now = byte_stream_get32(&bs);
xsnac.tokens[i].dates.epoch_valid = byte_stream_get32(&bs);
xsnac.tokens[i].dates.epoch_renew = byte_stream_get32(&bs);
xsnac.tokens[i].dates.epoch_expire = byte_stream_get32(&bs);
xsnac.tokens[i].dates.unknown3 = byte_stream_get32(&bs);
xsnac.tokens[i].dates.unknown4 = byte_stream_get32(&bs);
xsnac.tokens[i].dates.unknown5 = byte_stream_get32(&bs);
len = byte_stream_get16(&bs);
xsnac.tokens[i].tlvlist = aim_tlvlist_readnum(&bs, len);
purple_debug_info("oscar", "Token %lu has %d TLVs for service '%s'\n",
i, len, xsnac.tokens[i].service);
}
len = byte_stream_get16(&bs);
xsnac.tlvlist = aim_tlvlist_readnum(&bs, len);
for (i = 0; i < xsnac.num_tokens; i++) {
if (purple_strequal(xsnac.tokens[i].service, "im/boss")) {
aim_tlv_t *tlv;
GSList *tlvlist;
ByteStream tbs;
tlv = aim_tlv_gettlv(xsnac.tokens[i].tlvlist, 0x0003, 1);
if (tlv != NULL) {
byte_stream_init(&tbs, tlv->value, tlv->length);
byte_stream_get32(&tbs);
tlvlist = aim_tlvlist_read(&tbs);
if (aim_tlv_gettlv(tlvlist, 0x0005, 1))
bosip = aim_tlv_getstr(tlvlist, 0x0005, 1);
if (aim_tlv_gettlv(tlvlist, 0x0005, 1))
tlsCertName = aim_tlv_getstr(tlvlist, 0x008D, 1);
tlv = aim_tlv_gettlv(tlvlist, 0x0006, 1);
if (tlv) {
cookie_len = tlv->length;
cookie = tlv->value;
}
}
break;
}
}
if (bosip && cookie) {
port = AIM_DEFAULT_KDC_PORT;
for (i = 0; i < strlen(bosip); i++) {
if (bosip[i] == ':') {
port = atoi(&(bosip[i+1]));
break;
}
}
host = g_strndup(bosip, i);
oscar_connect_to_bos(gc, od, host, port, cookie, cookie_len, tlsCertName);
g_free(host);
} else {
purple_connection_error_reason(gc,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
_("Unknown error during authentication"));
}
aim_xsnac_free(&xsnac);
g_free(tlsCertName);
g_free(bosip);
}
/**
* This function sends a binary blob request to the Kerberos KDC server
* https://kdc.uas.aol.com with the user's username and password and
* receives the IM cookie, which is used to request a connection to the
* BOSS server.
* The binary data below is what AIM 8.0.8.1 sends in order to authenticate
* to the KDC server. It is an 'X-SNAC' packet, which is relatively similar
* to SNAC packets but somehow different.
* The header starts with the 0x50C family follow by 0x0002 subtype, then
* some fixed length data and TLVs. The string "COOL" appears in there for
* some reason followed by the 'US' and 'en' strings.
* Then the 'imApp key=<client key>' comes after that, and then the username
* and the string "im/boss" which seems to represent the service we are
* requesting the authentication for. Changing that will lead to a
* 'unknown service' error. The client key is then added again (without the
* 'imApp key' string prepended to it) then a XOR-ed version of the password.
* The meaning of the header/footer/in-between bytes is not known but never
* seems to change so there is no need to reverse engineer their meaning at
* this point.
*/
void send_kerberos_login(OscarData *od, const char *username)
{
PurpleConnection *gc;
GString *request;
gchar *url;
const gchar *password;
gchar password_xored[MAXAIMPASSLEN];
const gchar *client_key;
gchar *imapp_key;
GString *body;
guint16 len_be;
guint16 reqid;
const gchar header[] = {
0x05, 0x0C, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
0x00, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x05,
0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05,
0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x18, 0x99,
0x00, 0x05, 0x00, 0x04, 0x43, 0x4F, 0x4F, 0x4C,
0x00, 0x0A, 0x00, 0x02, 0x00, 0x01, 0x00, 0x0B,
0x00, 0x04, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00,
0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
0x55, 0x53, 0x00, 0x02, 0x65, 0x6E, 0x00, 0x04,
0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0D,
0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04,
0x00, 0x05};
const gchar pre_username[] = {
0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x01, 0x8B,
0x01, 0x00, 0x00, 0x00, 0x00};
const gchar post_username[] = {
0x00, 0x07, 0x69, 0x6D, 0x2F, 0x62, 0x6F, 0x73,
0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x02};
const gchar pre_password[] = {
0x40, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01,
0x00, 0x00};
const gchar post_password[] = {0x00, 0x00, 0x00, 0x1D};
const gchar footer[] = {
0x00, 0x21, 0x00, 0x32, 0x00, 0x01, 0x10, 0x03,
0x00, 0x2C, 0x00, 0x07, 0x00, 0x14, 0x00, 0x04,
0x00, 0x00, 0x01, 0x8B, 0x00, 0x16, 0x00, 0x02,
0x00, 0x26, 0x00, 0x17, 0x00, 0x02, 0x00, 0x07,
0x00, 0x18, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19,
0x00, 0x02, 0x00, 0x0D, 0x00, 0x1A, 0x00, 0x02,
0x00, 0x04, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x28,
0x00, 0x00};
gc = od->gc;
password = purple_connection_get_password(gc);
aim_encode_password(password, password_xored);
client_key = get_client_key(od);
imapp_key = g_strdup_printf("imApp key=%s", client_key);
/* Construct the body of the HTTP POST request */
body = g_string_new(NULL);
g_string_append_len(body, header, sizeof(header));
reqid = (guint16) g_random_int();
g_string_overwrite_len(body, 0xC, (void *)&reqid, sizeof(guint16));
len_be = GUINT16_TO_BE(strlen(imapp_key));
g_string_append_len(body, (void *)&len_be, sizeof(guint16));
g_string_append(body, imapp_key);
len_be = GUINT16_TO_BE(strlen(username));
g_string_append_len(body, pre_username, sizeof(pre_username));
g_string_append_len(body, (void *)&len_be, sizeof(guint16));
g_string_append(body, username);
g_string_append_len(body, post_username, sizeof(post_username));
len_be = GUINT16_TO_BE(strlen(password) + 0x10);
g_string_append_len(body, (void *)&len_be, sizeof(guint16));
g_string_append_len(body, pre_password, sizeof(pre_password));
len_be = GUINT16_TO_BE(strlen(password) + 4);
g_string_append_len(body, (void *)&len_be, sizeof(guint16));
len_be = GUINT16_TO_BE(strlen(password));
g_string_append_len(body, (void *)&len_be, sizeof(guint16));
g_string_append_len(body, password_xored, strlen(password));
g_string_append_len(body, post_password, sizeof(post_password));
len_be = GUINT16_TO_BE(strlen(client_key));
g_string_append_len(body, (void *)&len_be, sizeof(guint16));
g_string_append(body, client_key);
g_string_append_len(body, footer, sizeof(footer));
g_free(imapp_key);
url = get_kdc_url(od);
/* Construct an HTTP POST request */
request = g_string_new("POST / HTTP/1.1\n"
"Connection: close\n"
"Accept: application/x-snac\n");
/* Tack on the body */
g_string_append_printf(request, "Content-Type: application/x-snac\n");
g_string_append_printf(request, "Content-Length: %" G_GSIZE_FORMAT "\n\n", body->len);
g_string_append_len(request, body->str, body->len);
/* Send the POST request */
od->url_data = purple_util_fetch_url_request_data_len_with_account(
purple_connection_get_account(gc), url,
TRUE, NULL, TRUE, request->str, request->len, FALSE, -1,
kerberos_login_cb, od);
g_string_free(request, TRUE);
g_string_free(body, TRUE);
g_free(url);
}