Annotation of ChivanetAimPidgin/oscarprpl/src/c/kerberos.c, revision 1.1
1.1 ! snw 1: /*
! 2: * Purple's oscar protocol plugin
! 3: * This file is the legal property of its developers.
! 4: * Please see the AUTHORS file distributed alongside this file.
! 5: *
! 6: * This library is free software; you can redistribute it and/or
! 7: * modify it under the terms of the GNU Lesser General Public
! 8: * License as published by the Free Software Foundation; either
! 9: * version 2 of the License, or (at your option) any later version.
! 10: *
! 11: * This library is distributed in the hope that it will be useful,
! 12: * but WITHOUT ANY WARRANTY; without even the implied warranty of
! 13: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
! 14: * Lesser General Public License for more details.
! 15: *
! 16: * You should have received a copy of the GNU Lesser General Public
! 17: * License along with this library; if not, write to the Free Software
! 18: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
! 19: */
! 20:
! 21: /**
! 22: * This file implements AIM's kerberos procedure for authenticating
! 23: * users. This replaces the older MD5-based and XOR-based
! 24: * authentication methods that use SNAC family 0x0017.
! 25: *
! 26: * This doesn't use SNACs or FLAPs at all. It makes https
! 27: * POSTs to AOL KDC server to validate the user based on the password they
! 28: * provided to us. Upon successful authentication we receive two tokens
! 29: * in the response. One is assumed to be the kerberos ticket for authentication
! 30: * on the various AOL websites, while the other contains BOSS information, such
! 31: * as the hostname and port number to use, the TLS certificate name as well as
! 32: * the cookie to use to authenticate to the BOS server.
! 33: * And then everything else is the same as with BUCP.
! 34: *
! 35: */
! 36:
! 37: #include "oscar.h"
! 38: #include "oscarcommon.h"
! 39: #include "core.h"
! 40:
! 41: #define MAXAIMPASSLEN 16
! 42:
! 43: /*
! 44: * Incomplete X-SNAC format taken from reverse engineering doen by digsby:
! 45: * https://github.com/ifwe/digsby/blob/master/digsby/src/oscar/login2.py
! 46: */
! 47: typedef struct {
! 48: aim_tlv_t *main_tlv;
! 49: gchar *principal1;
! 50: gchar *service;
! 51: gchar *principal1_again;
! 52: gchar *principal2;
! 53: gchar unknown;
! 54: guint8 *footer;
! 55: struct {
! 56: guint32 unknown1;
! 57: guint32 unknown2;
! 58: guint32 epoch_now;
! 59: guint32 epoch_valid;
! 60: guint32 epoch_renew;
! 61: guint32 epoch_expire;
! 62: guint32 unknown3;
! 63: guint32 unknown4;
! 64: guint32 unknown5;
! 65: } dates;
! 66: GSList *tlvlist;
! 67: } aim_xsnac_token_t;
! 68:
! 69: typedef struct {
! 70: guint16 family;
! 71: guint16 subtype;
! 72: guint8 flags[8];
! 73: guint16 request_id;
! 74: guint32 epoch;
! 75: guint32 unknown;
! 76: gchar *principal1;
! 77: gchar *principal2;
! 78: guint16 num_tokens;
! 79: aim_xsnac_token_t *tokens;
! 80: GSList *tlvlist;
! 81: } aim_xsnac_t;
! 82:
! 83: static gchar *get_kdc_url(OscarData *od)
! 84: {
! 85: PurpleAccount *account = purple_connection_get_account(od->gc);
! 86: const gchar *server;
! 87: gchar *url;
! 88: gchar *port_str = NULL;
! 89: gint port;
! 90:
! 91: server = purple_account_get_string(account, "server", AIM_DEFAULT_KDC_SERVER);
! 92: port = purple_account_get_int(account, "port", AIM_DEFAULT_KDC_PORT);
! 93: if (port != 443)
! 94: port_str = g_strdup_printf(":%d", port);
! 95: url = g_strdup_printf("https://%s%s/", server, port_str ? port_str : "");
! 96: g_free(port_str);
! 97:
! 98: return url;
! 99: }
! 100:
! 101: static const char *get_client_key(OscarData *od)
! 102: {
! 103: return oscar_get_ui_info_string(
! 104: od->icq ? "prpl-icq-clientkey" : "prpl-aim-clientkey",
! 105: od->icq ? ICQ_DEFAULT_CLIENT_KEY : AIM_DEFAULT_CLIENT_KEY);
! 106: }
! 107:
! 108: static void
! 109: aim_encode_password(const char *password, gchar *encoded)
! 110: {
! 111: guint8 encoding_table[] = {
! 112: 0x76, 0x91, 0xc5, 0xe7,
! 113: 0xd0, 0xd9, 0x95, 0xdd,
! 114: 0x9e, 0x2F, 0xea, 0xd8,
! 115: 0x6B, 0x21, 0xc2, 0xbc,
! 116:
! 117: };
! 118: guint i;
! 119:
! 120: /*
! 121: * We truncate AIM passwords to 16 characters since that's what
! 122: * the official client does as well.
! 123: */
! 124: for (i = 0; i < strlen(password) && i < MAXAIMPASSLEN; i++)
! 125: encoded[i] = (password[i] ^ encoding_table[i]);
! 126: }
! 127:
! 128: static void
! 129: aim_xsnac_free(aim_xsnac_t *xsnac)
! 130: {
! 131: gint i;
! 132:
! 133: g_free(xsnac->principal1);
! 134: g_free(xsnac->principal2);
! 135: aim_tlvlist_free(xsnac->tlvlist);
! 136:
! 137: for (i = 0; i < xsnac->num_tokens; i++) {
! 138: g_free(xsnac->tokens[i].main_tlv->value);
! 139: g_free(xsnac->tokens[i].main_tlv);
! 140: g_free(xsnac->tokens[i].principal1);
! 141: g_free(xsnac->tokens[i].service);
! 142: g_free(xsnac->tokens[i].principal1_again);
! 143: g_free(xsnac->tokens[i].principal2);
! 144: g_free(xsnac->tokens[i].footer);
! 145: aim_tlvlist_free(xsnac->tokens[i].tlvlist);
! 146: }
! 147: g_free(xsnac->tokens);
! 148: }
! 149:
! 150: static void
! 151: kerberos_login_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
! 152: const gchar *got_data, gsize got_len, const gchar *error_message)
! 153: {
! 154: OscarData *od = user_data;
! 155: PurpleConnection *gc;
! 156: ByteStream bs;
! 157: aim_xsnac_t xsnac = {0};
! 158: guint16 len;
! 159: gchar *bosip = NULL;
! 160: gchar *tlsCertName = NULL;
! 161: guint8 *cookie = NULL;
! 162: guint32 cookie_len = 0;
! 163: char *host; int port;
! 164: gsize i;
! 165:
! 166: gc = od->gc;
! 167:
! 168: od->url_data = NULL;
! 169:
! 170: if (error_message != NULL || got_len == 0) {
! 171: gchar *tmp;
! 172: gchar *url;
! 173:
! 174: url = get_kdc_url(od);
! 175: tmp = g_strdup_printf(_("Error requesting %s: %s"),
! 176: url, error_message ?
! 177: error_message : _("The server returned an empty response"));
! 178: purple_connection_error_reason(gc,
! 179: PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
! 180: g_free(tmp);
! 181: g_free(url);
! 182: return;
! 183: }
! 184:
! 185: purple_debug_info("oscar", "Received kerberos login HTTP response %lu : ", got_len);
! 186:
! 187: byte_stream_init(&bs, (guint8 *)got_data, got_len);
! 188:
! 189: xsnac.family = byte_stream_get16(&bs);
! 190: xsnac.subtype = byte_stream_get16(&bs);
! 191: byte_stream_getrawbuf(&bs, (guint8 *) xsnac.flags, 8);
! 192:
! 193: if (xsnac.family == 0x50C && xsnac.subtype == 0x0005) {
! 194: purple_connection_error_reason(gc,
! 195: PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
! 196: _("Incorrect password"));
! 197: return;
! 198: }
! 199: if (xsnac.family != 0x50C || xsnac.subtype != 0x0003) {
! 200: purple_connection_error_reason(gc,
! 201: PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
! 202: _("Error parsing response from authentication server"));
! 203: return;
! 204: }
! 205: xsnac.request_id = byte_stream_get16(&bs);
! 206: xsnac.epoch = byte_stream_get32(&bs);
! 207: xsnac.unknown = byte_stream_get32(&bs);
! 208: len = byte_stream_get16(&bs);
! 209: xsnac.principal1 = byte_stream_getstr(&bs, len);
! 210: len = byte_stream_get16(&bs);
! 211: xsnac.principal2 = byte_stream_getstr(&bs, len);
! 212: xsnac.num_tokens = byte_stream_get16(&bs);
! 213:
! 214: purple_debug_info("oscar", "KDC: %d tokens between '%s' and '%s'\n",
! 215: xsnac.num_tokens, xsnac.principal1, xsnac.principal2);
! 216: xsnac.tokens = g_new0(aim_xsnac_token_t, xsnac.num_tokens);
! 217: for (i = 0; i < xsnac.num_tokens; i++) {
! 218: GSList *tlv;
! 219:
! 220: tlv = aim_tlvlist_readnum(&bs, 1);
! 221: if (tlv)
! 222: xsnac.tokens[i].main_tlv = tlv->data;
! 223: g_slist_free(tlv);
! 224:
! 225: len = byte_stream_get16(&bs);
! 226: xsnac.tokens[i].principal1 = byte_stream_getstr(&bs, len);
! 227: len = byte_stream_get16(&bs);
! 228: xsnac.tokens[i].service = byte_stream_getstr(&bs, len);
! 229: len = byte_stream_get16(&bs);
! 230: xsnac.tokens[i].principal1_again = byte_stream_getstr(&bs, len);
! 231: len = byte_stream_get16(&bs);
! 232: xsnac.tokens[i].principal2 = byte_stream_getstr(&bs, len);
! 233: xsnac.tokens[i].unknown = byte_stream_get8(&bs);
! 234: len = byte_stream_get16(&bs);
! 235: xsnac.tokens[i].footer = byte_stream_getraw(&bs, len);
! 236:
! 237: xsnac.tokens[i].dates.unknown1 = byte_stream_get32(&bs);
! 238: xsnac.tokens[i].dates.unknown2 = byte_stream_get32(&bs);
! 239: xsnac.tokens[i].dates.epoch_now = byte_stream_get32(&bs);
! 240: xsnac.tokens[i].dates.epoch_valid = byte_stream_get32(&bs);
! 241: xsnac.tokens[i].dates.epoch_renew = byte_stream_get32(&bs);
! 242: xsnac.tokens[i].dates.epoch_expire = byte_stream_get32(&bs);
! 243: xsnac.tokens[i].dates.unknown3 = byte_stream_get32(&bs);
! 244: xsnac.tokens[i].dates.unknown4 = byte_stream_get32(&bs);
! 245: xsnac.tokens[i].dates.unknown5 = byte_stream_get32(&bs);
! 246:
! 247: len = byte_stream_get16(&bs);
! 248: xsnac.tokens[i].tlvlist = aim_tlvlist_readnum(&bs, len);
! 249:
! 250: purple_debug_info("oscar", "Token %lu has %d TLVs for service '%s'\n",
! 251: i, len, xsnac.tokens[i].service);
! 252: }
! 253: len = byte_stream_get16(&bs);
! 254: xsnac.tlvlist = aim_tlvlist_readnum(&bs, len);
! 255:
! 256: for (i = 0; i < xsnac.num_tokens; i++) {
! 257: if (purple_strequal(xsnac.tokens[i].service, "im/boss")) {
! 258: aim_tlv_t *tlv;
! 259: GSList *tlvlist;
! 260: ByteStream tbs;
! 261:
! 262: tlv = aim_tlv_gettlv(xsnac.tokens[i].tlvlist, 0x0003, 1);
! 263: if (tlv != NULL) {
! 264: byte_stream_init(&tbs, tlv->value, tlv->length);
! 265: byte_stream_get32(&tbs);
! 266: tlvlist = aim_tlvlist_read(&tbs);
! 267: if (aim_tlv_gettlv(tlvlist, 0x0005, 1))
! 268: bosip = aim_tlv_getstr(tlvlist, 0x0005, 1);
! 269: if (aim_tlv_gettlv(tlvlist, 0x0005, 1))
! 270: tlsCertName = aim_tlv_getstr(tlvlist, 0x008D, 1);
! 271: tlv = aim_tlv_gettlv(tlvlist, 0x0006, 1);
! 272: if (tlv) {
! 273: cookie_len = tlv->length;
! 274: cookie = tlv->value;
! 275: }
! 276: }
! 277: break;
! 278: }
! 279: }
! 280: if (bosip && cookie) {
! 281: port = AIM_DEFAULT_KDC_PORT;
! 282: for (i = 0; i < strlen(bosip); i++) {
! 283: if (bosip[i] == ':') {
! 284: port = atoi(&(bosip[i+1]));
! 285: break;
! 286: }
! 287: }
! 288: host = g_strndup(bosip, i);
! 289: oscar_connect_to_bos(gc, od, host, port, cookie, cookie_len, tlsCertName);
! 290: g_free(host);
! 291: } else {
! 292: purple_connection_error_reason(gc,
! 293: PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
! 294: _("Unknown error during authentication"));
! 295: }
! 296: aim_xsnac_free(&xsnac);
! 297: g_free(tlsCertName);
! 298: g_free(bosip);
! 299: }
! 300:
! 301: /**
! 302: * This function sends a binary blob request to the Kerberos KDC server
! 303: * https://kdc.uas.aol.com with the user's username and password and
! 304: * receives the IM cookie, which is used to request a connection to the
! 305: * BOSS server.
! 306: * The binary data below is what AIM 8.0.8.1 sends in order to authenticate
! 307: * to the KDC server. It is an 'X-SNAC' packet, which is relatively similar
! 308: * to SNAC packets but somehow different.
! 309: * The header starts with the 0x50C family follow by 0x0002 subtype, then
! 310: * some fixed length data and TLVs. The string "COOL" appears in there for
! 311: * some reason followed by the 'US' and 'en' strings.
! 312: * Then the 'imApp key=<client key>' comes after that, and then the username
! 313: * and the string "im/boss" which seems to represent the service we are
! 314: * requesting the authentication for. Changing that will lead to a
! 315: * 'unknown service' error. The client key is then added again (without the
! 316: * 'imApp key' string prepended to it) then a XOR-ed version of the password.
! 317: * The meaning of the header/footer/in-between bytes is not known but never
! 318: * seems to change so there is no need to reverse engineer their meaning at
! 319: * this point.
! 320: */
! 321: void send_kerberos_login(OscarData *od, const char *username)
! 322: {
! 323: PurpleConnection *gc;
! 324: GString *request;
! 325: gchar *url;
! 326: const gchar *password;
! 327: gchar password_xored[MAXAIMPASSLEN];
! 328: const gchar *client_key;
! 329: gchar *imapp_key;
! 330: GString *body;
! 331: guint16 len_be;
! 332: guint16 reqid;
! 333: const gchar header[] = {
! 334: 0x05, 0x0C, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
! 335: 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
! 336: 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
! 337: 0x00, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x05,
! 338: 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05,
! 339: 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x18, 0x99,
! 340: 0x00, 0x05, 0x00, 0x04, 0x43, 0x4F, 0x4F, 0x4C,
! 341: 0x00, 0x0A, 0x00, 0x02, 0x00, 0x01, 0x00, 0x0B,
! 342: 0x00, 0x04, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00,
! 343: 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
! 344: 0x55, 0x53, 0x00, 0x02, 0x65, 0x6E, 0x00, 0x04,
! 345: 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0D,
! 346: 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04,
! 347: 0x00, 0x05};
! 348: const gchar pre_username[] = {
! 349: 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x01, 0x8B,
! 350: 0x01, 0x00, 0x00, 0x00, 0x00};
! 351: const gchar post_username[] = {
! 352: 0x00, 0x07, 0x69, 0x6D, 0x2F, 0x62, 0x6F, 0x73,
! 353: 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
! 354: 0x04, 0x00, 0x02};
! 355: const gchar pre_password[] = {
! 356: 0x40, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01,
! 357: 0x00, 0x00};
! 358: const gchar post_password[] = {0x00, 0x00, 0x00, 0x1D};
! 359: const gchar footer[] = {
! 360: 0x00, 0x21, 0x00, 0x32, 0x00, 0x01, 0x10, 0x03,
! 361: 0x00, 0x2C, 0x00, 0x07, 0x00, 0x14, 0x00, 0x04,
! 362: 0x00, 0x00, 0x01, 0x8B, 0x00, 0x16, 0x00, 0x02,
! 363: 0x00, 0x26, 0x00, 0x17, 0x00, 0x02, 0x00, 0x07,
! 364: 0x00, 0x18, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19,
! 365: 0x00, 0x02, 0x00, 0x0D, 0x00, 0x1A, 0x00, 0x02,
! 366: 0x00, 0x04, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x28,
! 367: 0x00, 0x00};
! 368:
! 369: gc = od->gc;
! 370:
! 371: password = purple_connection_get_password(gc);
! 372: aim_encode_password(password, password_xored);
! 373:
! 374: client_key = get_client_key(od);
! 375: imapp_key = g_strdup_printf("imApp key=%s", client_key);
! 376:
! 377: /* Construct the body of the HTTP POST request */
! 378: body = g_string_new(NULL);
! 379: g_string_append_len(body, header, sizeof(header));
! 380: reqid = (guint16) g_random_int();
! 381: g_string_overwrite_len(body, 0xC, (void *)&reqid, sizeof(guint16));
! 382:
! 383: len_be = GUINT16_TO_BE(strlen(imapp_key));
! 384: g_string_append_len(body, (void *)&len_be, sizeof(guint16));
! 385: g_string_append(body, imapp_key);
! 386:
! 387: len_be = GUINT16_TO_BE(strlen(username));
! 388: g_string_append_len(body, pre_username, sizeof(pre_username));
! 389: g_string_append_len(body, (void *)&len_be, sizeof(guint16));
! 390: g_string_append(body, username);
! 391: g_string_append_len(body, post_username, sizeof(post_username));
! 392:
! 393: len_be = GUINT16_TO_BE(strlen(password) + 0x10);
! 394: g_string_append_len(body, (void *)&len_be, sizeof(guint16));
! 395: g_string_append_len(body, pre_password, sizeof(pre_password));
! 396: len_be = GUINT16_TO_BE(strlen(password) + 4);
! 397: g_string_append_len(body, (void *)&len_be, sizeof(guint16));
! 398: len_be = GUINT16_TO_BE(strlen(password));
! 399: g_string_append_len(body, (void *)&len_be, sizeof(guint16));
! 400: g_string_append_len(body, password_xored, strlen(password));
! 401: g_string_append_len(body, post_password, sizeof(post_password));
! 402:
! 403: len_be = GUINT16_TO_BE(strlen(client_key));
! 404: g_string_append_len(body, (void *)&len_be, sizeof(guint16));
! 405: g_string_append(body, client_key);
! 406: g_string_append_len(body, footer, sizeof(footer));
! 407:
! 408: g_free(imapp_key);
! 409:
! 410: url = get_kdc_url(od);
! 411:
! 412: /* Construct an HTTP POST request */
! 413: request = g_string_new("POST / HTTP/1.1\n"
! 414: "Connection: close\n"
! 415: "Accept: application/x-snac\n"
! 416: "Host: kdc.uas.nina.chat\n");
! 417:
! 418: /* Tack on the body */
! 419: g_string_append_printf(request, "Content-Type: application/x-snac\n");
! 420: g_string_append_printf(request, "Content-Length: %" G_GSIZE_FORMAT "\n\n", body->len);
! 421: g_string_append_len(request, body->str, body->len);
! 422:
! 423: /* Send the POST request */
! 424: od->url_data = purple_util_fetch_url_request_data_len_with_account(
! 425: purple_connection_get_account(gc), url,
! 426: TRUE, NULL, TRUE, request->str, request->len, FALSE, -1,
! 427: kerberos_login_cb, od);
! 428: g_string_free(request, TRUE);
! 429:
! 430: g_string_free(body, TRUE);
! 431: g_free(url);
! 432: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>