/**
* Copyright (C) 2007-2008 Felipe Contreras
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include "nexus.h"
#include "pecan_log.h"
#include "pecan_util.h"
#include "session.h"
#include "notification.h"
#include "session_private.h"
/* libpurple stuff. */
#include "fix_purple_win32.h"
#include <sslconn.h>
#include "internal.h"
/**************************************************************************
* Main
**************************************************************************/
MsnNexus *
msn_nexus_new(MsnSession *session)
{
MsnNexus *nexus;
nexus = g_new0(MsnNexus, 1);
nexus->session = session;
nexus->challenge_data = g_hash_table_new_full(g_str_hash,
g_str_equal, g_free, g_free);
return nexus;
}
void
msn_nexus_destroy(MsnNexus *nexus)
{
if (nexus->gsc)
purple_ssl_close(nexus->gsc);
g_free(nexus->login_host);
g_free(nexus->login_path);
if (nexus->challenge_data != NULL)
g_hash_table_destroy(nexus->challenge_data);
if (nexus->input_handler > 0)
purple_input_remove(nexus->input_handler);
g_free(nexus->write_buf);
g_free(nexus->read_buf);
g_free(nexus);
}
/**************************************************************************
* Util
**************************************************************************/
static gssize
msn_ssl_read(MsnNexus *nexus)
{
gssize len;
char temp_buf[4096];
if ((len = purple_ssl_read(nexus->gsc, temp_buf,
sizeof(temp_buf))) > 0)
{
nexus->read_buf = g_realloc(nexus->read_buf,
nexus->read_len + len + 1);
strncpy(nexus->read_buf + nexus->read_len, temp_buf, len);
nexus->read_len += len;
nexus->read_buf[nexus->read_len] = '\0';
}
return len;
}
static void
nexus_write_cb(gpointer data, gint source, PurpleInputCondition cond)
{
MsnNexus *nexus = data;
gssize len, total_len;
total_len = strlen(nexus->write_buf);
len = purple_ssl_write(nexus->gsc,
nexus->write_buf + nexus->written_len,
total_len - nexus->written_len);
if (len < 0 && errno == EAGAIN)
return;
else if (len <= 0) {
purple_input_remove(nexus->input_handler);
nexus->input_handler = 0;
/* TODO: notify of the error */
return;
}
nexus->written_len += len;
if (nexus->written_len < total_len)
return;
purple_input_remove(nexus->input_handler);
nexus->input_handler = 0;
g_free(nexus->write_buf);
nexus->write_buf = NULL;
nexus->written_len = 0;
nexus->written_cb(nexus, source, 0);
}
/**************************************************************************
* Login
**************************************************************************/
static void
login_connect_cb(gpointer data, PurpleSslConnection *gsc,
PurpleInputCondition cond);
static void
login_error_cb(PurpleSslConnection *gsc, PurpleSslErrorType error, void *data)
{
MsnNexus *nexus;
MsnSession *session;
nexus = data;
g_return_if_fail(nexus != NULL);
nexus->gsc = NULL;
session = nexus->session;
g_return_if_fail(session != NULL);
msn_session_set_error(session, MSN_ERROR_AUTH, _("Unable to connect"));
/* the above line will result in nexus being destroyed, so we don't want
* to destroy it here, or we'd crash */
}
static void
nexus_login_written_cb(gpointer data, gint source, PurpleInputCondition cond)
{
MsnNexus *nexus = data;
MsnSession *session;
int len;
session = nexus->session;
g_return_if_fail(session != NULL);
if (nexus->input_handler == 0)
/* TODO: Use purple_ssl_input_add()? */
nexus->input_handler = purple_input_add(nexus->gsc->fd,
PURPLE_INPUT_READ, nexus_login_written_cb, nexus);
len = msn_ssl_read(nexus);
if (len < 0 && errno == EAGAIN)
return;
else if (len < 0) {
purple_input_remove(nexus->input_handler);
nexus->input_handler = 0;
g_free(nexus->read_buf);
nexus->read_buf = NULL;
nexus->read_len = 0;
/* TODO: error handling */
return;
}
if (g_strstr_len(nexus->read_buf, nexus->read_len,
"\r\n\r\n") == NULL)
return;
purple_input_remove(nexus->input_handler);
nexus->input_handler = 0;
purple_ssl_close(nexus->gsc);
nexus->gsc = NULL;
pecan_log ("ssl buffer: [%s]", nexus->read_buf);
if (strstr(nexus->read_buf, "HTTP/1.1 302") != NULL)
{
/* Redirect. */
char *location, *c;
location = strstr(nexus->read_buf, "Location: ");
if (location == NULL)
{
g_free(nexus->read_buf);
nexus->read_buf = NULL;
nexus->read_len = 0;
return;
}
location = strchr(location, ' ') + 1;
if ((c = strchr(location, '\r')) != NULL)
*c = '\0';
/* Skip the http:// */
if ((c = strchr(location, '/')) != NULL)
location = c + 2;
if ((c = strchr(location, '/')) != NULL)
{
g_free(nexus->login_path);
nexus->login_path = g_strdup(c);
*c = '\0';
}
g_free(nexus->login_host);
nexus->login_host = g_strdup(location);
nexus->gsc = purple_ssl_connect(session->account,
nexus->login_host, PURPLE_SSL_DEFAULT_PORT,
login_connect_cb, login_error_cb, nexus);
}
else if (strstr(nexus->read_buf, "HTTP/1.1 401 Unauthorized") != NULL)
{
const char *tmp;
gchar *error = NULL;
if ((tmp = strstr(nexus->read_buf, "WWW-Authenticate")) != NULL)
{
if ((tmp = strstr(tmp, "cbtxt=")) != NULL)
{
const char *c;
char *tmp2;
tmp += strlen("cbtxt=");
if ((c = strchr(tmp, '\n')) == NULL)
c = tmp + strlen(tmp);
tmp2 = g_strndup(tmp, c - tmp);
error = pecan_url_decode(tmp2);
g_free(tmp2);
if ((tmp2 = strstr(error, " Do one of the following or try again:")) != NULL)
*tmp2 = '\0';
}
}
msn_session_set_error(session, MSN_ERROR_AUTH, error);
g_free(error);
}
else if (strstr(nexus->read_buf, "HTTP/1.1 503 Service Unavailable"))
{
msn_session_set_error(session, MSN_ERROR_SERV_UNAVAILABLE, NULL);
}
else if (strstr(nexus->read_buf, "HTTP/1.1 200 OK"))
{
char *base, *c;
char *login_params;
#if 0
/* All your base are belong to us. */
base = buffer;
/* For great cookie! */
while ((base = strstr(base, "Set-Cookie: ")) != NULL)
{
base += strlen("Set-Cookie: ");
c = strchr(base, ';');
session->login_cookies =
g_list_append(session->login_cookies,
g_strndup(base, c - base));
}
#endif
base = strstr(nexus->read_buf, "Authentication-Info: ");
g_return_if_fail(base != NULL);
base = strstr(base, "from-PP='");
base += strlen("from-PP='");
c = strchr(base, '\'');
login_params = g_strndup(base, c - base);
msn_got_login_params(session, login_params);
g_free(login_params);
msn_nexus_destroy(nexus);
session->nexus = NULL;
return;
}
g_free(nexus->read_buf);
nexus->read_buf = NULL;
nexus->read_len = 0;
}
/* this guards against missing hash entries */
static const gchar *
nexus_challenge_data_lookup(GHashTable *challenge_data, const char *key)
{
const gchar *entry;
return (entry = g_hash_table_lookup(challenge_data, key)) ? entry : "(null)";
}
void
login_connect_cb(gpointer data, PurpleSslConnection *gsc,
PurpleInputCondition cond)
{
MsnNexus *nexus;
MsnSession *session;
char *username, *password;
char *request_str, *head, *tail;
char *buffer = NULL;
guint32 ctint;
nexus = data;
g_return_if_fail(nexus != NULL);
session = nexus->session;
g_return_if_fail(session != NULL);
msn_session_set_login_step(session, PECAN_LOGIN_STEP_GET_COOKIE);
username =
g_strdup(purple_url_encode(msn_session_get_username(session)));
password =
g_strdup(purple_url_encode(msn_session_get_password(session)));
ctint = strtoul((char *)g_hash_table_lookup(nexus->challenge_data, "ct"), NULL, 10) + 200;
head = pecan_strdup_printf(
"GET %s HTTP/1.1\r\n"
"Authorization: Passport1.4 OrgVerb=GET,OrgURL=%s,sign-in=%s",
nexus->login_path,
(char *)g_hash_table_lookup(nexus->challenge_data, "ru"),
username);
tail = pecan_strdup_printf(
"lc=%s,id=%s,tw=%s,fs=%s,ru=%s,ct=%" G_GUINT32_FORMAT ",kpp=%s,kv=%s,ver=%s,tpf=%s\r\n"
"User-Agent: MSMSGS\r\n"
"Host: %s\r\n"
"Connection: Keep-Alive\r\n"
"Cache-Control: no-cache\r\n",
nexus_challenge_data_lookup(nexus->challenge_data, "lc"),
nexus_challenge_data_lookup(nexus->challenge_data, "id"),
nexus_challenge_data_lookup(nexus->challenge_data, "tw"),
nexus_challenge_data_lookup(nexus->challenge_data, "fs"),
nexus_challenge_data_lookup(nexus->challenge_data, "ru"),
ctint,
nexus_challenge_data_lookup(nexus->challenge_data, "kpp"),
nexus_challenge_data_lookup(nexus->challenge_data, "kv"),
nexus_challenge_data_lookup(nexus->challenge_data, "ver"),
nexus_challenge_data_lookup(nexus->challenge_data, "tpf"),
nexus->login_host);
buffer = pecan_strdup_printf("%s,pwd=XXXXXXXX,%s\r\n", head, tail);
request_str = pecan_strdup_printf("%s,pwd=%s,%s\r\n", head, password, tail);
pecan_log ("sending: [%s]", buffer);
g_free(buffer);
g_free(head);
g_free(tail);
g_free(username);
g_free(password);
nexus->write_buf = request_str;
nexus->written_len = 0;
nexus->read_len = 0;
nexus->written_cb = nexus_login_written_cb;
nexus->input_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE,
nexus_write_cb, nexus);
nexus_write_cb(nexus, gsc->fd, PURPLE_INPUT_WRITE);
return;
}
static void
nexus_connect_written_cb(gpointer data, gint source, PurpleInputCondition cond)
{
MsnNexus *nexus = data;
int len;
char *da_login;
char *base, *c;
if (nexus->input_handler == 0)
/* TODO: Use purple_ssl_input_add()? */
nexus->input_handler = purple_input_add(nexus->gsc->fd,
PURPLE_INPUT_READ, nexus_connect_written_cb, nexus);
/* Get the PassportURLs line. */
len = msn_ssl_read(nexus);
if (len < 0 && errno == EAGAIN)
return;
else if (len < 0) {
purple_input_remove(nexus->input_handler);
nexus->input_handler = 0;
g_free(nexus->read_buf);
nexus->read_buf = NULL;
nexus->read_len = 0;
/* TODO: error handling */
return;
}
if (g_strstr_len(nexus->read_buf, nexus->read_len,
"\r\n\r\n") == NULL)
return;
purple_input_remove(nexus->input_handler);
nexus->input_handler = 0;
base = strstr(nexus->read_buf, "PassportURLs");
if (base == NULL)
{
g_free(nexus->read_buf);
nexus->read_buf = NULL;
nexus->read_len = 0;
return;
}
if ((da_login = strstr(base, "DALogin=")) != NULL)
{
/* skip over "DALogin=" */
da_login += 8;
if ((c = strchr(da_login, ',')) != NULL)
*c = '\0';
if ((c = strchr(da_login, '/')) != NULL)
{
nexus->login_path = g_strdup(c);
*c = '\0';
}
nexus->login_host = g_strdup(da_login);
}
g_free(nexus->read_buf);
nexus->read_buf = NULL;
nexus->read_len = 0;
purple_ssl_close(nexus->gsc);
/* Now begin the connection to the login server. */
nexus->gsc = purple_ssl_connect(nexus->session->account,
nexus->login_host, PURPLE_SSL_DEFAULT_PORT,
login_connect_cb, login_error_cb, nexus);
}
/**************************************************************************
* Connect
**************************************************************************/
static void
nexus_connect_cb(gpointer data, PurpleSslConnection *gsc,
PurpleInputCondition cond)
{
MsnNexus *nexus;
MsnSession *session;
nexus = data;
g_return_if_fail(nexus != NULL);
session = nexus->session;
g_return_if_fail(session != NULL);
msn_session_set_login_step(session, PECAN_LOGIN_STEP_AUTH);
nexus->write_buf = g_strdup("GET /rdr/pprdr.asp\r\n\r\n");
nexus->written_len = 0;
nexus->read_len = 0;
nexus->written_cb = nexus_connect_written_cb;
nexus->input_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE,
nexus_write_cb, nexus);
nexus_write_cb(nexus, gsc->fd, PURPLE_INPUT_WRITE);
}
void
msn_nexus_connect(MsnNexus *nexus)
{
nexus->gsc = purple_ssl_connect(nexus->session->account,
"nexus.passport.com", PURPLE_SSL_DEFAULT_PORT,
nexus_connect_cb, login_error_cb, nexus);
}