Annotation of ChivanetAimPidgin/oscarprpl/src/c/clientlogin.c, revision 1.1.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 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>