diff libpurple/protocols/mxit/login.c @ 28903:69aa4660401a

Initial addition of the MXit protocol plugin, provided by the MXit folks themselves.
author John Bailey <rekkanoryo@rekkanoryo.org>
date Sun, 08 Nov 2009 23:55:56 +0000
parents
children 95f8e7fb1f67
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/login.c	Sun Nov 08 23:55:56 2009 +0000
@@ -0,0 +1,789 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *				-- MXit user login functionality --
+ *
+ *				Pieter Loubser	<libpurple@mxit.com>
+ *
+ *			(C) Copyright 2009	MXit Lifestyle (Pty) Ltd.
+ *				<http://www.mxitlifestyle.com>
+ *
+ * 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	<stdio.h>
+#include	<string.h>
+
+#include	"purple.h"
+
+#include	"protocol.h"
+#include	"mxit.h"
+#include	"cipher.h"
+#include	"login.h"
+#include	"profile.h"
+
+/* requesting captcha size */
+#define		MXIT_CAPTCHA_HEIGHT		50
+#define		MXIT_CAPTCHA_WIDTH		150
+
+
+/* prototypes */
+static void mxit_register_view( struct MXitSession* session );
+static void get_clientinfo( struct MXitSession* session );
+
+
+/*------------------------------------------------------------------------
+ * Create a new mxit session object
+ *
+ * @return The MXit session object
+ */
+static struct MXitSession* mxit_create_object( PurpleAccount* account )
+{
+	struct MXitSession*	session		= NULL;
+	PurpleConnection*	con			= NULL;
+
+	/* currently the wapsite does not handle a '+' in front of the username (mxitid) so we just strip it */
+	if ( account->username[0] == '+' ) {
+		char*		fixed;
+
+		/* cut off the '+' */
+		fixed = g_strdup( &account->username[1] );
+		purple_account_set_username( account, fixed );
+		g_free( fixed );
+	}
+
+	session = g_new0( struct MXitSession, 1 );
+
+	/* configure the connection (reference: "libpurple/connection.h") */
+	con = purple_account_get_connection( account );
+	con->proto_data = session;
+	con->flags |= PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_HTML;
+	session->con = con;
+
+	/* add account */
+	session->acc = account;
+
+	/* configure the session (reference: "libpurple/account.h") */
+	g_strlcpy( session->server, purple_account_get_string( account, MXIT_CONFIG_SERVER_ADDR, DEFAULT_SERVER ), sizeof( session->server ) );
+	g_strlcpy( session->http_server, purple_account_get_string( account, MXIT_CONFIG_HTTPSERVER, DEFAULT_HTTP_SERVER ), sizeof( session->http_server ) );
+	session->port = purple_account_get_int( account, MXIT_CONFIG_SERVER_PORT, DEFAULT_PORT );
+	g_strlcpy( session->distcode, purple_account_get_string( account, MXIT_CONFIG_DISTCODE, "" ), sizeof( session->distcode ) );
+	g_strlcpy( session->clientkey, purple_account_get_string( account, MXIT_CONFIG_CLIENTKEY, "" ), sizeof( session->clientkey ) );
+	g_strlcpy( session->dialcode, purple_account_get_string( account, MXIT_CONFIG_DIALCODE, "" ), sizeof( session->dialcode ) );
+	session->http = purple_account_get_bool( account, MXIT_CONFIG_USE_HTTP, FALSE );
+	session->iimages = g_hash_table_new( g_str_hash, g_str_equal );
+	session->rx_state = RX_STATE_RLEN;
+	session->http_interval = MXIT_HTTP_POLL_MIN;
+	session->http_last_poll = time( NULL );
+
+	return session;
+}
+
+
+/*------------------------------------------------------------------------
+ * We now have a connection established with MXit, so we can start the
+ * login procedure
+ *
+ * @param session	The MXit session object
+ */
+static void mxit_connected( struct MXitSession* session )
+{
+	int			state;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_connected\n" );
+
+	session->flags |= MXIT_FLAG_CONNECTED;
+	purple_connection_update_progress( session->con, _( "Logging In..." ), 2, 4 );
+
+	/* create a timer to send a ping packet if the connection is idle */
+	session->last_tx = time( NULL );
+
+	/* encrypt the user password */
+	session->encpwd = mxit_encrypt_password( session );
+
+	state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN ); 
+	if ( state == MXIT_STATE_LOGIN ) {
+		/* create and send login packet */
+		mxit_send_login( session );
+	}
+	else {
+		if ( !session->profile ) {
+			/* we have lost the session profile, so ask the user to enter it again */
+			mxit_register_view( session );
+		}
+		else {
+			/* create and send the register packet */
+			mxit_send_register( session );
+		}
+	}
+
+	/* enable signals */
+	mxit_enable_signals( session );
+
+#ifdef		MXIT_LINK_CLICK
+	/* register for uri click notification */
+	mxit_register_uri_handler();
+#endif
+
+	/* start the polling if this is a HTTP connection */
+	if ( session->http ) {
+		session->http_timer_id = purple_timeout_add_seconds( 2, mxit_manage_polling, session );
+	}
+
+	/* start the tx queue manager timer */
+	session->q_timer = purple_timeout_add_seconds( 2, mxit_manage_queue, session );
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback invoked once the connection has been established to the MXit server,
+ * or on connection failure.
+ *
+ *  @param user_data		The MXit session object
+ *  @param source			The file-descriptor associated with the connection
+ *  @param error_message	Message explaining why the connection failed
+ */
+static void mxit_cb_connect( gpointer user_data, gint source, const gchar* error_message )
+{
+	struct MXitSession*	session		= (struct MXitSession*) user_data;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_connect\n" );
+
+	/* source is the file descriptor of the new connection */
+	if ( source < 0 ) {
+		purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_connect failed: %s\n", error_message );
+		purple_connection_error( session->con, _( "Unable to connect to the mxit server. Please check your server server settings." ) );
+		return;
+	}
+
+	/* we now have an open and active TCP connection to the mxit server */
+	session->fd = source;
+
+	/* start listening on the open connection for messages from the server (reference: "libpurple/eventloop.h") */
+	session->con->inpa = purple_input_add( session->fd, PURPLE_INPUT_READ, mxit_cb_rx, session );
+
+	mxit_connected( session );
+}
+
+
+/*------------------------------------------------------------------------
+ * Attempt to establish a connection to the MXit server.
+ *
+ *  @param session			The MXit session object
+ */
+static void mxit_login_connect( struct MXitSession* session )
+{
+	PurpleProxyConnectData*		data	= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_login_connect\n" );
+
+	purple_connection_update_progress( session->con, _( "Connecting..." ), 1, 4 );
+
+	/*
+	 * at this stage we have all the user's information we require
+	 * for logging into MXit. we will now create a new connection to
+	 * a MXit server.
+	 */
+
+	if ( !session->http ) {
+		/* socket connection */
+		data = purple_proxy_connect( session->con, session->acc, session->server, session->port, mxit_cb_connect, session );
+		if ( !data ) {
+			purple_connection_error( session->con, _( "Unable to connect to the mxit server. Please check your server server settings." ) );
+			return;
+		}
+	}
+	else {
+		/* http connection */
+		mxit_connected( session );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Register a new account with MXit
+ *
+ * @param gc		The connection object
+ * @param fields	This is the fields filled-in by the user
+ */
+static void mxit_cb_register_ok( PurpleConnection *gc, PurpleRequestFields *fields )
+{
+	struct MXitSession*		session		= (struct MXitSession*) gc->proto_data;
+	struct MXitProfile*		profile		= session->profile;
+	const char*				str;
+	const char*				pin;
+	char*					err			= NULL;
+	int						len;
+	int						i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_register_ok\n" );
+
+	if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Unable to register; account offline.\n" );
+		return;
+	}
+
+	/* nickname */
+	str = purple_request_fields_get_string( fields, "nickname" );
+	if ( ( !str ) || ( strlen( str ) < 3 ) ) {
+		err = "The nick name you entered is invalid.";
+		goto out;
+	}
+	g_strlcpy( profile->nickname, str, sizeof( profile->nickname ) );
+
+	/* birthdate */
+	str = purple_request_fields_get_string( fields, "bday" );
+	if ( ( !str ) || ( strlen( str ) < 10 ) || ( !validateDate( str ) ) ) {
+		err = "The birthday you entered is invalid. The correct format is: 'YYYY-MM-DD'.";
+		goto out;
+	}
+	g_strlcpy( profile->birthday, str, sizeof( profile->birthday ) );
+
+	/* gender */
+	if ( purple_request_fields_get_choice( fields, "male" ) == 0 )
+		profile->male = FALSE;
+	else
+		profile->male = TRUE;
+
+	/* pin */
+	pin = purple_request_fields_get_string( fields, "pin" );
+	if ( !pin ) {
+		err = "The PIN you entered is invalid.";
+		goto out;
+	}
+	len = strlen( pin );
+	if ( ( len < 7 ) || ( len > 10 ) ) {
+		err = "The PIN you entered has an invalid length [7-10].";
+		goto out;
+	}
+	for ( i = 0; i < len; i++ ) {
+		if ( !g_ascii_isdigit( pin[i] ) ) {
+			err = "The PIN is invalid. It should only consist of digits [0-9].";
+			goto out;
+		}
+	}
+	str = purple_request_fields_get_string( fields, "pin2" );
+	if ( ( !str ) || ( strcmp( pin, str ) != 0 ) ) {
+		err = "The two PINs you entered does not match.";
+		goto out;
+	}
+	g_strlcpy( profile->pin, pin, sizeof( profile->pin ) );
+
+out:
+	if ( !err ) {
+		purple_account_set_password( session->acc, session->profile->pin );
+		mxit_login_connect( session );
+	}
+	else {
+		/* show error to user */
+		mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Registration Error" ), _( err ) );
+		mxit_register_view( session );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Register a new account with MXit
+ *
+ * @param gc		The connection object
+ * @param fields	This is the fields filled-in by the user
+ */
+static void mxit_cb_register_cancel( PurpleConnection *gc, PurpleRequestFields *fields )
+{
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_register_cancel\n" );
+
+	/* disconnect */
+	purple_account_disconnect( gc->account );
+}
+
+
+/*------------------------------------------------------------------------
+ * Show a window to the user so that he can enter his information
+ *
+ *  @param session		The MXit session object
+ */
+static void mxit_register_view( struct MXitSession* session )
+{
+	struct MXitProfile*			profile;
+	PurpleRequestFields*		fields;
+	PurpleRequestFieldGroup*	group;
+	PurpleRequestField*			field;
+
+	if ( !session->profile ) {
+		/* we need to create a profile object here */
+		session->profile = g_new0( struct MXitProfile, 1 );
+	}
+	profile = session->profile;
+
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new( NULL );
+	purple_request_fields_add_group( fields, group );
+
+	/* mxit login name */
+	field = purple_request_field_string_new( "loginname", _( "MXit Login Name" ), purple_account_get_username( session->acc ), FALSE );
+	purple_request_field_string_set_editable( field, FALSE );
+	purple_request_field_group_add_field( group, field );
+
+	/* nick name */
+	field = purple_request_field_string_new( "nickname", _( "Nick Name" ), profile->nickname, FALSE );
+	purple_request_field_group_add_field( group, field );
+
+	/* birthday */
+	field = purple_request_field_string_new( "bday", _( "Birthday" ), profile->birthday, FALSE );
+	purple_request_field_string_set_default_value( field, "YYYY-MM-DD" );
+	purple_request_field_group_add_field( group, field );
+
+	/* gender */
+	field = purple_request_field_choice_new( "male", _( "Gender" ), ( profile->male ) ? 1 : 0 );
+	purple_request_field_choice_add( field, _( "Female" ) );		/* 0 */
+	purple_request_field_choice_add( field, _( "Male" ) );			/* 1 */
+	purple_request_field_group_add_field( group, field );
+
+	/* pin */
+	field = purple_request_field_string_new( "pin", _( "PIN" ), profile->pin, FALSE );
+	purple_request_field_string_set_masked( field, TRUE );
+	purple_request_field_group_add_field( group, field );
+	field = purple_request_field_string_new( "pin2", _( "Verify PIN" ), "", FALSE );
+	purple_request_field_string_set_masked( field, TRUE );
+	purple_request_field_group_add_field( group, field );
+
+	/* show the form to the user to complete */
+	purple_request_fields( session->con, _( "Register New MXit Account" ), _( "Register New MXit Account" ), _( "Please fill in the following fields:" ), fields, _( "OK" ), G_CALLBACK( mxit_cb_register_ok ), _( "Cancel" ), G_CALLBACK( mxit_cb_register_cancel ), session->acc, NULL, NULL, session->con );
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback function invoked once the Authorization information has been submitted
+ * to the MXit WAP site.
+ *
+ *  @param url_data			libPurple internal object (see purple_util_fetch_url_request)
+ *  @param user_data		The MXit session object
+ *  @param url_text			The data returned from the WAP site
+ *  @param len				The length of the data returned
+ *  @param error_message	Descriptive error message
+ */
+static void mxit_cb_clientinfo2( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
+{
+	struct MXitSession*		session		= (struct MXitSession*) user_data;
+	gchar**					parts;
+	gchar**					host;
+	int						state;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_clientinfo_cb2\n" );
+
+#ifdef	DEBUG_PROTOCOL
+	purple_debug_info( MXIT_PLUGIN_ID, "HTTP RESPONSE: '%s'\n", url_text );
+#endif
+
+	if ( !url_text ) {
+		/* no reply from the WAP site */
+		purple_connection_error( session->con, _( "Error contacting the MXit WAP site. Please try again later." ) );
+		return;
+	}
+
+	/* explode the response from the WAP site into an array */
+	parts = g_strsplit( url_text, ";", 15 );
+
+	if ( !parts ) {
+		/* wapserver error */
+		purple_connection_error( session->con, _( "MXit is currently unable to process the request. Please try again later." ) );
+		return;
+	}
+
+	/* check wapsite return code */
+	switch ( parts[0][0] ) {
+			case '0' :
+				/* valid reply! */
+				break;
+			case '1' :
+				purple_connection_error( session->con, _( "Wrong security code entered. Please try again later." ) );
+				return;
+			case '2' :
+				purple_connection_error( session->con, _( "Your session has expired. Please try again later." ) );
+				return;
+			case '5' :
+				purple_connection_error( session->con, _( "Invalid country selected. Please try again." ) );
+				return;
+			case '6' :
+				purple_connection_error( session->con, _( "Username is not registered. Please register first." ) );
+				return;
+			case '7' :
+				purple_connection_error( session->con, _( "Username is already registered. Please choose another username." ) );
+				/* this user's account already exists, so we need to change the registration login flag to be login */
+				purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+				return;
+			case '3' :
+			case '4' :
+			default :
+				purple_connection_error( session->con, _( "Internal error. Please try again later." ) );
+				return;
+	}
+
+	/* now parse and split the distribution code and the client key */
+	g_strlcpy( session->distcode, &parts[1][2], 36 + 1 );
+	g_strlcpy( session->clientkey, &parts[1][38], 8 + 1 );
+
+	/* get the dial code for the client */
+	g_strlcpy( session->dialcode, parts[4], sizeof( session->dialcode ) );
+
+	/* parse the proxy server address and port number */
+	host = g_strsplit( parts[2], ":", 4 );
+	g_strlcpy( session->server, &host[1][2], sizeof( session->server ) );
+	session->port = atoi( &host[2][0] );
+
+	/* parse the http proxy server address and port number */
+	g_strlcpy( session->http_server, parts[3], sizeof( session->http_server ) );
+
+	purple_debug_info( MXIT_PLUGIN_ID, "distcode='%s', clientkey='%s', dialcode='%s'\n", session->distcode, session->clientkey, session->dialcode );
+	purple_debug_info( MXIT_PLUGIN_ID, "sock_server='%s', http_server='%s', port='%i', cc='%s'\n", session->server, session->http_server, session->port, parts[11] );
+
+	/* save the information (reference: "libpurple/account.h") */
+	purple_account_set_string( session->acc, MXIT_CONFIG_DISTCODE, session->distcode );
+	purple_account_set_string( session->acc, MXIT_CONFIG_CLIENTKEY, session->clientkey );
+	purple_account_set_string( session->acc, MXIT_CONFIG_DIALCODE, session->dialcode );
+	purple_account_set_string( session->acc, MXIT_CONFIG_SERVER_ADDR, session->server );
+	purple_account_set_int( session->acc, MXIT_CONFIG_SERVER_PORT, session->port );
+	purple_account_set_string( session->acc, MXIT_CONFIG_HTTPSERVER, session->http_server );
+
+	/* update the state */
+	state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+	if ( state == MXIT_STATE_REGISTER1 )
+		purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_REGISTER2 );
+
+	/* freeup the memory */
+	g_strfreev( host );
+	g_strfreev( parts );
+
+	if ( state == MXIT_STATE_LOGIN ) {
+		/* now we can continue with the login process */
+		mxit_login_connect( session );
+	}
+	else {
+		/* the user is registering so we need to get more information from him/her first to complete the process */
+		mxit_register_view( session );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Free up the data associated with the Authorization process.
+ *
+ *  @param data			The data object to free
+ */
+static void free_logindata( struct login_data* data )
+{
+	if ( !data )
+		return;
+
+	/* free up the login resources */
+	g_free( data->wapserver );
+	g_free( data->sessionid );
+	g_free( data->captcha );
+	g_free( data->cc );
+	g_free( data->locale );
+	g_free( data );
+}
+
+
+/*------------------------------------------------------------------------
+ * This function is called when the user accepts the Authorization form.
+ *
+ *  @param gc				The connection object
+ *  @param fields			The list of fields in the accepted form
+ */
+static void mxit_cb_captcha_ok( PurpleConnection* gc, PurpleRequestFields* fields )
+{
+	struct MXitSession*		session	= (struct MXitSession*) gc->proto_data;
+	PurpleUtilFetchUrlData*	url_data;
+	PurpleRequestField*		field;
+	const char*				captcha_resp;
+	GList*					entries;
+	GList*					entry;
+	char*					url;
+	int						state;
+
+	/* get the captcha response */
+	captcha_resp = purple_request_fields_get_string( fields, "code" );
+	if ( ( captcha_resp == NULL ) || ( captcha_resp[0] == '\0' ) ) {
+		/* the user did not fill in the captcha */
+		mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Error" ), _( "You did not enter the security code" ) );
+		free_logindata( session->logindata );
+		purple_account_disconnect( session->acc );
+		return;
+	}
+
+	/* get chosen country */
+	field = purple_request_fields_get_field( fields, "country" );
+	entries = purple_request_field_list_get_selected( field );
+	entry = g_list_first( entries );
+	session->logindata->cc = purple_request_field_list_get_data( field, entry->data );
+	purple_account_set_string( session->acc, MXIT_CONFIG_COUNTRYCODE, session->logindata->cc );
+
+	/* get chosen language */
+	field = purple_request_fields_get_field( fields, "locale" );
+	entries = purple_request_field_list_get_selected( field );
+	entry = g_list_first( entries );
+	session->logindata->locale = purple_request_field_list_get_data( field, entry->data );
+	purple_account_set_string( session->acc, MXIT_CONFIG_LOCALE, session->logindata->locale );
+
+#ifdef	DEBUG_PROTOCOL
+	purple_debug_info( MXIT_PLUGIN_ID, "cc='%s', locale='%s', captcha='%s'\n", session->logindata->cc, session->logindata->locale, captcha_resp );
+#endif
+
+	/* get state */
+	state = purple_account_get_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+
+	url = g_strdup_printf( "%s?type=getpid&sessionid=%s&login=%s&ver=%s&clientid=%s&cat=%s&chalresp=%s&cc=%s&loc=%s&path=%i&brand=%s&model=%s&h=%i&w=%i&ts=%li",
+			session->logindata->wapserver, session->logindata->sessionid, purple_url_encode( session->acc->username ), MXIT_CP_RELEASE, MXIT_CLIENT_ID, MXIT_CP_ARCH,
+			captcha_resp, session->logindata->cc, session->logindata->locale, ( state == MXIT_STATE_REGISTER1 ) ? 0 : 1, MXIT_CP_PLATFORM, MXIT_CP_OS,
+			MXIT_CAPTCHA_HEIGHT, MXIT_CAPTCHA_WIDTH, time( NULL ) );
+	url_data = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_clientinfo2, session );
+
+#ifdef	DEBUG_PROTOCOL
+	purple_debug_info( MXIT_PLUGIN_ID, "HTTP REQUEST: '%s'\n", url );
+#endif
+	g_free( url );
+
+	/* free up the login resources */
+	free_logindata( session->logindata );
+}
+
+
+/*------------------------------------------------------------------------
+ * This function is called when the user cancels the Authorization form.
+ *
+ *  @param gc				The connection object
+ *  @param fields			The list of fields in the cancelled form
+ */
+static void mxit_cb_captcha_cancel( PurpleConnection* gc, PurpleRequestFields* fields )
+{
+	struct MXitSession*		session	= (struct MXitSession*) gc->proto_data;
+
+	/* free up the login resources */
+	free_logindata( session->logindata );
+
+	/* we cannot continue, so we disconnect this account */
+	purple_account_disconnect( session->acc );
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback function invoked once the client information has been retrieved from
+ * the MXit WAP site.  Display page where user can select their authorization information.
+ *
+ *  @param url_data			libPurple internal object (see purple_util_fetch_url_request)
+ *  @param user_data		The MXit session object
+ *  @param url_text			The data returned from the WAP site
+ *  @param len				The length of the data returned
+ *  @param error_message	Descriptive error message
+ */
+static void mxit_cb_clientinfo1( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
+{
+	struct MXitSession*			session		= (struct MXitSession*) user_data;
+	struct login_data*			logindata;
+	PurpleRequestFields*		fields;
+	PurpleRequestFieldGroup*	group		= NULL;
+	PurpleRequestField*			field		= NULL;
+	gchar**						parts;
+	gchar**						countries;
+	gchar**						locales;
+	int							i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_clientinfo_cb1\n" );
+
+#ifdef	DEBUG_PROTOCOL
+	purple_debug_info( MXIT_PLUGIN_ID, "RESPONSE: %s\n", url_text );
+#endif
+
+	if ( !url_text ) {
+		/* no reply from the WAP site */
+		purple_connection_error( session->con, _( "Error contacting the MXit WAP site. Please try again later." ) );
+		return;
+	}
+
+	/* explode the response from the WAP site into an array */
+	parts = g_strsplit( url_text, ";", 15 );
+
+	if ( ( !parts ) || ( parts[0][0] != '0' ) ) {
+		/* server could not find the user */
+		purple_connection_error( session->con, _( "MXit is currently unable to process the request. Please try again later." ) );
+		return;
+	}
+
+	/* save received settings */
+	logindata = g_new0( struct login_data, 1 );
+	logindata->wapserver = g_strdup( parts[1] );
+	logindata->sessionid = g_strdup( parts[2] );
+	session->logindata = logindata;
+
+	/* now generate the popup requesting the user for action */
+
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new( NULL );
+	purple_request_fields_add_group( fields, group );
+
+	/* add the captcha */
+	logindata->captcha = purple_base64_decode( parts[3], &logindata->captcha_size );
+	field = purple_request_field_image_new( "capcha", _( "Security Code" ), (gchar*) logindata->captcha, logindata->captcha_size );
+	purple_request_field_group_add_field( group, field );
+
+	/* ask for input */
+	field = purple_request_field_string_new( "code", _( "Enter Security Code" ), NULL, FALSE );
+	purple_request_field_group_add_field( group, field );
+
+	/* choose your country, but be careful, we already know your IP! ;-) */
+	countries = g_strsplit( parts[4], ",", 500 );
+	field = purple_request_field_list_new( "country", _( "Your Country" ) );
+	purple_request_field_list_set_multi_select( field, FALSE );
+	for ( i = 0; countries[i]; i++ ) {
+		gchar**		country;
+
+		country = g_strsplit( countries[i], "|", 2 );
+		if ( !country ) {
+			/* oops, this is not good, time to bail */
+			break;
+		}
+		purple_request_field_list_add( field, country[1], g_strdup( country[0] ) );
+		if ( strcmp( country[1], parts[6] ) == 0 ) {
+			/* based on the user's ip, this is his current country code, so we default to it */
+			purple_request_field_list_add_selected( field, country[1] );
+		}
+		g_strfreev( country );
+	}
+	purple_request_field_group_add_field( group, field );
+
+	/* choose your language */
+	locales = g_strsplit( parts[5], ",", 200 );
+	field = purple_request_field_list_new( "locale", _( "Your Language" ) );
+	purple_request_field_list_set_multi_select( field, FALSE );
+	for ( i = 0; locales[i]; i++ ) {
+		gchar**		locale;
+
+		locale = g_strsplit( locales[i], "|", 2 );
+		if ( !locale ) {
+			/* oops, this is not good, time to bail */
+			break;
+		}
+		purple_request_field_list_add( field, locale[1], g_strdup( locale[0] ) );
+		g_strfreev( locale );
+	}
+	purple_request_field_list_add_selected( field, "English" );
+	purple_request_field_group_add_field( group, field );
+
+	/* display the form to the user and wait for his/her input */
+	purple_request_fields( session->con, "MXit", _( "MXit Authorization" ), _( "MXit account validation" ), fields,
+			_( "Continue" ), G_CALLBACK( mxit_cb_captcha_ok ), _( "Cancel" ), G_CALLBACK( mxit_cb_captcha_cancel ), session->acc, NULL, NULL, session->con );
+
+	/* freeup the memory */
+	g_strfreev( parts );
+}
+
+
+/*------------------------------------------------------------------------
+ * Initiate a request for the client information (distribution code, client key, etc)
+ *  required for logging in from the MXit WAP site.
+ *
+ *  @param session		The MXit session object
+ */
+static void get_clientinfo( struct MXitSession* session )
+{
+	PurpleUtilFetchUrlData*	url_data;
+	const char*				wapserver;
+	char*					url;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "get_clientinfo\n" );
+
+	purple_connection_update_progress( session->con, _( "Retrieving User Information..." ), 0, 4 );
+
+	/* get the WAP site as was configured by the user in the advanced settings */
+	wapserver = purple_account_get_string( session->acc, MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE );
+
+	/* reference: "libpurple/util.h" */
+	url = g_strdup_printf( "%s/res/?type=challenge&getcountries=true&getlanguage=true&getimage=true&h=%i&w=%i&ts=%li", wapserver, MXIT_CAPTCHA_HEIGHT, MXIT_CAPTCHA_WIDTH, time( NULL ) );
+	url_data = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_clientinfo1, session );
+
+#ifdef	DEBUG_PROTOCOL
+	purple_debug_info( MXIT_PLUGIN_ID, "HTTP REQUEST: '%s'\n", url );
+#endif
+	g_free( url );
+}
+
+
+/*------------------------------------------------------------------------
+ * Log the user into MXit.
+ *
+ *  @param account		The account object
+ */
+void mxit_login( PurpleAccount* account )
+{
+	struct MXitSession*		session		= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_login\n" );
+
+	/* create and save a new mxit session */
+	session = mxit_create_object( account );
+
+	/*
+	 * before we can login we need to have a valid distribution code and client key for authentication.
+	 * if we don't have any info saved from a previous login, we need to get it from the MXit WAP site.
+	 * we do cache it, so this step is only done on the very first login for each account.
+	 */
+	if ( ( session->distcode == NULL ) || ( strlen( session->distcode ) == 0 ) ) {
+		/* this must be the very first login, so we need to retrieve the user information */
+		get_clientinfo( session );
+	}
+	else {
+		/* we can continue with the login */
+		mxit_login_connect( session );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Perform a reconnect to the MXit server, and maintain same session object.
+ *
+ *  @param account		The account object
+ */
+void mxit_reconnect( struct MXitSession* session )
+{
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_reconnect\n" );
+
+	/* close existing connection */
+	session->flags &= ~MXIT_FLAG_CONNECTED;
+	purple_proxy_connect_cancel_with_handle( session->con );
+
+	/* perform the re-connect */
+	mxit_login_connect( session );
+}
+
+
+/*------------------------------------------------------------------------
+ * Register a new account with MXit
+ *
+ * @param acc		The account object
+ */
+void mxit_register( PurpleAccount* account )
+{
+	struct MXitSession*		session		= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_register\n" );
+
+	/* create and save a new mxit session */
+	session = mxit_create_object( account );
+	purple_account_set_int( account, MXIT_CONFIG_STATE, MXIT_STATE_REGISTER1 );
+
+	get_clientinfo( session );
+}
+