--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/oscar/kerberos.c Fri May 06 16:22:21 2016 -0400 @@ -0,0 +1,468 @@ +/* + * 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 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 "oscar.h" +#include "oscarcommon.h" +#include "core.h" + +#include "ciphers/hmaccipher.h" +#include "ciphers/sha256hash.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 : ""); + if (port_str) + g_free (port_str); + + return url; +} + +/* + * Using clientLogin requires a developer ID. This key is for libpurple. + * It is the default key for all libpurple-based clients. AOL encourages + * UIs (especially ones with lots of users) to override this with their + * own key. This key is owned by the AIM account "markdoliner" + * + * Keys can be managed at http://developer.aim.com/manageKeys.jsp + */ +#define DEFAULT_CLIENT_KEY "ma15d7JTxbmVG-RP" + +static const char *get_client_key(OscarData *od) +{ + return oscar_get_ui_info_string( + od->icq ? "prpl-icq-clientkey" : "prpl-aim-clientkey", + DEFAULT_CLIENT_KEY); +} + +static void +aim_encode_password(const char *password, guint8 *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; + + if (xsnac->principal1) + g_free (xsnac->principal1); + if (xsnac->principal2) + 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); + if (xsnac->tokens[i].principal1) + g_free (xsnac->tokens[i].principal1); + if (xsnac->tokens[i].principal1_again) + if (xsnac->tokens[i].service) + g_free (xsnac->tokens[i].service); + g_free (xsnac->tokens[i].principal1_again); + if (xsnac->tokens[i].principal2) + g_free (xsnac->tokens[i].principal2); + if (xsnac->tokens[i].footer) + g_free (xsnac->tokens[i].footer); + aim_tlvlist_free (xsnac->tokens[i].tlvlist); + } + g_free (xsnac->tokens); +} + +static void +kerberos_login_cb(PurpleHttpConnection *http_conn, + PurpleHttpResponse *response, gpointer _od) +{ + OscarData *od = _od; + PurpleConnection *gc; + const gchar *got_data; + size_t got_len; + 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->hc = NULL; + + if (!purple_http_response_is_successful(response)) { + gchar *tmp; + gchar *url; + + url = get_kdc_url(od); + tmp = g_strdup_printf(_("Error requesting %s: %s"), + url, + purple_http_response_get_error(response)); + purple_connection_error(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); + g_free(tmp); + g_free(url); + return; + } + + got_data = purple_http_response_get_data(response, &got_len); + 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(gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, + _("Incorrect password")); + return; + } + if (xsnac.family != 0x50C || xsnac.subtype != 0x0003) { + purple_connection_error(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 (strcmp (xsnac.tokens[i].service, "im/boss") == 0) { + 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(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unknown error during authentication")); + } + aim_xsnac_free (&xsnac); + if (tlsCertName) + g_free (tlsCertName); + if (bosip) + g_free (bosip); +} + +/** + * 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_kerberos_login(OscarData *od, const char *username) +{ + PurpleConnection *gc; + PurpleHttpRequest *req; + gchar *url; + const gchar *password; + guint8 password_xored[MAXAIMPASSLEN]; + const gchar *client_key; + gchar *imapp_key; + gchar *body; + gint body_len; + 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_len = sizeof(header); + body_len += 2 + strlen (imapp_key); + body_len += sizeof(pre_username); + body_len += 2 + strlen (username); + body_len += sizeof(post_username); + body_len += 2 + sizeof(pre_password); + body_len += 4 + strlen (password); + body_len += sizeof(post_password); + body_len += 2 + strlen (client_key); + body_len += sizeof(footer); + + body = g_malloc (body_len); + + body_len = 0; + reqid = (guint16) g_random_int(); + memcpy (body, header, sizeof(header)); + memcpy (body + 0xC, (void *)&reqid, sizeof(guint16)); + body_len += sizeof(header); + + len_be = GUINT16_TO_BE (strlen (imapp_key)); + memcpy (body + body_len, (void *)&len_be, sizeof(guint16)); + body_len += sizeof(guint16); + memcpy (body + body_len, imapp_key, strlen (imapp_key)); + body_len += strlen (imapp_key); + + memcpy (body + body_len, pre_username, sizeof(pre_username)); + body_len += sizeof(pre_username); + len_be = GUINT16_TO_BE (strlen (username)); + memcpy (body + body_len, (void *)&len_be, sizeof(guint16)); + body_len += sizeof(guint16); + memcpy (body + body_len, username, strlen (username)); + body_len += strlen (username); + memcpy (body + body_len, post_username, sizeof(post_username)); + body_len += sizeof(post_username); + + len_be = GUINT16_TO_BE (strlen (password) + 0x10); + memcpy (body + body_len, (void *)&len_be, sizeof(guint16)); + body_len += sizeof(guint16); + memcpy (body + body_len, pre_password, sizeof(pre_password)); + body_len += sizeof(pre_password); + len_be = GUINT16_TO_BE (strlen (password) + 4); + memcpy (body + body_len, (void *)&len_be, sizeof(guint16)); + body_len += sizeof(guint16); + len_be = GUINT16_TO_BE (strlen (password)); + memcpy (body + body_len, (void *)&len_be, sizeof(guint16)); + body_len += sizeof(guint16); + memcpy (body + body_len, password_xored, strlen (password)); + body_len += strlen (password); + memcpy (body + body_len, post_password, sizeof(post_password)); + body_len += sizeof(post_password); + + len_be = GUINT16_TO_BE (strlen (client_key)); + memcpy (body + body_len, (void *)&len_be, sizeof(guint16)); + body_len += sizeof(guint16); + memcpy (body + body_len, client_key, strlen (client_key)); + body_len += strlen (client_key); + memcpy (body + body_len, footer, sizeof(footer)); + body_len += sizeof(footer); + + g_free(imapp_key); + + url = get_kdc_url(od); + req = purple_http_request_new(url); + purple_http_request_set_method(req, "POST"); + purple_http_request_header_set(req, "Content-Type", + "application/x-snac"); + purple_http_request_header_set(req, "Accept", + "application/x-snac"); + purple_http_request_set_contents(req, body, body_len); + od->hc = purple_http_request(gc, req, kerberos_login_cb, od); + purple_http_request_unref(req); + + g_free (body); + g_free (url); +}