File:  [Coherent Logic Development] / ChivanetAimPidgin / oscarprpl / src / c / clientlogin.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 clientLogin 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 http and https
   27:  * POSTs to AOL to validate the user based on the password they
   28:  * provided to us.  Upon successful authentication we request a
   29:  * connection to the BOS server by calling startOSCARsession.  The
   30:  * AOL server gives us the hostname and port number to use, as well
   31:  * as the cookie to use to authenticate to the BOS server.  And then
   32:  * everything else is the same as with BUCP.
   33:  *
   34:  * For details, see:
   35:  * http://dev.aol.com/aim/oscar/#AUTH
   36:  * http://dev.aol.com/authentication_for_clients
   37:  */
   38: 
   39: #include "oscar.h"
   40: #include "oscarcommon.h"
   41: 
   42: #include "cipher.h"
   43: #include "core.h"
   44: 
   45: #define AIM_LOGIN_HOST "api.nina.chat"
   46: #define ICQ_LOGIN_HOST "api.nina.chat"
   47: 
   48: #define AIM_API_HOST "api.nina.chat"
   49: #define ICQ_API_HOST "api.nina.chat"
   50: 
   51: #define CLIENT_LOGIN_PAGE "/auth/clientLogin"
   52: #define START_OSCAR_SESSION_PAGE "/aim/startOSCARSession"
   53: 
   54: #define HTTPS_FORMAT_URL(host, page) "https://" host page
   55: 
   56: static const gchar *client_login_urls[] = {
   57: 	HTTPS_FORMAT_URL(AIM_LOGIN_HOST, CLIENT_LOGIN_PAGE),
   58: 	HTTPS_FORMAT_URL(ICQ_LOGIN_HOST, CLIENT_LOGIN_PAGE),
   59: };
   60: 
   61: static const gchar *start_oscar_session_urls[] = {
   62: 	HTTPS_FORMAT_URL(AIM_API_HOST, START_OSCAR_SESSION_PAGE),
   63: 	HTTPS_FORMAT_URL(ICQ_API_HOST, START_OSCAR_SESSION_PAGE),
   64: };
   65: 
   66: static const gchar *get_client_login_url(OscarData *od)
   67: {
   68: 	return client_login_urls[od->icq ? 1 : 0];
   69: }
   70: 
   71: static const gchar *get_start_oscar_session_url(OscarData *od)
   72: {
   73: 	return start_oscar_session_urls[od->icq ? 1 : 0];
   74: }
   75: 
   76: static const char *get_client_key(OscarData *od)
   77: {
   78: 	return oscar_get_ui_info_string(
   79: 			od->icq ? "prpl-icq-clientkey" : "prpl-aim-clientkey",
   80: 			od->icq ? ICQ_DEFAULT_CLIENT_KEY : AIM_DEFAULT_CLIENT_KEY);
   81: }
   82: 
   83: static gchar *generate_error_message(xmlnode *resp, const char *url)
   84: {
   85: 	xmlnode *text;
   86: 	xmlnode *status_code_node;
   87: 	gboolean have_error_code = TRUE;
   88: 	gchar *err = NULL;
   89: 	gchar *details = NULL;
   90: 
   91: 	status_code_node = xmlnode_get_child(resp, "statusCode");
   92: 	if (status_code_node) {
   93: 		gchar *status_code;
   94: 
   95: 		/* We can get 200 OK here if the server omitted something we think it shouldn't have (see #12783).
   96: 		 * No point in showing the "Ok" string to the user.
   97: 		 */
   98: 		status_code = xmlnode_get_data_unescaped(status_code_node);
   99: 		if (purple_strequal(status_code, "200")) {
  100: 			have_error_code = FALSE;
  101: 		}
  102: 	}
  103: 	if (have_error_code && resp && (text = xmlnode_get_child(resp, "statusText"))) {
  104: 		details = xmlnode_get_data(text);
  105: 	}
  106: 
  107: 	if (details && *details) {
  108: 		err = g_strdup_printf(_("Received unexpected response from %s: %s"), url, details);
  109: 	} else {
  110: 		err = g_strdup_printf(_("Received unexpected response from %s"), url);
  111: 	}
  112: 
  113: 	g_free(details);
  114: 	return err;
  115: }
  116: 
  117: /**
  118:  * @return A null-terminated base64 encoded version of the HMAC
  119:  *         calculated using the given key and data.
  120:  */
  121: static gchar *hmac_sha256(const char *key, const char *message)
  122: {
  123: 	PurpleCipherContext *context;
  124: 	guchar digest[32];
  125: 
  126: 	context = purple_cipher_context_new_by_name("hmac", NULL);
  127: 	purple_cipher_context_set_option(context, "hash", "sha256");
  128: 	purple_cipher_context_set_key(context, (guchar *)key);
  129: 	purple_cipher_context_append(context, (guchar *)message, strlen(message));
  130: 	purple_cipher_context_digest(context, sizeof(digest), digest, NULL);
  131: 	purple_cipher_context_destroy(context);
  132: 
  133: 	return purple_base64_encode(digest, sizeof(digest));
  134: }
  135: 
  136: /**
  137:  * @return A base-64 encoded HMAC-SHA256 signature created using the
  138:  *         technique documented at
  139:  *         http://dev.aol.com/authentication_for_clients#signing
  140:  */
  141: static gchar *generate_signature(const char *method, const char *url, const char *parameters, const char *session_key)
  142: {
  143: 	char *encoded_url, *signature_base_string, *signature;
  144: 	const char *encoded_parameters;
  145: 
  146: 	encoded_url = g_strdup(purple_url_encode(url));
  147: 	encoded_parameters = purple_url_encode(parameters);
  148: 	signature_base_string = g_strdup_printf("%s&%s&%s",
  149: 			method, encoded_url, encoded_parameters);
  150: 	g_free(encoded_url);
  151: 
  152: 	signature = hmac_sha256(session_key, signature_base_string);
  153: 	g_free(signature_base_string);
  154: 
  155: 	return signature;
  156: }
  157: 
  158: static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **host, unsigned short *port, char **cookie, char **tls_certname)
  159: {
  160: 	OscarData *od = purple_connection_get_protocol_data(gc);
  161: 	xmlnode *response_node, *tmp_node, *data_node;
  162: 	xmlnode *host_node = NULL, *port_node = NULL, *cookie_node = NULL, *tls_node = NULL;
  163: 	char *tmp;
  164: 	guint code;
  165: 	const gchar *encryption_type = purple_account_get_string(purple_connection_get_account(gc), "encryption", OSCAR_DEFAULT_ENCRYPTION);
  166: 
  167: 	/* Parse the response as XML */
  168: 	response_node = xmlnode_from_str(response, response_len);
  169: 	if (response_node == NULL)
  170: 	{
  171: 		char *msg;
  172: 		purple_debug_error("oscar", "startOSCARSession could not parse "
  173: 				"response as XML: %s\n", response);
  174: 		/* Note to translators: %s in this string is a URL */
  175: 		msg = generate_error_message(response_node,
  176: 				get_start_oscar_session_url(od));
  177: 		purple_connection_error_reason(gc,
  178: 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
  179: 		g_free(msg);
  180: 		return FALSE;
  181: 	}
  182: 
  183: 	/* Grab the necessary XML nodes */
  184: 	tmp_node = xmlnode_get_child(response_node, "statusCode");
  185: 	data_node = xmlnode_get_child(response_node, "data");
  186: 	if (data_node != NULL) {
  187: 		host_node = xmlnode_get_child(data_node, "host");
  188: 		port_node = xmlnode_get_child(data_node, "port");
  189: 		cookie_node = xmlnode_get_child(data_node, "cookie");
  190: 	}
  191: 
  192: 	/* Make sure we have a status code */
  193: 	if (tmp_node == NULL || (tmp = xmlnode_get_data_unescaped(tmp_node)) == NULL) {
  194: 		char *msg;
  195: 		purple_debug_error("oscar", "startOSCARSession response was "
  196: 				"missing statusCode: %s\n", response);
  197: 		msg = generate_error_message(response_node,
  198: 				get_start_oscar_session_url(od));
  199: 		purple_connection_error_reason(gc,
  200: 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
  201: 		g_free(msg);
  202: 		xmlnode_free(response_node);
  203: 		return FALSE;
  204: 	}
  205: 
  206: 	/* Make sure the status code was 200 */
  207: 	code = atoi(tmp);
  208: 	if (code != 200)
  209: 	{
  210: 		xmlnode *status_detail_node;
  211: 		guint status_detail = 0;
  212: 
  213: 		status_detail_node = xmlnode_get_child(response_node,
  214: 		                                       "statusDetailCode");
  215: 		if (status_detail_node) {
  216: 			gchar *data = xmlnode_get_data(status_detail_node);
  217: 			if (data) {
  218: 				status_detail = atoi(data);
  219: 				g_free(data);
  220: 			}
  221: 		}
  222: 
  223: 		purple_debug_error("oscar", "startOSCARSession response statusCode "
  224: 				"was %s: %s\n", tmp, response);
  225: 
  226: 		if ((code == 401 && status_detail != 1014) || code == 607)
  227: 			purple_connection_error_reason(gc,
  228: 					PURPLE_CONNECTION_ERROR_OTHER_ERROR,
  229: 					_("You have been connecting and disconnecting too "
  230: 					  "frequently. Wait ten minutes and try again. If "
  231: 					  "you continue to try, you will need to wait even "
  232: 					  "longer."));
  233: 		else {
  234: 			char *msg;
  235: 			msg = generate_error_message(response_node,
  236: 					get_start_oscar_session_url(od));
  237: 			purple_connection_error_reason(gc,
  238: 					PURPLE_CONNECTION_ERROR_OTHER_ERROR, msg);
  239: 			g_free(msg);
  240: 		}
  241: 
  242: 		g_free(tmp);
  243: 		xmlnode_free(response_node);
  244: 		return FALSE;
  245: 	}
  246: 	g_free(tmp);
  247: 
  248: 	/* Make sure we have everything else */
  249: 	if (data_node == NULL || host_node == NULL || port_node == NULL || cookie_node == NULL)
  250: 	{
  251: 		char *msg;
  252: 		purple_debug_error("oscar", "startOSCARSession response was missing "
  253: 				"something: %s\n", response);
  254: 		msg = generate_error_message(response_node,
  255: 				get_start_oscar_session_url(od));
  256: 		purple_connection_error_reason(gc,
  257: 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
  258: 		g_free(msg);
  259: 		xmlnode_free(response_node);
  260: 		return FALSE;
  261: 	}
  262: 
  263: 	if (!purple_strequal(encryption_type, OSCAR_NO_ENCRYPTION)) {
  264: 		tls_node = xmlnode_get_child(data_node, "tlsCertName");
  265: 		if (tls_node != NULL) {
  266: 			*tls_certname = xmlnode_get_data_unescaped(tls_node);
  267: 		} else {
  268: 			if (purple_strequal(encryption_type, OSCAR_OPPORTUNISTIC_ENCRYPTION)) {
  269: 				purple_debug_warning("oscar", "We haven't received a tlsCertName to use. We will not do SSL to BOS.\n");
  270: 			} else {
  271: 				purple_debug_error("oscar", "startOSCARSession was missing tlsCertName: %s\n", response);
  272: 				purple_connection_error_reason(
  273: 					gc,
  274: 					PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
  275: 					_("You required encryption in your account settings, but one of the servers doesn't support it."));
  276: 				xmlnode_free(response_node);
  277: 				return FALSE;
  278: 			}
  279: 		}
  280: 	}
  281: 
  282: 	/* Extract data from the XML */
  283: 	*host = xmlnode_get_data_unescaped(host_node);
  284: 	tmp = xmlnode_get_data_unescaped(port_node);
  285: 	*cookie = xmlnode_get_data_unescaped(cookie_node);
  286: 
  287: 	if (*host == NULL || **host == '\0' || tmp == NULL || *tmp == '\0' || *cookie == NULL || **cookie == '\0')
  288: 	{
  289: 		char *msg;
  290: 		purple_debug_error("oscar", "startOSCARSession response was missing "
  291: 				"something: %s\n", response);
  292: 		msg = generate_error_message(response_node,
  293: 				get_start_oscar_session_url(od));
  294: 		purple_connection_error_reason(gc,
  295: 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
  296: 		g_free(msg);
  297: 		g_free(*host);
  298: 		g_free(tmp);
  299: 		g_free(*cookie);
  300: 		xmlnode_free(response_node);
  301: 		return FALSE;
  302: 	}
  303: 
  304: 	*port = atoi(tmp);
  305: 	g_free(tmp);
  306: 
  307: 	return TRUE;
  308: }
  309: 
  310: static void start_oscar_session_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
  311: {
  312: 	OscarData *od;
  313: 	PurpleConnection *gc;
  314: 	char *host, *cookie;
  315: 	char *tls_certname = NULL;
  316: 	unsigned short port;
  317: 	guint8 *cookiedata;
  318: 	gsize cookiedata_len = 0;
  319: 
  320: 	od = user_data;
  321: 	gc = od->gc;
  322: 
  323: 	od->url_data = NULL;
  324: 
  325: 	if (error_message != NULL || len == 0) {
  326: 		gchar *tmp;
  327: 		/* Note to translators: The first %s is a URL, the second is an
  328: 		   error message. */
  329: 		tmp = g_strdup_printf(_("Error requesting %s: %s"),
  330: 				get_start_oscar_session_url(od), error_message ?
  331: 				error_message : _("The server returned an empty response"));
  332: 		purple_connection_error_reason(gc,
  333: 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
  334: 		g_free(tmp);
  335: 		return;
  336: 	}
  337: 
  338: 	if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie, &tls_certname))
  339: 		return;
  340: 
  341: 	cookiedata = purple_base64_decode(cookie, &cookiedata_len);
  342: 	oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len, tls_certname);
  343: 	g_free(cookiedata);
  344: 
  345: 	g_free(host);
  346: 	g_free(cookie);
  347: 	g_free(tls_certname);
  348: }
  349: 
  350: static void send_start_oscar_session(OscarData *od, const char *token, const char *session_key, time_t hosttime)
  351: {
  352: 	char *query_string, *signature, *url;
  353: 	PurpleAccount *account = purple_connection_get_account(od->gc);
  354: 	const gchar *encryption_type = purple_account_get_string(account, "encryption", OSCAR_DEFAULT_ENCRYPTION);
  355: 
  356: 	/*
  357: 	 * Construct the GET parameters.
  358: 	 */
  359: 	query_string = g_strdup_printf("a=%s"
  360: 			"&distId=%d"
  361: 			"&f=xml"
  362: 			"&k=%s"
  363: 			"&ts=%" PURPLE_TIME_T_MODIFIER
  364: 			"&useTLS=%d",
  365: 			purple_url_encode(token),
  366: 			oscar_get_ui_info_int(od->icq ? "prpl-icq-distid" : "prpl-aim-distid",
  367: 				od->icq ? ICQ_DEFAULT_DIST_ID : AIM_DEFAULT_DIST_ID),
  368: 			get_client_key(od),
  369: 			hosttime,
  370: 			!purple_strequal(encryption_type, OSCAR_NO_ENCRYPTION));
  371: 	signature = generate_signature("GET", get_start_oscar_session_url(od),
  372: 			query_string, session_key);
  373: 	url = g_strdup_printf("%s?%s&sig_sha256=%s", get_start_oscar_session_url(od),
  374: 			query_string, signature);
  375: 	g_free(query_string);
  376: 	g_free(signature);
  377: 
  378: 	/* Make the request */
  379: 	od->url_data = purple_util_fetch_url_request_len_with_account(account,
  380: 			url, TRUE, NULL, FALSE, NULL, FALSE, -1,
  381: 			start_oscar_session_cb, od);
  382: 	g_free(url);
  383: }
  384: 
  385: /**
  386:  * This function parses the given response from a clientLogin request
  387:  * and extracts the useful information.
  388:  *
  389:  * @param gc           The PurpleConnection.  If the response data does
  390:  *                     not indicate then purple_connection_error_reason()
  391:  *                     will be called to close this connection.
  392:  * @param response     The response data from the clientLogin request.
  393:  * @param response_len The length of the above response, or -1 if
  394:  *                     @response is NUL terminated.
  395:  * @param token        If parsing was successful then this will be set to
  396:  *                     a newly allocated string containing the token.  The
  397:  *                     caller should g_free this string when it is finished
  398:  *                     with it.  On failure this value will be untouched.
  399:  * @param secret       If parsing was successful then this will be set to
  400:  *                     a newly allocated string containing the secret.  The
  401:  *                     caller should g_free this string when it is finished
  402:  *                     with it.  On failure this value will be untouched.
  403:  * @param hosttime     If parsing was successful then this will be set to
  404:  *                     the time on the OpenAuth Server in seconds since the
  405:  *                     Unix epoch.  On failure this value will be untouched.
  406:  *
  407:  * @return TRUE if the request was successful and we were able to
  408:  *         extract all info we need.  Otherwise FALSE.
  409:  */
  410: static gboolean parse_client_login_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **token, char **secret, time_t *hosttime)
  411: {
  412: 	OscarData *od = purple_connection_get_protocol_data(gc);
  413: 	xmlnode *response_node, *tmp_node, *data_node;
  414: 	xmlnode *secret_node = NULL, *hosttime_node = NULL, *token_node = NULL, *tokena_node = NULL;
  415: 	char *tmp;
  416: 
  417: 	/* Parse the response as XML */
  418: 	response_node = xmlnode_from_str(response, response_len);
  419: 	if (response_node == NULL)
  420: 	{
  421: 		char *msg;
  422: 		purple_debug_error("oscar", "clientLogin could not parse "
  423: 				"response as XML: %s\n", response);
  424: 		msg = generate_error_message(response_node,
  425: 				get_client_login_url(od));
  426: 		purple_connection_error_reason(gc,
  427: 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
  428: 		g_free(msg);
  429: 		return FALSE;
  430: 	}
  431: 
  432: 	/* Grab the necessary XML nodes */
  433: 	tmp_node = xmlnode_get_child(response_node, "statusCode");
  434: 	data_node = xmlnode_get_child(response_node, "data");
  435: 	if (data_node != NULL) {
  436: 		secret_node = xmlnode_get_child(data_node, "sessionSecret");
  437: 		hosttime_node = xmlnode_get_child(data_node, "hostTime");
  438: 		token_node = xmlnode_get_child(data_node, "token");
  439: 		if (token_node != NULL)
  440: 			tokena_node = xmlnode_get_child(token_node, "a");
  441: 	}
  442: 
  443: 	/* Make sure we have a status code */
  444: 	if (tmp_node == NULL || (tmp = xmlnode_get_data_unescaped(tmp_node)) == NULL) {
  445: 		char *msg;
  446: 		purple_debug_error("oscar", "clientLogin response was "
  447: 				"missing statusCode: %s\n", response);
  448: 		msg = generate_error_message(response_node,
  449: 				get_client_login_url(od));
  450: 		purple_connection_error_reason(gc,
  451: 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
  452: 		g_free(msg);
  453: 		xmlnode_free(response_node);
  454: 		return FALSE;
  455: 	}
  456: 
  457: 	/* Make sure the status code was 200 */
  458: 	if (!purple_strequal(tmp, "200"))
  459: 	{
  460: 		int status_code, status_detail_code = 0;
  461: 
  462: 		status_code = atoi(tmp);
  463: 		g_free(tmp);
  464: 		tmp_node = xmlnode_get_child(response_node, "statusDetailCode");
  465: 		if (tmp_node != NULL && (tmp = xmlnode_get_data_unescaped(tmp_node)) != NULL) {
  466: 			status_detail_code = atoi(tmp);
  467: 			g_free(tmp);
  468: 		}
  469: 
  470: 		purple_debug_error("oscar", "clientLogin response statusCode "
  471: 				"was %d (%d): %s\n", status_code, status_detail_code, response);
  472: 
  473: 		if (status_code == 330 && status_detail_code == 3011) {
  474: 			PurpleAccount *account = purple_connection_get_account(gc);
  475: 			if (!purple_account_get_remember_password(account))
  476: 				purple_account_set_password(account, NULL);
  477: 			purple_connection_error_reason(gc,
  478: 					PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
  479: 					_("Incorrect password"));
  480: 		} else if (status_code == 330 && status_detail_code == 3015) {
  481: 			purple_connection_error_reason(gc,
  482: 					PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
  483: 					_("Server requested that you fill out a CAPTCHA in order to "
  484: 					"sign in, but this client does not currently support CAPTCHAs."));
  485: 		} else if (status_code == 401 && status_detail_code == 3019) {
  486: 			purple_connection_error_reason(gc,
  487: 					PURPLE_CONNECTION_ERROR_OTHER_ERROR,
  488: 					_("AOL does not allow your screen name to authenticate here"));
  489: 		} else {
  490: 			char *msg;
  491: 			msg = generate_error_message(response_node,
  492: 					get_client_login_url(od));
  493: 			purple_connection_error_reason(gc,
  494: 					PURPLE_CONNECTION_ERROR_OTHER_ERROR, msg);
  495: 			g_free(msg);
  496: 		}
  497: 
  498: 		xmlnode_free(response_node);
  499: 		return FALSE;
  500: 	}
  501: 	g_free(tmp);
  502: 
  503: 	/* Make sure we have everything else */
  504: 	if (data_node == NULL || secret_node == NULL ||
  505: 		token_node == NULL || tokena_node == NULL)
  506: 	{
  507: 		char *msg;
  508: 		purple_debug_error("oscar", "clientLogin response was missing "
  509: 				"something: %s\n", response);
  510: 		msg = generate_error_message(response_node,
  511: 				get_client_login_url(od));
  512: 		purple_connection_error_reason(gc,
  513: 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
  514: 		g_free(msg);
  515: 		xmlnode_free(response_node);
  516: 		return FALSE;
  517: 	}
  518: 
  519: 	/* Extract data from the XML */
  520: 	*token = xmlnode_get_data_unescaped(tokena_node);
  521: 	*secret = xmlnode_get_data_unescaped(secret_node);
  522: 	tmp = xmlnode_get_data_unescaped(hosttime_node);
  523: 	if (*token == NULL || **token == '\0' || *secret == NULL || **secret == '\0' || tmp == NULL || *tmp == '\0')
  524: 	{
  525: 		char *msg;
  526: 		purple_debug_error("oscar", "clientLogin response was missing "
  527: 				"something: %s\n", response);
  528: 		msg = generate_error_message(response_node,
  529: 				get_client_login_url(od));
  530: 		purple_connection_error_reason(gc,
  531: 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
  532: 		g_free(msg);
  533: 		g_free(*token);
  534: 		g_free(*secret);
  535: 		g_free(tmp);
  536: 		xmlnode_free(response_node);
  537: 		return FALSE;
  538: 	}
  539: 
  540: 	*hosttime = strtol(tmp, NULL, 10);
  541: 	g_free(tmp);
  542: 
  543: 	xmlnode_free(response_node);
  544: 
  545: 	return TRUE;
  546: }
  547: 
  548: static void client_login_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
  549: {
  550: 	OscarData *od;
  551: 	PurpleConnection *gc;
  552: 	char *token, *secret, *session_key;
  553: 	time_t hosttime;
  554: 	int password_len;
  555: 	char *password;
  556: 
  557: 	od = user_data;
  558: 	gc = od->gc;
  559: 
  560: 	od->url_data = NULL;
  561: 
  562: 	if (error_message != NULL || len == 0) {
  563: 		gchar *tmp;
  564: 		tmp = g_strdup_printf(_("Error requesting %s: %s"),
  565: 				get_client_login_url(od), error_message ?
  566: 				error_message : _("The server returned an empty response"));
  567: 		purple_connection_error_reason(gc,
  568: 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
  569: 		g_free(tmp);
  570: 		return;
  571: 	}
  572: 
  573: 	if (!parse_client_login_response(gc, url_text, len, &token, &secret, &hosttime))
  574: 		return;
  575: 
  576: 	password_len = strlen(purple_connection_get_password(gc));
  577: 	password = g_strdup_printf("%.*s",
  578: 			od->icq ? MIN(password_len, MAXICQPASSLEN) : password_len,
  579: 			purple_connection_get_password(gc));
  580: 	session_key = hmac_sha256(password, secret);
  581: 	g_free(password);
  582: 	g_free(secret);
  583: 
  584: 	send_start_oscar_session(od, token, session_key, hosttime);
  585: 
  586: 	g_free(token);
  587: 	g_free(session_key);
  588: }
  589: 
  590: /**
  591:  * This function sends a request to
  592:  * https://api.screenname.aol.com/auth/clientLogin with the user's
  593:  * username and password and receives the user's session key, which is
  594:  * used to request a connection to the BOSS server.
  595:  */
  596: void send_client_login(OscarData *od, const char *username)
  597: {
  598: 	PurpleConnection *gc;
  599: 	GString *request, *body;
  600: 	const char *tmp;
  601: 	char *password;
  602: 	int password_len;
  603: 
  604: 	gc = od->gc;
  605: 
  606: 	/*
  607: 	 * We truncate ICQ passwords to 8 characters.  There is probably a
  608: 	 * limit for AIM passwords, too, but we really only need to do
  609: 	 * this for ICQ because older ICQ clients let you enter a password
  610: 	 * as long as you wanted and then they truncated it silently.
  611: 	 *
  612: 	 * And we can truncate based on the number of bytes and not the
  613: 	 * number of characters because passwords for AIM and ICQ are
  614: 	 * supposed to be plain ASCII (I don't know if this has always been
  615: 	 * the case, though).
  616: 	 */
  617: 	tmp = purple_connection_get_password(gc);
  618: 	password_len = strlen(tmp);
  619: 	password = g_strndup(tmp, od->icq ? MIN(password_len, MAXICQPASSLEN) : password_len);
  620: 
  621: 	/* Construct the body of the HTTP POST request */
  622: 	body = g_string_new("");
  623: 	g_string_append_printf(body, "devId=%s", get_client_key(od));
  624: 	g_string_append_printf(body, "&f=xml");
  625: 	g_string_append_printf(body, "&pwd=%s", purple_url_encode(password));
  626: 	g_string_append_printf(body, "&s=%s", purple_url_encode(username));
  627: 	g_free(password);
  628: 
  629: 	/* Construct an HTTP POST request */
  630: 	request = g_string_new("POST /auth/clientLogin HTTP/1.0\r\n"
  631: 			"Connection: close\r\n"
  632: 			"Accept: */*\r\n");
  633: 
  634: 	/* Tack on the body */
  635: 	g_string_append_printf(request, "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n");
  636: 	g_string_append_printf(request, "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n", body->len);
  637: 	g_string_append_len(request, body->str, body->len);
  638: 	g_string_free(body, TRUE);
  639: 
  640: 	/* Send the POST request  */
  641: 	od->url_data = purple_util_fetch_url_request_len_with_account(
  642: 			purple_connection_get_account(gc), get_client_login_url(od),
  643: 			TRUE, NULL, FALSE, request->str, FALSE, -1,
  644: 			client_login_cb, od);
  645: 	g_string_free(request, TRUE);
  646: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>