File:  [Coherent Logic Development] / ChivanetAimPidgin / oscarprpl / src / c / kerberos.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Mon Jan 27 19:48:25 2025 UTC (6 months ago) by snw
Branches: MAIN, CoherentLogicDevelopment
CVS tags: test-tag, start, HEAD
Pidgin AIM Plugin for ChivaNet

    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>