changeset 29304:f85510c955e4

propagate from branch 'im.pidgin.pidgin' (head 8b6b0e8385b470c8181ddd6b6fd6d8e708c08f9e) to branch 'im.pidgin.cpw.attention_ui' (head 88ae6a30dcde06957d2d1f668c1cf068ea24c308)
author Marcus Lundblad <ml@update.uu.se>
date Thu, 12 Nov 2009 23:25:57 +0000
parents 40b523845a6e (current diff) 738cd1adb3cf (diff)
children 410d47b6e266
files libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/message.c pidgin/pixmaps/Makefile.am
diffstat 75 files changed, 11641 insertions(+), 231 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Tue Nov 03 18:27:22 2009 +0000
+++ b/COPYRIGHT	Thu Nov 12 23:25:57 2009 +0000
@@ -268,6 +268,7 @@
 Ambrose C. Li
 Nicolas Lichtmaier
 Wesley Lin
+Shaun Lindsay
 Artem Litvinovich
 Josh Littlefield
 Daniel Ljungborg
@@ -275,6 +276,7 @@
 Lokheed
 Norberto Lopes
 Shlomi Loubaton
+Pieter Loubser
 Brian Lu
 Uli Luckas
 Matthew Luckie
@@ -318,6 +320,7 @@
 Sergio Moretto
 Andrei Mozzhuhin
 Christian Muise
+MXit Lifestyle (Pty) Ltd.
 Richard Nelson
 Dennis Nezic
 Matthew A. Nicholson
@@ -496,6 +499,7 @@
 James Vega
 David Vermeille
 Sid Vicious
+Andrew Victor
 Jorge VillaseƱor (Masca)
 Bjoern Voigt
 Wan Hing Wah
--- a/ChangeLog	Tue Nov 03 18:27:22 2009 +0000
+++ b/ChangeLog	Thu Nov 12 23:25:57 2009 +0000
@@ -1,8 +1,11 @@
+
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
 version 2.6.4 (??/??/20??):
 	libpurple:
 	* Actually emit the hold signal for media calls.
+	* Added "MXit" protocol plugin, supported and maintained by the MXit folks
+	  themselves (MXit Lifestyle (Pty) Ltd.)
 
 	General:
 	* New 'plugins' sub-command to 'debug' command (i.e. '/debug plugins')
@@ -14,6 +17,10 @@
 	AIM and ICQ:
 	* Better rate limit calculations and other improvements.  (Aman Gupta)
 	* More detailed error messages when messages fail to send.  (Aman Gupta)
+	* The simultaneous login account option is respected when using
+	  the clientLogin authentication method.
+	* Fix offline message retrieval (broken in 2.6.3)
+	* Fix SSL when clientLogin is enabled.
 
 	MSN:
 	* Don't forget display names for buddies.
@@ -21,6 +28,9 @@
 	* Fix more FQY 240 connection errors.
 	* Fix a crash that could occur when adding a buddy.
 	* Fix an occasional crash when sending message to an offline user.
+	* Fix a random crash that might occur when idle.
+	* Fix a crash when logging in with some long non-ASCII passwords.
+	  (Shaun Lindsay)
 
 	XMPP:
 	* Users connecting to Google Talk now have an "Initiate Chat" context menu
@@ -28,7 +38,12 @@
 	* Fix a crash when attempting to validate an invalid JID.
 	* Resolve an issue when connecting to iChat Server when no resource
 	  is specified.
+	* Try to automatically find a STUN server by using an SRV lookup on the
+	  account's domain, and use that for voice and video if found and the user 
+	  didn't set one manually in prefs.
 	* Fix a crash when adding a buddy without an '@'.
+	* Don't show the option to send a file to a buddy if we know for certain
+	  they don't support any file transfer method supported by libpurple.
 
 	Yahoo:
 	* Fix sending /buzz.
@@ -47,12 +62,6 @@
 	* The userlist in a multiuser chat can be styled via gtkrc by using the
 	  widget name "pidgin_conv_userlist". (Heiko Schmitt)
 	* Add a hold button to the media window.
-	* Tooltips for custom smileys should work now.
-	* Users with unread messages are again bolded in the Buddy List.
-	* Minor reworking of the Preferences window's Network tab to make it need
-	  less vertical space.
-	* The global "Use remote DNS with SOCKS4 proxies" preference no longer
-	  disappears when the preference value changes in certain ways.
 
 version 2.6.3 (10/16/2009):
 	General:
--- a/configure.ac	Tue Nov 03 18:27:22 2009 +0000
+++ b/configure.ac	Thu Nov 12 23:25:57 2009 +0000
@@ -1078,7 +1078,7 @@
 fi
 
 if test "x$STATIC_PRPLS" = "xall" ; then
-	STATIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr"
+	STATIC_PRPLS="bonjour gg irc jabber msn myspace mxit novell oscar qq sametime silc simple yahoo zephyr"
 fi
 if test "x$have_meanwhile" != "xyes" ; then
 	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'`
@@ -1135,6 +1135,7 @@
 		msn)		static_msn=yes ;;
 		msnp9)		static_msn=yes ;;
 		myspace)	static_myspace=yes ;;
+		mxit)		static_mxit=yes ;;
 		novell)		static_novell=yes ;;
 		oscar)		static_oscar=yes ;;
 		aim)		static_oscar=yes ;;
@@ -1155,6 +1156,7 @@
 AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes")
 AM_CONDITIONAL(STATIC_MSN, test "x$static_msn" = "xyes")
 AM_CONDITIONAL(STATIC_MYSPACE, test "x$static_myspace" = "xyes")
+AM_CONDITIONAL(STATIC_MXIT, test "x$static_mxit" = "xyes")
 AM_CONDITIONAL(STATIC_NOVELL, test "x$static_novell" = "xyes")
 AM_CONDITIONAL(STATIC_OSCAR, test "x$static_oscar" = "xyes")
 AM_CONDITIONAL(STATIC_QQ, test "x$static_qq" = "xyes")
@@ -1169,7 +1171,7 @@
 
 AC_ARG_WITH(dynamic_prpls, [AC_HELP_STRING([--with-dynamic-prpls], [specify which protocols to build dynamically])], [DYNAMIC_PRPLS=`echo $withval | $sedpath 's/,/ /g'`])
 if test "x$DYNAMIC_PRPLS" = "xall" ; then
-	DYNAMIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr"
+	DYNAMIC_PRPLS="bonjour gg irc jabber msn myspace mxit novell oscar qq sametime silc simple yahoo zephyr"
 fi
 if test "x$have_meanwhile" != "xyes"; then
 	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'`
@@ -1196,6 +1198,7 @@
 		msn)		dynamic_msn=yes ;;
 		msnp9)		dynamic_msn=yes ;;
 		myspace)	dynamic_myspace=yes ;;
+		mxit)		dynamic_mxit=yes ;;
 		novell)		dynamic_novell=yes ;;
 		null)		dynamic_null=yes ;;
 		oscar)		dynamic_oscar=yes ;;
@@ -2529,6 +2532,7 @@
 		   libpurple/protocols/msn/Makefile
 		   libpurple/protocols/msnp9/Makefile
 		   libpurple/protocols/myspace/Makefile
+		   libpurple/protocols/mxit/Makefile
 		   libpurple/protocols/novell/Makefile
 		   libpurple/protocols/null/Makefile
 		   libpurple/protocols/oscar/Makefile
--- a/finch/libgnt/gnttree.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/finch/libgnt/gnttree.c	Thu Nov 12 23:25:57 2009 +0000
@@ -815,7 +815,7 @@
 		gnt_widget_activate(widget);
 	} else if (tree->priv->search) {
 		gboolean changed = TRUE;
-		if (isalnum(*text)) {
+		if (g_unichar_isprint(*text)) {
 			tree->priv->search = g_string_append_c(tree->priv->search, *text);
 		} else if (g_utf8_collate(text, GNT_KEY_BACKSPACE) == 0) {
 			if (tree->priv->search->len)
--- a/libpurple/media.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/media.c	Thu Nov 12 23:25:57 2009 +0000
@@ -2281,7 +2281,8 @@
 					stream->session->type), NULL);
 			stream->accepted = TRUE;
 
-			if (stream->remote_candidates != NULL) {
+			if (stream->remote_candidates != NULL &&
+					stream->initiator == FALSE) {
 				GError *err = NULL;
 				fs_stream_set_remote_candidates(stream->stream,
 						stream->remote_candidates, &err);
@@ -2816,14 +2817,16 @@
 			}
 
 			fsstream = fs_session_new_stream(session->session,
-					participant, type_direction &
-					FS_DIRECTION_RECV, transmitter,
+					participant, initiator == TRUE ?
+					type_direction : (type_direction &
+					FS_DIRECTION_RECV), transmitter,
 					new_num_params, param, &err);
 			g_free(param);
 		} else {
 			fsstream = fs_session_new_stream(session->session,
-					participant, type_direction &
-					FS_DIRECTION_RECV, transmitter,
+					participant, initiator == TRUE ?
+					type_direction : (type_direction &
+					FS_DIRECTION_RECV), transmitter,
 					num_params, params, &err);
 		}
 
@@ -2952,7 +2955,7 @@
 	stream->remote_candidates = g_list_concat(stream->remote_candidates,
 			purple_media_candidate_list_to_fs(remote_candidates));
 
-	if (stream->accepted == TRUE) {
+	if (stream->initiator == TRUE || stream->accepted == TRUE) {
 		fs_stream_set_remote_candidates(stream->stream,
 				stream->remote_candidates, &err);
 
--- a/libpurple/protocols/Makefile.am	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/Makefile.am	Thu Nov 12 23:25:57 2009 +0000
@@ -1,5 +1,5 @@
 EXTRA_DIST = Makefile.mingw
 
-DIST_SUBDIRS = bonjour gg irc jabber msn msnp9 myspace novell null oscar qq sametime silc silc10 simple yahoo zephyr
+DIST_SUBDIRS = bonjour gg irc jabber msn msnp9 myspace mxit novell null oscar qq sametime silc silc10 simple yahoo zephyr
 
 SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS)
--- a/libpurple/protocols/bonjour/mdns_avahi.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Thu Nov 12 23:25:57 2009 +0000
@@ -150,6 +150,10 @@
 			}
 			break;
 		case AVAHI_RESOLVER_FOUND:
+
+			purple_debug_info("bonjour", "_resolve_callback - name:%s account:%p bb:%p\n",
+				name, account, bb);
+
 			/* create a buddy record */
 			if (bb == NULL)
 				bb = bonjour_buddy_new(name, account);
@@ -173,8 +177,12 @@
 
 
 			/* Get the ip as a string */
+			ip[0] = '\0';
 			avahi_address_snprint(ip, AVAHI_ADDRESS_STR_MAX, a);
 
+			purple_debug_info("bonjour", "_resolve_callback - name:%s ip:%s prev_ip:%s\n",
+				name, ip, rd->ip);
+
 			if (rd->ip == NULL || strcmp(rd->ip, ip) != 0) {
 				/* We store duplicates in bb->ips, so we always remove the one */
 				if (rd->ip != NULL) {
--- a/libpurple/protocols/jabber/auth.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/auth.c	Thu Nov 12 23:25:57 2009 +0000
@@ -58,7 +58,7 @@
 				PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
 				_("Server requires TLS/SSL, but no TLS/SSL support was found."));
 			return TRUE;
-		} else if(purple_account_get_bool(js->gc->account, "require_tls", FALSE)) {
+		} else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
 			purple_connection_error_reason(js->gc,
 				 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
 				_("You require encryption, but no TLS/SSL support was found."));
@@ -381,13 +381,13 @@
 				 * due to mechanism specific issues, so we want to try one of the other
 				 * supported mechanisms. This code handles that case
 				 */
-				if (js->current_mech && strlen(js->current_mech) > 0) {
+				if (js->current_mech && *js->current_mech) {
 					char *pos;
 					if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) {
 						g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech));
 					}
 					/* Remove space which separated this mech from the next */
-					if (strlen(js->sasl_mechs->str) > 0 && ((js->sasl_mechs->str)[0] == ' ')) {
+					if ((js->sasl_mechs->str)[0] == ' ') {
 						g_string_erase(js->sasl_mechs, 0, 1);
 					}
 					again = TRUE;
@@ -511,7 +511,7 @@
 		 * support it and including it gives a false fall-back to other mechs offerred,
 		 * leading to incorrect error handling.
 		 */
-		if (mech_name && !strcmp(mech_name, "X-GOOGLE-TOKEN")) {
+		if (purple_strequal(mech_name, "X-GOOGLE-TOKEN")) {
 			g_free(mech_name);
 			continue;
 		}
@@ -519,9 +519,9 @@
 		g_string_append(js->sasl_mechs, mech_name);
 		g_string_append_c(js->sasl_mechs, ' ');
 #else
-		if(mech_name && !strcmp(mech_name, "DIGEST-MD5"))
+		if (purple_strequal(mech_name, "DIGEST-MD5"))
 			digest_md5 = TRUE;
-		else if(mech_name && !strcmp(mech_name, "PLAIN"))
+		else if (purple_strequal(mech_name, "PLAIN"))
 			plain = TRUE;
 #endif
 		g_free(mech_name);
@@ -586,7 +586,7 @@
 		/* FIXME: Why is this not in jabber_parse_error? */
 		if((error = xmlnode_get_child(packet, "error")) &&
 					(err_code = xmlnode_get_attrib(error, "code")) &&
-					!strcmp(err_code, "401")) {
+					g_str_equal(err_code, "401")) {
 			reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
 			/* Clear the pasword if it isn't being saved */
 			if (!purple_account_get_remember_password(js->gc->account))
@@ -698,7 +698,7 @@
 	 * is requiring SSL/TLS, we need to enforce it.
 	 */
 	if (!jabber_stream_is_ssl(js) &&
-			purple_account_get_bool(purple_connection_get_account(js->gc), "require_tls", FALSE)) {
+			purple_account_get_bool(purple_connection_get_account(js->gc), "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
 		purple_connection_error_reason(js->gc,
 			PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
 			_("You require encryption, but it is not available on this server."));
@@ -877,7 +877,7 @@
 		}
 
 		dec_in = (char *)purple_base64_decode(enc_in, NULL);
-		purple_debug(PURPLE_DEBUG_MISC, "jabber", "decoded challenge (%"
+		purple_debug_misc("jabber", "decoded challenge (%"
 				G_GSIZE_FORMAT "): %s\n", strlen(dec_in), dec_in);
 
 		parts = parse_challenge(dec_in);
@@ -887,8 +887,7 @@
 			char *rspauth = g_hash_table_lookup(parts, "rspauth");
 
 
-			if(rspauth && js->expected_rspauth &&
-					!strcmp(rspauth, js->expected_rspauth)) {
+			if (rspauth && purple_strequal(rspauth, js->expected_rspauth)) {
 				jabber_send_raw(js,
 						"<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />",
 						-1);
@@ -1014,7 +1013,7 @@
 				 * realm are always encoded in UTF-8 (they seem to be the values
 				 * we pass in), so we need to ensure charset=utf-8 is set.
 				 */
-				if (!js->current_mech || !g_str_equal(js->current_mech, "DIGEST-MD5") ||
+				if (!purple_strequal(js->current_mech, "DIGEST-MD5") ||
 						strstr(c_out, ",charset="))
 					/* If we're not using DIGEST-MD5 or Cyrus SASL is fixed */
 					enc_out = purple_base64_encode((unsigned char*)c_out, clen);
@@ -1041,7 +1040,7 @@
 	const void *x;
 #endif
 
-	if(!ns || strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) {
+	if (!purple_strequal(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) {
 		purple_connection_error_reason(js->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Invalid response from server"));
@@ -1072,6 +1071,7 @@
 			purple_connection_error_reason(js->gc,
 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Invalid response from server"));
+			g_return_if_reached();
 		}
 	}
 	/* If we've negotiated a security layer, we need to enable it */
@@ -1099,17 +1099,17 @@
 
 #ifdef HAVE_CYRUS_SASL
 	if(js->auth_fail_count++ < 5) {
-		if (js->current_mech && strlen(js->current_mech) > 0) {
+		if (js->current_mech && *js->current_mech) {
 			char *pos;
 			if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) {
 				g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech));
 			}
 			/* Remove space which separated this mech from the next */
-			if (strlen(js->sasl_mechs->str) > 0 && ((js->sasl_mechs->str)[0] == ' ')) {
+			if ((js->sasl_mechs->str)[0] == ' ') {
 				g_string_erase(js->sasl_mechs, 0, 1);
 			}
 		}
-		if (strlen(js->sasl_mechs->str)) {
+		if (*js->sasl_mechs->str) {
 			/* If we have remaining mechs to try, do so */
 			sasl_dispose(&js->sasl);
 
--- a/libpurple/protocols/jabber/buddy.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Thu Nov 12 23:25:57 2009 +0000
@@ -581,8 +581,7 @@
 		if (text != NULL && *text != '\0') {
 			xmlnode *xp;
 
-			purple_debug(PURPLE_DEBUG_INFO, "jabber",
-					"Setting %s to '%s'\n", vc_tp->tag, text);
+			purple_debug_info("jabber", "Setting %s to '%s'\n", vc_tp->tag, text);
 
 			if ((xp = insert_tag_to_parent_tag(vc_node,
 											   NULL, vc_tp->tag)) != NULL) {
@@ -2342,6 +2341,12 @@
 }
 
 gboolean
+jabber_resource_know_capabilities(const JabberBuddyResource *jbr)
+{
+	return jbr->caps.info != NULL;
+}
+
+gboolean
 jabber_resource_has_capability(const JabberBuddyResource *jbr, const gchar *cap)
 {
 	const GList *node = NULL;
--- a/libpurple/protocols/jabber/buddy.h	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.h	Thu Nov 12 23:25:57 2009 +0000
@@ -123,6 +123,7 @@
 
 void jabber_vcard_fetch_mine(JabberStream *js);
 
+gboolean jabber_resource_know_capabilities(const JabberBuddyResource *jbr);
 gboolean jabber_resource_has_capability(const JabberBuddyResource *jbr,
 										const gchar *cap);
 gboolean jabber_buddy_has_capability(const JabberBuddy *jb, const gchar *cap);
--- a/libpurple/protocols/jabber/disco.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/disco.c	Thu Nov 12 23:25:57 2009 +0000
@@ -421,6 +421,76 @@
 
 }
 
+/* should probably share this code with google.c, or maybe from 2.7.0
+ introduce an abstracted hostname -> IP function in dns.c */
+static void
+jabber_disco_stun_lookup_cb(GSList *hosts, gpointer data,
+	const char *error_message)
+{
+	JabberStream *js = (JabberStream *) data;
+
+	if (error_message) {
+		purple_debug_error("jabber", "STUN lookup failed: %s\n",
+			error_message);
+		g_slist_free(hosts);
+		js->stun_query = NULL;
+		return;
+	}
+
+	if (hosts && g_slist_next(hosts)) {
+		struct sockaddr *addr = g_slist_next(hosts)->data;
+		char dst[INET6_ADDRSTRLEN];
+		int port;
+
+		if (addr->sa_family == AF_INET6) {
+			inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr,
+				dst, sizeof(dst));
+			port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port);
+		} else {
+			inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr,
+				dst, sizeof(dst));
+			port = ntohs(((struct sockaddr_in *) addr)->sin_port);
+		}
+
+		if (js->stun_ip)
+			g_free(js->stun_ip);
+		js->stun_ip = g_strdup(dst);
+		js->stun_port = port;
+
+		purple_debug_info("jabber", "set STUN IP/port address: "
+		                  "%s:%d\n", dst, port);
+
+		/* unmark ongoing query */
+		js->stun_query = NULL;
+	}
+
+	while (hosts != NULL) {
+		hosts = g_slist_delete_link(hosts, hosts);
+		/* Free the address */
+		g_free(hosts->data);
+		hosts = g_slist_delete_link(hosts, hosts);
+	}
+}
+
+
+static void
+jabber_disco_stun_srv_resolve_cb(PurpleSrvResponse *resp, int results, gpointer data)
+{
+	JabberStream *js = (JabberStream *) data;
+
+	purple_debug_info("jabber", "got %d SRV responses for STUN.\n", results);
+	js->srv_query_data = NULL;
+
+	if (results > 0) {
+		purple_debug_info("jabber", "looking up IP for %s:%d\n", 
+			resp[0].hostname, resp[0].port);
+		js->stun_query = 
+			purple_dnsquery_a(resp[0].hostname, resp[0].port, 
+				jabber_disco_stun_lookup_cb, js);
+	}
+}
+
+
 static void
 jabber_disco_server_info_result_cb(JabberStream *js, const char *from,
                                    JabberIqType type, const char *id,
@@ -471,7 +541,10 @@
 			/* autodiscover stun and relays */
 			jabber_google_send_jingle_info(js);
 		} else {
-			/* TODO: add external service discovery here... */
+			js->srv_query_data = 
+				purple_srv_resolve("stun", "udp", js->user->domain,
+					jabber_disco_stun_srv_resolve_cb, js);
+			/* TODO: add TURN support later... */
 		}
 	}
 
--- a/libpurple/protocols/jabber/google.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/google.c	Thu Nov 12 23:25:57 2009 +0000
@@ -910,7 +910,7 @@
 	xmlnode_set_attrib(iq->node, "id", id);
 	jabber_iq_send(iq);
 
-	purple_debug(PURPLE_DEBUG_MISC, "jabber",
+	purple_debug_misc("jabber",
 		   "Got new mail notification. Sending request for more info\n");
 
 	iq = jabber_iq_new_query(js, JABBER_IQ_GET, "google:mail:notify");
@@ -1094,12 +1094,13 @@
 			jbr = l->data;
 			if (jbr && jbr->name)
 			{
-				purple_debug(PURPLE_DEBUG_MISC, "jabber", "Removing resource %s\n", jbr->name);
+				purple_debug_misc("jabber", "Removing resource %s\n", jbr->name);
 				jabber_buddy_remove_resource(jb, jbr->name);
 			}
 			l = l->next;
 		}
 	}
+
 	purple_prpl_got_user_status(purple_connection_get_account(gc), who, "offline", NULL);
 }
 
--- a/libpurple/protocols/jabber/iq.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/iq.c	Thu Nov 12 23:25:57 2009 +0000
@@ -342,7 +342,7 @@
 		return;
 	}
 
-	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
+	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc),
 			"jabber-receiving-iq", js->gc, iq_type, id, from, packet));
 	if (signal_return)
 		return;
@@ -367,7 +367,7 @@
 		g_free(key);
 
 		if (signal_ref > 0) {
-			signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin, "jabber-watched-iq",
+			signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc), "jabber-watched-iq",
 					js->gc, iq_type, id, from, child));
 			if (signal_return)
 				return;
--- a/libpurple/protocols/jabber/jabber.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Thu Nov 12 23:25:57 2009 +0000
@@ -52,6 +52,7 @@
 #include "data.h"
 #include "disco.h"
 #include "google.h"
+#include "ibb.h"
 #include "iq.h"
 #include "jutil.h"
 #include "message.h"
@@ -68,10 +69,9 @@
 #include "jingle/jingle.h"
 #include "jingle/rtp.h"
 
-PurplePlugin *jabber_plugin = NULL;
 GList *jabber_features = NULL;
 GList *jabber_identities = NULL;
-GSList *jabber_cmds = NULL;
+static GSList *jabber_cmds = NULL;
 
 static void jabber_unregister_account_cb(JabberStream *js);
 static void try_srv_connect(JabberStream *js);
@@ -200,7 +200,7 @@
 			jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
 			return;
 		}
-	} else if(purple_account_get_bool(js->gc->account, "require_tls", FALSE) && !jabber_stream_is_ssl(js)) {
+	} else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS) && !jabber_stream_is_ssl(js)) {
 		purple_connection_error_reason(js->gc,
 			 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
 			_("You require encryption, but it is not available on this server."));
@@ -255,7 +255,7 @@
 {
 	const char *xmlns;
 
-	purple_signal_emit(jabber_plugin, "jabber-receiving-xmlnode", js->gc, packet);
+	purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-receiving-xmlnode", js->gc, packet);
 
 	/* if the signal leaves us with a null packet, we're done */
 	if(NULL == *packet)
@@ -294,8 +294,7 @@
 		else
 			purple_debug_warning("jabber", "Ignoring spurious <proceed/>\n");
 	} else {
-		purple_debug(PURPLE_DEBUG_WARNING, "jabber", "Unknown packet: %s\n",
-				(*packet)->name);
+		purple_debug_warning("jabber", "Unknown packet: %s\n", (*packet)->name);
 	}
 }
 
@@ -377,9 +376,9 @@
 
 void jabber_send_raw(JabberStream *js, const char *data, int len)
 {
-
 	/* because printing a tab to debug every minute gets old */
 	if(strcmp(data, "\t")) {
+		const char *username;
 		char *text = NULL, *last_part = NULL, *tag_start = NULL;
 
 		/* Because debug logs with plaintext passwords make me sad */
@@ -404,8 +403,13 @@
 			*data_start = '\0';
 		}
 
-		purple_debug(PURPLE_DEBUG_MISC, "jabber", "Sending%s: %s%s%s\n",
-				jabber_stream_is_ssl(js) ? " (ssl)" : "", text ? text : data,
+		username = purple_connection_get_display_name(js->gc);
+		if (!username)
+			username = purple_account_get_username(purple_connection_get_account(js->gc));
+
+		purple_debug_misc("jabber", "Sending%s (%s): %s%s%s\n",
+				jabber_stream_is_ssl(js) ? " (ssl)" : "", username,
+				text ? text : data,
 				last_part ? "password removed" : "",
 				last_part ? last_part : "");
 
@@ -415,7 +419,7 @@
 	/* If we've got a security layer, we need to encode the data,
 	 * splitting it on the maximum buffer length negotiated */
 
-	purple_signal_emit(jabber_plugin, "jabber-sending-text", js->gc, &data);
+	purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-sending-text", js->gc, &data);
 	if (data == NULL)
 		return;
 
@@ -485,7 +489,7 @@
 
 void jabber_send(JabberStream *js, xmlnode *packet)
 {
-	purple_signal_emit(jabber_plugin, "jabber-sending-xmlnode", js->gc, &packet);
+	purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-sending-xmlnode", js->gc, &packet);
 }
 
 static gboolean jabber_keepalive_timeout(PurpleConnection *gc)
@@ -526,7 +530,7 @@
 	while((len = purple_ssl_read(gsc, buf, sizeof(buf) - 1)) > 0) {
 		gc->last_received = time(NULL);
 		buf[len] = '\0';
-		purple_debug(PURPLE_DEBUG_INFO, "jabber", "Recv (ssl)(%d): %s\n", len, buf);
+		purple_debug_info("jabber", "Recv (ssl)(%d): %s\n", len, buf);
 		jabber_parser_process(js, buf, len);
 		if(js->reinit)
 			jabber_stream_init(js);
@@ -566,7 +570,7 @@
 			unsigned int olen;
 			sasl_decode(js->sasl, buf, len, &out, &olen);
 			if (olen>0) {
-				purple_debug(PURPLE_DEBUG_INFO, "jabber", "RecvSASL (%u): %s\n", olen, out);
+				purple_debug_info("jabber", "RecvSASL (%u): %s\n", olen, out);
 				jabber_parser_process(js,out,olen);
 				if(js->reinit)
 					jabber_stream_init(js);
@@ -575,7 +579,7 @@
 		}
 #endif
 		buf[len] = '\0';
-		purple_debug(PURPLE_DEBUG_INFO, "jabber", "Recv (%d): %s\n", len, buf);
+		purple_debug_info("jabber", "Recv (%d): %s\n", len, buf);
 		jabber_parser_process(js, buf, len);
 		if(js->reinit)
 			jabber_stream_init(js);
@@ -3218,6 +3222,50 @@
 #endif
 }
 
+gboolean jabber_can_receive_file(PurpleConnection *gc, const char *who)
+{
+	JabberStream *js = gc->proto_data;
+
+	if (js) {
+		JabberBuddy *jb = jabber_buddy_find(js, who, FALSE);
+		GList *iter;
+		gboolean has_resources_without_caps = FALSE;
+
+		/* find out if there is any resources without caps */
+		for (iter = jb->resources; iter ; iter = g_list_next(iter)) {
+			JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data;
+
+			if (!jabber_resource_know_capabilities(jbr)) {
+				has_resources_without_caps = TRUE;
+			}
+		}
+
+		if (has_resources_without_caps) {
+			/* there is at least one resource which we don't have caps for, 
+			 let's assume they can receive files... */
+			return TRUE;
+		} else {
+			/* we have caps for all the resources, see if at least one has
+			 right caps */
+			for (iter = jb->resources; iter ; iter = g_list_next(iter)) {
+				JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data;
+
+				if (jabber_resource_has_capability(jbr,
+						"http://jabber.org/protocol/si/profile/file-transfer")
+			    	&& (jabber_resource_has_capability(jbr,
+			    			"http://jabber.org/protocol/bytestreams")
+			        	|| jabber_resource_has_capability(jbr,
+				           		XEP_0047_NAMESPACE))) {
+					return TRUE;
+				}
+			}
+			return FALSE;
+		}
+	} else {
+		return TRUE;
+	}
+}
+
 void jabber_register_commands(void)
 {
 	PurpleCmdId id;
@@ -3398,8 +3446,6 @@
 								unspecified */
 	const gchar *ui_name = NULL;
 
-	jabber_plugin = plugin;
-
 	ui_type = ui_info ? g_hash_table_lookup(ui_info, "client_type") : NULL;
 	if (ui_type) {
 		if (strcmp(ui_type, "pc") == 0 ||
@@ -3489,9 +3535,9 @@
 }
 
 void
-jabber_uninit_plugin(void)
+jabber_uninit_plugin(PurplePlugin *plugin)
 {
-	purple_plugin_ipc_unregister_all(jabber_plugin);
+	purple_plugin_ipc_unregister_all(plugin);
 
 	jabber_features_destroy();
 	jabber_identities_destroy();
--- a/libpurple/protocols/jabber/jabber.h	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Thu Nov 12 23:25:57 2009 +0000
@@ -76,11 +76,11 @@
 
 #define CAPS0115_NODE "http://pidgin.im/"
 
+#define JABBER_DEFAULT_REQUIRE_TLS    TRUE
+
 /* Index into attention_types list */
 #define JABBER_BUZZ 0
 
-extern PurplePlugin *jabber_plugin;
-
 typedef enum {
 	JABBER_STREAM_OFFLINE,
 	JABBER_STREAM_CONNECTING,
@@ -193,25 +193,16 @@
 
 	char *serverFQDN;
 
-	/* OK, this stays at the end of the struct, so plugins can depend
-	 * on the rest of the stuff being in the right place
-	 */
 #ifdef HAVE_CYRUS_SASL
 	sasl_conn_t *sasl;
 	sasl_callback_t *sasl_cb;
-#else /* keep the struct the same size */
-	void *sasl;
-	void *sasl_cb;
-#endif
-	/* did someone say something about the end of the struct? */
-#ifdef HAVE_CYRUS_SASL
 	const char *current_mech;
 	int auth_fail_count;
-#endif
 
 	int sasl_state;
 	int sasl_maxbuf;
 	GString *sasl_mechs;
+#endif
 
 	gboolean unregistration;
 	PurpleAccountUnregistrationCb unregistration_cb;
@@ -377,11 +368,12 @@
 gboolean jabber_initiate_media(PurpleAccount *account, const char *who,
 		PurpleMediaSessionType type);
 PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who);
+gboolean jabber_can_receive_file(PurpleConnection *gc, const gchar *who);
 
 void jabber_register_commands(void);
 void jabber_unregister_commands(void);
 
 void jabber_init_plugin(PurplePlugin *plugin);
-void jabber_uninit_plugin(void);
+void jabber_uninit_plugin(PurplePlugin *plugin);
 
 #endif /* PURPLE_JABBER_H_ */
--- a/libpurple/protocols/jabber/jingle/jingle.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.c	Thu Nov 12 23:25:57 2009 +0000
@@ -442,15 +442,15 @@
 	if (num_params > 0) {
 		params = g_new0(GParameter, num_params);
 
-		purple_debug_info("jabber", 
-						  "setting param stun-ip for stream using Google auto-config: %s\n",
-						  js->stun_ip);
+		purple_debug_info("jabber",
+			"setting param stun-ip for stream using auto-discovered IP: %s\n",
+			js->stun_ip);
 		params[0].name = "stun-ip";
 		g_value_init(&params[0].value, G_TYPE_STRING);
 		g_value_set_string(&params[0].value, js->stun_ip);
 		purple_debug_info("jabber", 
-						  "setting param stun-port for stream using Google auto-config: %d\n",
-						  js->stun_port);
+			"setting param stun-port for stream using auto-discovered port: %d\n",
+			js->stun_port);
 		params[1].name = "stun-port";
 		g_value_init(&params[1].value, G_TYPE_UINT);
 		g_value_set_uint(&params[1].value, js->stun_port);
--- a/libpurple/protocols/jabber/jingle/rtp.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/rtp.c	Thu Nov 12 23:25:57 2009 +0000
@@ -823,6 +823,64 @@
 			g_object_unref(session);
 			break;
 		}
+		case JINGLE_DESCRIPTION_INFO: {
+			JingleSession *session =
+					jingle_content_get_session(content);
+			xmlnode *description = xmlnode_get_child(
+					xmlcontent, "description");
+			GList *codecs, *iter, *iter2, *remote_codecs =
+					jingle_rtp_parse_codecs(description);
+			gchar *name = jingle_content_get_name(content);
+			gchar *remote_jid =
+					jingle_session_get_remote_jid(session);
+			PurpleMedia *media;
+
+			media = jingle_rtp_get_media(session);
+
+			/*
+			 * This may have problems if description-info is
+			 * received without the optional parameters for a
+			 * codec with configuration info (such as THEORA
+			 * or H264). The local configuration info may be
+			 * set for the remote codec.
+			 *
+			 * As of 2.6.3 there's no API to support getting
+			 * the remote codecs specifically, just the
+			 * intersection. Another option may be to cache
+			 * the remote codecs received in initiate/accept.
+			 */
+			codecs = purple_media_get_codecs(media, name);
+
+			for (iter = codecs; iter; iter = g_list_next(iter)) {
+				guint id;
+
+				id = purple_media_codec_get_id(iter->data);
+				iter2 = remote_codecs;
+
+				for (; iter2; iter2 = g_list_next(iter2)) {
+					if (purple_media_codec_get_id(
+							iter2->data) != id)
+						continue;
+
+					g_object_unref(iter->data);
+					iter->data = iter2->data;
+					remote_codecs = g_list_delete_link(
+							remote_codecs, iter2);
+					break;
+				}
+			}
+
+			codecs = g_list_concat(codecs, remote_codecs);
+
+			purple_media_set_remote_codecs(media,
+					name, remote_jid, codecs);
+
+			purple_media_codec_list_free (codecs);
+			g_free(remote_jid);
+			g_free(name);
+			g_object_unref(session);
+			break;
+		}
 		default:
 			break;
 	}
--- a/libpurple/protocols/jabber/libxmpp.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Thu Nov 12 23:25:57 2009 +0000
@@ -111,7 +111,7 @@
 	jabber_roomlist_get_list,		/* roomlist_get_list */
 	jabber_roomlist_cancel,			/* roomlist_cancel */
 	NULL,							/* roomlist_expand_category */
-	NULL,							/* can_receive_file */
+	jabber_can_receive_file,		/* can_receive_file */
 	jabber_si_xfer_send,			/* send_file */
 	jabber_si_new_xfer,				/* new_xfer */
 	jabber_offline_message,			/* offline_message */
@@ -227,7 +227,7 @@
 	jabber_unregister_commands();
 
 	/* Stay on target...stay on target... Almost there... */
-	jabber_uninit_plugin();
+	jabber_uninit_plugin(plugin);
 
 	return TRUE;
 }
@@ -355,7 +355,7 @@
 	purple_account_user_split_set_reverse(split, FALSE);
 	prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
 
-	option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", TRUE);
+	option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", JABBER_DEFAULT_REQUIRE_TLS);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
 											   option);
 
--- a/libpurple/protocols/jabber/message.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/message.c	Thu Nov 12 23:25:57 2009 +0000
@@ -555,7 +555,7 @@
 	to   = xmlnode_get_attrib(packet, "to");
 	type = xmlnode_get_attrib(packet, "type");
 
-	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
+	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc),
 			"jabber-receiving-message", js->gc, type, id, from, to, packet));
 	if (signal_return)
 		return;
@@ -822,7 +822,7 @@
 
 	switch(jm->type) {
 		case JABBER_MESSAGE_OTHER:
-			purple_debug(PURPLE_DEBUG_INFO, "jabber",
+			purple_debug_info("jabber",
 					"Received message of unknown type: %s\n", type);
 			/* Fall-through is intentional */
 		case JABBER_MESSAGE_NORMAL:
@@ -1113,7 +1113,7 @@
 		if ((child = xmlnode_from_str(jm->xhtml, -1))) {
 			xmlnode_insert_child(message, child);
 		} else {
-			purple_debug(PURPLE_DEBUG_ERROR, "jabber",
+			purple_debug_error("jabber",
 					"XHTML translation/validation failed, returning: %s\n",
 					jm->xhtml);
 		}
--- a/libpurple/protocols/jabber/oob.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/oob.c	Thu Nov 12 23:25:57 2009 +0000
@@ -95,7 +95,7 @@
 	if(len < 0 && errno == EAGAIN)
 		return;
 	else if(len < 0) {
-		purple_debug(PURPLE_DEBUG_ERROR, "jabber", "Write error on oob xfer!\n");
+		purple_debug_error("jabber", "Write error on oob xfer!\n");
 		purple_input_remove(jox->writeh);
 		purple_xfer_cancel_local(xfer);
 	}
@@ -150,7 +150,7 @@
 		}
 		return 0;
 	} else if (errno != EAGAIN) {
-		purple_debug(PURPLE_DEBUG_ERROR, "jabber", "Read error on oob xfer!\n");
+		purple_debug_error("jabber", "Read error on oob xfer!\n");
 		purple_xfer_cancel_local(xfer);
 	}
 
--- a/libpurple/protocols/jabber/presence.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Thu Nov 12 23:25:57 2009 +0000
@@ -518,7 +518,7 @@
 	jb = jabber_buddy_find(js, from, TRUE);
 	g_return_if_fail(jb != NULL);
 
-	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(jabber_plugin,
+	signal_return = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_connection_get_prpl(js->gc),
 			"jabber-receiving-presence", js->gc, type, from, packet));
 	if (signal_return)
 		return;
--- a/libpurple/protocols/jabber/si.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/si.c	Thu Nov 12 23:25:57 2009 +0000
@@ -1349,7 +1349,7 @@
 		jabber_ibb_session_close(jsx->ibb_session);
 	}
 	jabber_si_xfer_free(xfer);
-	purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_send\n");
+	purple_debug_info("jabber", "in jabber_si_xfer_cancel_send\n");
 }
 
 
@@ -1381,7 +1381,7 @@
 	}
 
 	jabber_si_xfer_free(xfer);
-	purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_request_denied\n");
+	purple_debug_info("jabber", "in jabber_si_xfer_request_denied\n");
 }
 
 
@@ -1393,7 +1393,7 @@
 		jabber_ibb_session_close(jsx->ibb_session);
 	}
 	jabber_si_xfer_free(xfer);
-	purple_debug(PURPLE_DEBUG_INFO, "jabber", "in jabber_si_xfer_cancel_recv\n");
+	purple_debug_info("jabber", "in jabber_si_xfer_cancel_recv\n");
 }
 
 
--- a/libpurple/protocols/jabber/useravatar.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/jabber/useravatar.c	Thu Nov 12 23:25:57 2009 +0000
@@ -262,7 +262,7 @@
 	gpointer icon_data;
 
 	if(!url_text) {
-		purple_debug(PURPLE_DEBUG_ERROR, "jabber",
+		purple_debug_error("jabber",
 		             "do_buddy_avatar_update_fromurl got error \"%s\"",
 		             error_message);
 		goto out;
--- a/libpurple/protocols/msn/nexus.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/msn/nexus.c	Thu Nov 12 23:25:57 2009 +0000
@@ -399,7 +399,14 @@
 
 	username = purple_account_get_username(session->account);
 	password = purple_connection_get_password(session->account->gc);
-	password_xml = g_markup_escape_text(password, MIN(strlen(password), 16));
+	if (g_utf8_strlen(password, -1) > 16) {
+		/* max byte size for 16 utf8 characters is 64 + 1 for the null */
+		gchar truncated[65];
+		g_utf8_strncpy(truncated, password, 16);
+		password_xml = g_markup_escape_text(truncated, -1);
+	} else {
+		password_xml = g_markup_escape_text(password, -1);
+	}
 
 	purple_debug_info("msn", "Logging on %s, with policy '%s', nonce '%s'\n",
 	                  username, nexus->policy, nexus->nonce);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/Makefile.am	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,63 @@
+EXTRA_DIST = \
+	Makefile.mingw
+
+pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
+
+MXITSOURCES = \
+	actions.c \
+	actions.h \
+	aes.c \
+	aes.h \
+	chunk.c \
+	chunk.h \
+	cipher.c \
+	cipher.h \
+	filexfer.c \
+	filexfer.h \
+	formcmds.c \
+	formcmds.h \
+	http.c \
+	http.h \
+	login.c \
+	login.h \
+	markup.c \
+	markup.h \
+	multimx.c \
+	multimx.h \
+	mxit.c \
+	mxit.h \
+	profile.c \
+	profile.h \
+	protocol.c \
+	protocol.h \
+	roster.c \
+	roster.h \
+	splashscreen.c \
+	splashscreen.h
+
+
+AM_CFLAGS = $(st)
+
+libmxit_la_LDFLAGS = -module -avoid-version
+
+if STATIC_MXIT
+
+st = -DPURPLE_STATIC_PRPL
+noinst_LTLIBRARIES = libmxit.la
+libmxit_la_SOURCES  = $(MXITSOURCES)
+libmxit_la_CFLAGS   = $(AM_CFLAGS)
+
+else
+
+st =
+pkg_LTLIBRARIES   = libmxit.la
+libmxit_la_SOURCES = $(MXITSOURCES)
+libmxit_la_LIBADD  = $(GLIB_LIBS)
+
+endif
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/libpurple \
+	-I$(top_builddir)/libpurple \
+	$(GLIB_CFLAGS) \
+	$(DEBUG_CFLAGS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/Makefile.mingw	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,91 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of libmxit
+#
+
+PIDGIN_TREE_TOP := ../../..
+include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+
+TARGET = libmxit
+TYPE = PLUGIN
+
+# Static or Plugin...
+ifeq ($(TYPE),STATIC)
+  DEFINES += -DSTATIC
+  DLL_INSTALL_DIR =	$(PURPLE_INSTALL_DIR)
+else
+ifeq ($(TYPE),PLUGIN)
+  DLL_INSTALL_DIR =	$(PURPLE_INSTALL_PLUGINS_DIR)
+endif
+endif
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS +=	-I. \
+			-I$(GTK_TOP)/include \
+			-I$(GTK_TOP)/include/glib-2.0 \
+			-I$(GTK_TOP)/lib/glib-2.0/include \
+			-I$(PURPLE_TOP) \
+			-I$(PURPLE_TOP)/win32 \
+			-I$(PIDGIN_TREE_TOP)
+
+LIB_PATHS +=		-L$(GTK_TOP)/lib \
+			-L$(PURPLE_TOP)
+
+##
+##  SOURCES, OBJECTS
+##
+C_SRC =			actions.c \
+			aes.c \
+			chunk.c \
+			cipher.c \
+			filexfer.c \
+			formcmds.c \
+			http.c \
+			login.c \
+			markup.c \
+			multimx.c \
+			mxit.c \
+			profile.c \
+			protocol.c \
+			roster.c \
+			splashscreen.c
+
+OBJECTS = $(C_SRC:%.c=%.o)
+
+##
+## LIBRARIES
+##
+LIBS =	\
+			-lglib-2.0 \
+			-lintl \
+			-lws2_32 \
+			-lpurple
+
+include $(PIDGIN_COMMON_RULES)
+
+##
+## TARGET DEFINITIONS
+##
+.PHONY: all install clean
+
+all: $(TARGET).dll
+
+install: all $(DLL_INSTALL_DIR)
+	cp $(TARGET).dll $(DLL_INSTALL_DIR)
+
+$(OBJECTS): $(PURPLE_CONFIG_H)
+
+$(TARGET).dll: $(PURPLE_DLL).a $(OBJECTS)
+	$(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll
+
+##
+## CLEAN RULES
+##
+clean:
+	rm -f $(OBJECTS)
+	rm -f $(TARGET).dll
+
+include $(PIDGIN_COMMON_TARGETS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/actions.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,437 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *					-- handle MXit plugin actions --
+ *
+ *				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	<stdlib.h>
+#include	<string.h>
+#include	<time.h>
+
+#include	"purple.h"
+
+#include	"protocol.h"
+#include	"mxit.h"
+#include	"roster.h"
+#include	"actions.h"
+#include	"splashscreen.h"
+#include	"cipher.h"
+#include	"profile.h"
+
+
+/* MXit Moods */
+static const char*	moods[] = {
+	/* 0 */		"None",
+	/* 1 */		"Angry",
+	/* 2 */		"Excited",
+	/* 3 */		"Grumpy",
+	/* 4 */		"Happy",
+	/* 5 */		"In Love",
+	/* 6 */		"Invincible",
+	/* 7 */		"Sad",
+	/* 8 */		"Hot",
+	/* 9 */		"Sick",
+	/* 10 */	"Sleepy"
+};
+
+
+/*------------------------------------------------------------------------
+ * The user has selected to change their current mood.
+ *
+ *  @param gc		The connection object
+ *  @param fields	The fields from the request pop-up
+ */
+static void mxit_cb_set_mood( PurpleConnection* gc, PurpleRequestFields* fields )
+{
+	struct MXitSession*		session	= (struct MXitSession*) gc->proto_data;
+	int						mood	= purple_request_fields_get_choice( fields, "mood" );
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_set_mood (%i)\n", mood );
+
+	if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Unable to set mood; account offline.\n" );
+		return;
+	}
+
+	/* Save the new mood in session */
+	session->mood = mood;
+
+	/* now send the update to MXit */
+	mxit_send_mood( session, mood );
+}
+
+
+/*------------------------------------------------------------------------
+ * Create and display the mood selection window to the user.
+ *
+ *  @param action	The action object
+ */
+static void mxit_cb_action_mood( PurplePluginAction* action )
+{
+	PurpleConnection*			gc		= (PurpleConnection*) action->context;
+	struct MXitSession*			session	= (struct MXitSession*) gc->proto_data;
+
+	PurpleRequestFields*		fields	= NULL;
+	PurpleRequestFieldGroup*	group	= NULL;
+	PurpleRequestField*			field	= NULL;
+	unsigned int				i		= 0;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_action_mood\n" );
+
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new( NULL );
+	purple_request_fields_add_group( fields, group );
+
+	/* show current mood */
+	field = purple_request_field_string_new( "current", _( "Current Mood" ), _( moods[session->mood] ), FALSE );
+	purple_request_field_string_set_editable( field, FALSE );	/* current mood field is not editable */
+	purple_request_field_group_add_field( group, field );
+
+	/* add all moods to list */
+	field = purple_request_field_choice_new( "mood", _( "New Mood" ), 0 );
+	for ( i = 0; i < ARRAY_SIZE( moods ); i++ ) {
+		purple_request_field_choice_add( field, _( moods[i] ) );
+	}
+	purple_request_field_set_required( field, TRUE );
+	purple_request_field_choice_set_default_value( field, session->mood );
+	purple_request_field_group_add_field( group, field );
+
+	/* (reference: "libpurple/request.h") */
+	purple_request_fields( gc, _( "Mood" ), _( "Change your Mood" ), _( "How do you feel right now?" ), fields, _( "Set" ),
+			G_CALLBACK( mxit_cb_set_mood ), _( "Cancel" ), NULL, purple_connection_get_account( gc ), NULL, NULL, gc );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has selected to change their profile.
+ *
+ *  @param gc		The connection object
+ *  @param fields	The fields from the request pop-up
+ */
+static void mxit_cb_set_profile( PurpleConnection* gc, PurpleRequestFields* fields )
+{
+	struct MXitSession*		session	= (struct MXitSession*) gc->proto_data;
+	PurpleRequestField*		field	= NULL;
+	const char*				pin		= NULL;
+	const char*				pin2	= NULL;
+	const char*				name	= NULL;
+	const char*				bday	= NULL;
+	const char*				err		= NULL;
+	int						len;
+	int						i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_set_profile\n" );
+
+	if ( !PURPLE_CONNECTION_IS_VALID( gc ) ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Unable to update profile; account offline.\n" );
+		return;
+	}
+
+	/* validate 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 < 4 ) || ( len > 10 ) ) {
+		err = "The PIN you entered has an invalid length [4-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;
+		}
+	}
+	pin2 = purple_request_fields_get_string( fields, "pin2" );
+	if ( ( !pin2 ) || ( strcmp( pin, pin2 ) != 0 ) ) {
+		err = "The two PINs you entered does not match.";
+		goto out;
+	}
+
+	/* validate name */
+	name = purple_request_fields_get_string( fields, "name" );
+	if ( ( !name ) || ( strlen( name ) < 3 ) ) {
+		err = "The name you entered is invalid.";
+		goto out;
+	}
+
+	/* validate birthdate */
+	bday = purple_request_fields_get_string( fields, "bday" );
+	if ( ( !bday ) || ( strlen( bday ) < 10 ) || ( !validateDate( bday ) ) ) {
+		err = "The birthday you entered is invalid. The correct format is: 'YYYY-MM-DD'.";
+		goto out;
+	}
+
+out:
+	if ( !err ) {
+		struct MXitProfile*	profile		= session->profile;
+		GString*			attributes	= g_string_sized_new( 128 );
+		char				attrib[512];
+		unsigned int		acount		= 0;
+
+		/* all good, so we can now update the profile */
+
+		/* update pin */
+		purple_account_set_password( session->acc, pin );
+		g_free( session->encpwd );
+		session->encpwd = mxit_encrypt_password( session );
+
+		/* update name */
+		g_strlcpy( profile->nickname, name, sizeof( profile->nickname ) );
+		g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_FULLNAME, CP_PROF_TYPE_UTF8, profile->nickname );
+		g_string_append( attributes, attrib );
+		acount++;
+
+		/* update hidden */
+		field = purple_request_fields_get_field( fields, "hidden" );
+		profile->hidden = purple_request_field_bool_get_value( field );
+		g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_HIDENUMBER, CP_PROF_TYPE_BOOL, ( profile->hidden ) ? "1" : "0" );
+		g_string_append( attributes, attrib );
+		acount++;
+
+		/* update birthday */
+		strcpy( profile->birthday, bday );
+		g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_BIRTHDATE, CP_PROF_TYPE_UTF8, profile->birthday );
+		g_string_append( attributes, attrib );
+		acount++;
+
+		/* update gender */
+		if ( purple_request_fields_get_choice( fields, "male" ) == 0 )
+			profile->male = FALSE;
+		else
+			profile->male = TRUE;
+		g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_GENDER, CP_PROF_TYPE_BOOL, ( profile->male ) ? "1" : "0" );
+		g_string_append( attributes, attrib );
+		acount++;
+
+		/* update title */
+		name = purple_request_fields_get_string( fields, "title" );
+		if ( !name )
+			profile->title[0] = '\0';
+		else
+			strcpy( profile->title, name );
+		g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_TITLE, CP_PROF_TYPE_UTF8, profile->title );
+		g_string_append( attributes, attrib );
+		acount++;
+
+		/* update firstname */
+		name = purple_request_fields_get_string( fields, "firstname" );
+		if ( !name )
+			profile->firstname[0] = '\0';
+		else
+			strcpy( profile->firstname, name );
+		g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_FIRSTNAME, CP_PROF_TYPE_UTF8, profile->firstname );
+		g_string_append( attributes, attrib );
+		acount++;
+
+		/* update lastname */
+		name = purple_request_fields_get_string( fields, "lastname" );
+		if ( !name )
+			profile->lastname[0] = '\0';
+		else
+			strcpy( profile->lastname, name );
+		g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_LASTNAME, CP_PROF_TYPE_UTF8, profile->lastname );
+		g_string_append( attributes, attrib );
+		acount++;
+
+		/* update email address */
+		name = purple_request_fields_get_string( fields, "email" );
+		if ( !name )
+			profile->email[0] = '\0';
+		else
+			strcpy( profile->email, name );
+		g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_EMAIL, CP_PROF_TYPE_UTF8, profile->email );
+		g_string_append( attributes, attrib );
+		acount++;
+
+		/* update mobile number */
+		name = purple_request_fields_get_string( fields, "mobilenumber" );
+		if ( !name )
+			profile->mobilenr[0] = '\0';
+		else
+			strcpy( profile->mobilenr, name );
+		g_snprintf( attrib, sizeof( attrib ), "\01%s\01%i\01%s", CP_PROFILE_MOBILENR, CP_PROF_TYPE_UTF8, profile->mobilenr );
+		g_string_append( attributes, attrib );
+		acount++;
+
+		/* send the profile update to MXit */
+		mxit_send_extprofile_update( session, session->encpwd, acount, attributes->str );
+		g_string_free( attributes, TRUE );
+	}
+	else {
+		/* show error to user */
+		mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Profile Update Error" ), _( err ) );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Display and update the user's profile.
+ *
+ *  @param action	The action object
+ */
+static void mxit_cb_action_profile( PurplePluginAction* action )
+{
+	PurpleConnection*			gc		= (PurpleConnection*) action->context;
+	struct MXitSession*			session	= (struct MXitSession*) gc->proto_data;
+	struct MXitProfile*			profile	= session->profile;
+
+	PurpleRequestFields*		fields	= NULL;
+	PurpleRequestFieldGroup*	group	= NULL;
+	PurpleRequestField*			field	= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_action_profile\n" );
+
+	/* ensure that we actually have the user's profile information */
+	if ( !profile ) {
+		/* no profile information yet, so we cannot update */
+		mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Profile" ), _( "Your profile information is not yet retrieved. Please try again later." ) );
+		return;
+	}
+
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new( NULL );
+	purple_request_fields_add_group( fields, group );
+
+	/* pin */
+	field = purple_request_field_string_new( "pin", _( "PIN" ), session->acc->password, 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" ), session->acc->password, FALSE );
+	purple_request_field_string_set_masked( field, TRUE );
+	purple_request_field_group_add_field( group, field );
+
+	/* display name */
+	field = purple_request_field_string_new( "name", _( "Display 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_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 );
+
+	/* hidden */
+	field = purple_request_field_bool_new( "hidden", _( "Hide my number" ), profile->hidden );
+	purple_request_field_group_add_field( group, field );
+
+	/* title */
+	field = purple_request_field_string_new( "title", _( "Title" ), profile->title, FALSE );
+	purple_request_field_group_add_field( group, field );
+
+	/* first name */
+	field = purple_request_field_string_new( "firstname", _( "First Name" ), profile->firstname, FALSE );
+	purple_request_field_group_add_field( group, field );
+
+	/* last name */
+	field = purple_request_field_string_new( "lastname", _( "Last Name" ), profile->lastname, FALSE );
+	purple_request_field_group_add_field( group, field );
+
+	/* email */
+	field = purple_request_field_string_new( "email", _( "Email" ), profile->email, FALSE );
+	purple_request_field_group_add_field( group, field );
+
+	/* mobile number */
+	field = purple_request_field_string_new( "mobilenumber", _( "Mobile Number" ), profile->mobilenr, FALSE );
+	purple_request_field_group_add_field( group, field );
+
+	/* (reference: "libpurple/request.h") */
+	purple_request_fields( gc, _( "Profile" ), _( "Update your Profile" ), _( "Here you can update your MXit profile" ), fields, _( "Set" ),
+			G_CALLBACK( mxit_cb_set_profile ), _( "Cancel" ), NULL, purple_connection_get_account( gc ), NULL, NULL, gc );
+}
+
+
+/*------------------------------------------------------------------------
+ * Display the current splash-screen, or a notification pop-up if one is not available.
+ *
+ *  @param action	The action object
+ */
+static void mxit_cb_action_splash( PurplePluginAction* action )
+{
+	PurpleConnection*		gc		= (PurpleConnection*) action->context;
+	struct MXitSession*		session	= (struct MXitSession*) gc->proto_data;
+
+	if ( splash_current( session ) != NULL )
+		splash_display( session );
+	else
+		mxit_popup( PURPLE_NOTIFY_MSG_INFO, _( "View Splash" ), _( "There is no splash-screen currently available" ) );
+}
+
+
+/*------------------------------------------------------------------------
+ * Display info about the plugin.
+ *
+ *  @param action	The action object
+ */
+static void mxit_cb_action_about( PurplePluginAction* action )
+{
+	char	version[256];
+
+	g_snprintf( version, sizeof( version ), "MXit libPurple Plugin v%s\n"
+											"MXit Client Protocol v%s\n\n"
+											"Author:\nPieter Loubser\n\n"
+											"Contributors:\nAndrew Victor\n\n"
+											"Testers:\nBraeme Le Roux\n\n",
+											MXIT_PLUGIN_VERSION, MXIT_CP_RELEASE );
+
+	mxit_popup( PURPLE_NOTIFY_MSG_INFO, _( "About" ), version );
+}
+
+
+/*------------------------------------------------------------------------
+ * Associate actions with the MXit plugin.
+ *
+ *  @param plugin	The MXit protocol plugin
+ *  @param context	The connection context (if available)
+ *  @return			The list of plugin actions
+ */
+GList* mxit_actions( PurplePlugin* plugin, gpointer context )
+{
+	PurplePluginAction*		action	= NULL;
+	GList*					m		= NULL;
+
+	/* display / change mood */
+	action = purple_plugin_action_new( _( "Change Mood..." ), mxit_cb_action_mood );
+	m = g_list_append( m, action );
+
+	/* display / change profile */
+	action = purple_plugin_action_new( _( "Change Profile..." ), mxit_cb_action_profile );
+	m = g_list_append( m, action );
+
+	/* display splash-screen */
+	action = purple_plugin_action_new( _( "View Splash..." ), mxit_cb_action_splash );
+	m = g_list_append( m, action );
+
+	/* display plugin version */
+	action = purple_plugin_action_new( _( "About..." ), mxit_cb_action_about );
+	m = g_list_append( m, action );
+
+	return m;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/actions.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,34 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *					-- handle MXit plugin actions --
+ *
+ *				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
+ */
+
+#ifndef		_MXIT_ACTIONS_H_
+#define		_MXIT_ACTIONS_H_
+
+
+/* callbacks */
+GList* mxit_actions( PurplePlugin* plugin, gpointer context );
+
+
+#endif		/* _MXIT_ACTIONS_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/aes.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,405 @@
+
+// advanced encryption standard
+// author: karl malbrain, malbrain@yahoo.com
+
+/*
+This work, including the source code, documentation
+and related data, is placed into the public domain.
+
+The orginal author is Karl Malbrain.
+
+THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY
+OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF
+MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE,
+ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE
+RESULTING FROM THE USE, MODIFICATION, OR
+REDISTRIBUTION OF THIS SOFTWARE.
+*/
+
+#include <string.h>
+#include <memory.h>
+
+#include "aes.h"
+
+// AES only supports Nb=4
+#define Nb 4			// number of columns in the state & expanded key
+
+#define Nk 4			// number of columns in a key
+#define Nr 10			// number of rounds in encryption
+
+static uchar Sbox[256] = {		// forward s-box
+0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
+0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
+0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
+0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
+0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16};
+
+static uchar InvSbox[256] = {	// inverse s-box
+0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
+0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
+0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
+0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
+0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
+0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
+0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
+0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
+0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
+0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
+0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
+0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
+0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
+0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
+0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
+0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d};
+
+// combined Xtimes2[Sbox[]]
+static uchar Xtime2Sbox[256] = {
+0xc6, 0xf8, 0xee, 0xf6, 0xff, 0xd6, 0xde, 0x91, 0x60, 0x02, 0xce, 0x56, 0xe7, 0xb5, 0x4d, 0xec, 
+0x8f, 0x1f, 0x89, 0xfa, 0xef, 0xb2, 0x8e, 0xfb, 0x41, 0xb3, 0x5f, 0x45, 0x23, 0x53, 0xe4, 0x9b, 
+0x75, 0xe1, 0x3d, 0x4c, 0x6c, 0x7e, 0xf5, 0x83, 0x68, 0x51, 0xd1, 0xf9, 0xe2, 0xab, 0x62, 0x2a, 
+0x08, 0x95, 0x46, 0x9d, 0x30, 0x37, 0x0a, 0x2f, 0x0e, 0x24, 0x1b, 0xdf, 0xcd, 0x4e, 0x7f, 0xea, 
+0x12, 0x1d, 0x58, 0x34, 0x36, 0xdc, 0xb4, 0x5b, 0xa4, 0x76, 0xb7, 0x7d, 0x52, 0xdd, 0x5e, 0x13, 
+0xa6, 0xb9, 0x00, 0xc1, 0x40, 0xe3, 0x79, 0xb6, 0xd4, 0x8d, 0x67, 0x72, 0x94, 0x98, 0xb0, 0x85, 
+0xbb, 0xc5, 0x4f, 0xed, 0x86, 0x9a, 0x66, 0x11, 0x8a, 0xe9, 0x04, 0xfe, 0xa0, 0x78, 0x25, 0x4b, 
+0xa2, 0x5d, 0x80, 0x05, 0x3f, 0x21, 0x70, 0xf1, 0x63, 0x77, 0xaf, 0x42, 0x20, 0xe5, 0xfd, 0xbf, 
+0x81, 0x18, 0x26, 0xc3, 0xbe, 0x35, 0x88, 0x2e, 0x93, 0x55, 0xfc, 0x7a, 0xc8, 0xba, 0x32, 0xe6, 
+0xc0, 0x19, 0x9e, 0xa3, 0x44, 0x54, 0x3b, 0x0b, 0x8c, 0xc7, 0x6b, 0x28, 0xa7, 0xbc, 0x16, 0xad, 
+0xdb, 0x64, 0x74, 0x14, 0x92, 0x0c, 0x48, 0xb8, 0x9f, 0xbd, 0x43, 0xc4, 0x39, 0x31, 0xd3, 0xf2, 
+0xd5, 0x8b, 0x6e, 0xda, 0x01, 0xb1, 0x9c, 0x49, 0xd8, 0xac, 0xf3, 0xcf, 0xca, 0xf4, 0x47, 0x10, 
+0x6f, 0xf0, 0x4a, 0x5c, 0x38, 0x57, 0x73, 0x97, 0xcb, 0xa1, 0xe8, 0x3e, 0x96, 0x61, 0x0d, 0x0f, 
+0xe0, 0x7c, 0x71, 0xcc, 0x90, 0x06, 0xf7, 0x1c, 0xc2, 0x6a, 0xae, 0x69, 0x17, 0x99, 0x3a, 0x27, 
+0xd9, 0xeb, 0x2b, 0x22, 0xd2, 0xa9, 0x07, 0x33, 0x2d, 0x3c, 0x15, 0xc9, 0x87, 0xaa, 0x50, 0xa5, 
+0x03, 0x59, 0x09, 0x1a, 0x65, 0xd7, 0x84, 0xd0, 0x82, 0x29, 0x5a, 0x1e, 0x7b, 0xa8, 0x6d, 0x2c 
+};
+
+// combined Xtimes3[Sbox[]]
+static uchar Xtime3Sbox[256] = {
+0xa5, 0x84, 0x99, 0x8d, 0x0d, 0xbd, 0xb1, 0x54, 0x50, 0x03, 0xa9, 0x7d, 0x19, 0x62, 0xe6, 0x9a, 
+0x45, 0x9d, 0x40, 0x87, 0x15, 0xeb, 0xc9, 0x0b, 0xec, 0x67, 0xfd, 0xea, 0xbf, 0xf7, 0x96, 0x5b, 
+0xc2, 0x1c, 0xae, 0x6a, 0x5a, 0x41, 0x02, 0x4f, 0x5c, 0xf4, 0x34, 0x08, 0x93, 0x73, 0x53, 0x3f, 
+0x0c, 0x52, 0x65, 0x5e, 0x28, 0xa1, 0x0f, 0xb5, 0x09, 0x36, 0x9b, 0x3d, 0x26, 0x69, 0xcd, 0x9f, 
+0x1b, 0x9e, 0x74, 0x2e, 0x2d, 0xb2, 0xee, 0xfb, 0xf6, 0x4d, 0x61, 0xce, 0x7b, 0x3e, 0x71, 0x97, 
+0xf5, 0x68, 0x00, 0x2c, 0x60, 0x1f, 0xc8, 0xed, 0xbe, 0x46, 0xd9, 0x4b, 0xde, 0xd4, 0xe8, 0x4a, 
+0x6b, 0x2a, 0xe5, 0x16, 0xc5, 0xd7, 0x55, 0x94, 0xcf, 0x10, 0x06, 0x81, 0xf0, 0x44, 0xba, 0xe3, 
+0xf3, 0xfe, 0xc0, 0x8a, 0xad, 0xbc, 0x48, 0x04, 0xdf, 0xc1, 0x75, 0x63, 0x30, 0x1a, 0x0e, 0x6d, 
+0x4c, 0x14, 0x35, 0x2f, 0xe1, 0xa2, 0xcc, 0x39, 0x57, 0xf2, 0x82, 0x47, 0xac, 0xe7, 0x2b, 0x95, 
+0xa0, 0x98, 0xd1, 0x7f, 0x66, 0x7e, 0xab, 0x83, 0xca, 0x29, 0xd3, 0x3c, 0x79, 0xe2, 0x1d, 0x76, 
+0x3b, 0x56, 0x4e, 0x1e, 0xdb, 0x0a, 0x6c, 0xe4, 0x5d, 0x6e, 0xef, 0xa6, 0xa8, 0xa4, 0x37, 0x8b, 
+0x32, 0x43, 0x59, 0xb7, 0x8c, 0x64, 0xd2, 0xe0, 0xb4, 0xfa, 0x07, 0x25, 0xaf, 0x8e, 0xe9, 0x18, 
+0xd5, 0x88, 0x6f, 0x72, 0x24, 0xf1, 0xc7, 0x51, 0x23, 0x7c, 0x9c, 0x21, 0xdd, 0xdc, 0x86, 0x85, 
+0x90, 0x42, 0xc4, 0xaa, 0xd8, 0x05, 0x01, 0x12, 0xa3, 0x5f, 0xf9, 0xd0, 0x91, 0x58, 0x27, 0xb9, 
+0x38, 0x13, 0xb3, 0x33, 0xbb, 0x70, 0x89, 0xa7, 0xb6, 0x22, 0x92, 0x20, 0x49, 0xff, 0x78, 0x7a, 
+0x8f, 0xf8, 0x80, 0x17, 0xda, 0x31, 0xc6, 0xb8, 0xc3, 0xb0, 0x77, 0x11, 0xcb, 0xfc, 0xd6, 0x3a 
+};
+
+// modular multiplication tables
+// based on:
+
+// Xtime2[x] = (x & 0x80 ? 0x1b : 0) ^ (x + x)
+// Xtime3[x] = x^Xtime2[x];
+
+#if	0
+static uchar Xtime2[256] = {
+0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, 
+0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, 
+0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 
+0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, 
+0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, 
+0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, 
+0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, 
+0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, 
+0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05, 
+0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25, 
+0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, 
+0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65, 
+0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85, 
+0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, 
+0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5, 
+0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5}; 
+#endif
+
+static uchar Xtime9[256] = {
+0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, 
+0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7, 
+0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, 
+0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc, 
+0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01, 
+0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91, 
+0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a, 
+0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa, 
+0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b, 
+0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b, 
+0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0, 
+0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30, 
+0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed, 
+0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d, 
+0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6, 
+0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46};
+
+static uchar XtimeB[256] = {
+0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69, 
+0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9, 
+0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12, 
+0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2, 
+0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f, 
+0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f, 
+0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4, 
+0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54, 
+0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e, 
+0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e, 
+0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5, 
+0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55, 
+0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68, 
+0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8, 
+0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13, 
+0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3}; 
+
+static uchar XtimeD[256] = {
+0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b, 
+0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b, 
+0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0, 
+0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20, 
+0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26, 
+0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6, 
+0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d, 
+0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d, 
+0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91, 
+0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41, 
+0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a, 
+0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa, 
+0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc, 
+0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c, 
+0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47, 
+0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97}; 
+
+static uchar XtimeE[256] = {
+0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a, 
+0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba, 
+0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81, 
+0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61, 
+0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7, 
+0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17, 
+0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c, 
+0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc, 
+0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b, 
+0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb, 
+0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0, 
+0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20, 
+0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6, 
+0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56, 
+0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d, 
+0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d}; 
+
+// exchanges columns in each of 4 rows
+// row0 - unchanged, row1- shifted left 1, 
+// row2 - shifted left 2 and row3 - shifted left 3
+static void ShiftRows (uchar *state)
+{
+uchar tmp;
+
+	// just substitute row 0
+	state[0] = Sbox[state[0]], state[4] = Sbox[state[4]];
+	state[8] = Sbox[state[8]], state[12] = Sbox[state[12]];
+
+	// rotate row 1
+	tmp = Sbox[state[1]], state[1] = Sbox[state[5]];
+	state[5] = Sbox[state[9]], state[9] = Sbox[state[13]], state[13] = tmp;
+
+	// rotate row 2
+	tmp = Sbox[state[2]], state[2] = Sbox[state[10]], state[10] = tmp;
+	tmp = Sbox[state[6]], state[6] = Sbox[state[14]], state[14] = tmp;
+
+	// rotate row 3
+	tmp = Sbox[state[15]], state[15] = Sbox[state[11]];
+	state[11] = Sbox[state[7]], state[7] = Sbox[state[3]], state[3] = tmp;
+}
+
+// restores columns in each of 4 rows
+// row0 - unchanged, row1- shifted right 1, 
+// row2 - shifted right 2 and row3 - shifted right 3
+static void InvShiftRows (uchar *state)
+{
+uchar tmp;
+
+	// restore row 0
+	state[0] = InvSbox[state[0]], state[4] = InvSbox[state[4]];
+	state[8] = InvSbox[state[8]], state[12] = InvSbox[state[12]];
+
+	// restore row 1
+	tmp = InvSbox[state[13]], state[13] = InvSbox[state[9]];
+	state[9] = InvSbox[state[5]], state[5] = InvSbox[state[1]], state[1] = tmp;
+
+	// restore row 2
+	tmp = InvSbox[state[2]], state[2] = InvSbox[state[10]], state[10] = tmp;
+	tmp = InvSbox[state[6]], state[6] = InvSbox[state[14]], state[14] = tmp;
+
+	// restore row 3
+	tmp = InvSbox[state[3]], state[3] = InvSbox[state[7]];
+	state[7] = InvSbox[state[11]], state[11] = InvSbox[state[15]], state[15] = tmp;
+}
+
+// recombine and mix each row in a column
+static void MixSubColumns (uchar *state)
+{
+uchar tmp[4 * Nb];
+
+	// mixing column 0
+	tmp[0] = Xtime2Sbox[state[0]] ^ Xtime3Sbox[state[5]] ^ Sbox[state[10]] ^ Sbox[state[15]];
+	tmp[1] = Sbox[state[0]] ^ Xtime2Sbox[state[5]] ^ Xtime3Sbox[state[10]] ^ Sbox[state[15]];
+	tmp[2] = Sbox[state[0]] ^ Sbox[state[5]] ^ Xtime2Sbox[state[10]] ^ Xtime3Sbox[state[15]];
+	tmp[3] = Xtime3Sbox[state[0]] ^ Sbox[state[5]] ^ Sbox[state[10]] ^ Xtime2Sbox[state[15]];
+
+	// mixing column 1
+	tmp[4] = Xtime2Sbox[state[4]] ^ Xtime3Sbox[state[9]] ^ Sbox[state[14]] ^ Sbox[state[3]];
+	tmp[5] = Sbox[state[4]] ^ Xtime2Sbox[state[9]] ^ Xtime3Sbox[state[14]] ^ Sbox[state[3]];
+	tmp[6] = Sbox[state[4]] ^ Sbox[state[9]] ^ Xtime2Sbox[state[14]] ^ Xtime3Sbox[state[3]];
+	tmp[7] = Xtime3Sbox[state[4]] ^ Sbox[state[9]] ^ Sbox[state[14]] ^ Xtime2Sbox[state[3]];
+
+	// mixing column 2
+	tmp[8] = Xtime2Sbox[state[8]] ^ Xtime3Sbox[state[13]] ^ Sbox[state[2]] ^ Sbox[state[7]];
+	tmp[9] = Sbox[state[8]] ^ Xtime2Sbox[state[13]] ^ Xtime3Sbox[state[2]] ^ Sbox[state[7]];
+	tmp[10]  = Sbox[state[8]] ^ Sbox[state[13]] ^ Xtime2Sbox[state[2]] ^ Xtime3Sbox[state[7]];
+	tmp[11]  = Xtime3Sbox[state[8]] ^ Sbox[state[13]] ^ Sbox[state[2]] ^ Xtime2Sbox[state[7]];
+
+	// mixing column 3
+	tmp[12] = Xtime2Sbox[state[12]] ^ Xtime3Sbox[state[1]] ^ Sbox[state[6]] ^ Sbox[state[11]];
+	tmp[13] = Sbox[state[12]] ^ Xtime2Sbox[state[1]] ^ Xtime3Sbox[state[6]] ^ Sbox[state[11]];
+	tmp[14] = Sbox[state[12]] ^ Sbox[state[1]] ^ Xtime2Sbox[state[6]] ^ Xtime3Sbox[state[11]];
+	tmp[15] = Xtime3Sbox[state[12]] ^ Sbox[state[1]] ^ Sbox[state[6]] ^ Xtime2Sbox[state[11]];
+
+	memcpy (state, tmp, sizeof(tmp));
+}
+
+// restore and un-mix each row in a column
+static void InvMixSubColumns (uchar *state)
+{
+uchar tmp[4 * Nb];
+int i;
+
+	// restore column 0
+	tmp[0] = XtimeE[state[0]] ^ XtimeB[state[1]] ^ XtimeD[state[2]] ^ Xtime9[state[3]];
+	tmp[5] = Xtime9[state[0]] ^ XtimeE[state[1]] ^ XtimeB[state[2]] ^ XtimeD[state[3]];
+	tmp[10] = XtimeD[state[0]] ^ Xtime9[state[1]] ^ XtimeE[state[2]] ^ XtimeB[state[3]];
+	tmp[15] = XtimeB[state[0]] ^ XtimeD[state[1]] ^ Xtime9[state[2]] ^ XtimeE[state[3]];
+
+	// restore column 1
+	tmp[4] = XtimeE[state[4]] ^ XtimeB[state[5]] ^ XtimeD[state[6]] ^ Xtime9[state[7]];
+	tmp[9] = Xtime9[state[4]] ^ XtimeE[state[5]] ^ XtimeB[state[6]] ^ XtimeD[state[7]];
+	tmp[14] = XtimeD[state[4]] ^ Xtime9[state[5]] ^ XtimeE[state[6]] ^ XtimeB[state[7]];
+	tmp[3] = XtimeB[state[4]] ^ XtimeD[state[5]] ^ Xtime9[state[6]] ^ XtimeE[state[7]];
+
+	// restore column 2
+	tmp[8] = XtimeE[state[8]] ^ XtimeB[state[9]] ^ XtimeD[state[10]] ^ Xtime9[state[11]];
+	tmp[13] = Xtime9[state[8]] ^ XtimeE[state[9]] ^ XtimeB[state[10]] ^ XtimeD[state[11]];
+	tmp[2]  = XtimeD[state[8]] ^ Xtime9[state[9]] ^ XtimeE[state[10]] ^ XtimeB[state[11]];
+	tmp[7]  = XtimeB[state[8]] ^ XtimeD[state[9]] ^ Xtime9[state[10]] ^ XtimeE[state[11]];
+
+	// restore column 3
+	tmp[12] = XtimeE[state[12]] ^ XtimeB[state[13]] ^ XtimeD[state[14]] ^ Xtime9[state[15]];
+	tmp[1] = Xtime9[state[12]] ^ XtimeE[state[13]] ^ XtimeB[state[14]] ^ XtimeD[state[15]];
+	tmp[6] = XtimeD[state[12]] ^ Xtime9[state[13]] ^ XtimeE[state[14]] ^ XtimeB[state[15]];
+	tmp[11] = XtimeB[state[12]] ^ XtimeD[state[13]] ^ Xtime9[state[14]] ^ XtimeE[state[15]];
+
+	for( i=0; i < 4 * Nb; i++ )
+		state[i] = InvSbox[tmp[i]];
+}
+
+// encrypt/decrypt columns of the key
+// n.b. you can replace this with
+//      byte-wise xor if you wish.
+
+static void AddRoundKey (unsigned *state, unsigned *key)
+{
+int idx;
+
+	for( idx = 0; idx < 4; idx++ )
+		state[idx] ^= key[idx];
+}
+
+static uchar Rcon[11] = {
+0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
+
+// produce Nb bytes for each round
+void ExpandKey (uchar *key, uchar *expkey)
+{
+uchar tmp0, tmp1, tmp2, tmp3, tmp4;
+unsigned idx;
+
+	memcpy (expkey, key, Nk * 4);
+
+	for( idx = Nk; idx < Nb * (Nr + 1); idx++ ) {
+		tmp0 = expkey[4*idx - 4];
+		tmp1 = expkey[4*idx - 3];
+		tmp2 = expkey[4*idx - 2];
+		tmp3 = expkey[4*idx - 1];
+		if( !(idx % Nk) ) {
+			tmp4 = tmp3;
+			tmp3 = Sbox[tmp0];
+			tmp0 = Sbox[tmp1] ^ Rcon[idx/Nk];
+			tmp1 = Sbox[tmp2];
+			tmp2 = Sbox[tmp4];
+		} else if( Nk > 6 && idx % Nk == 4 ) {
+			tmp0 = Sbox[tmp0];
+			tmp1 = Sbox[tmp1];
+			tmp2 = Sbox[tmp2];
+			tmp3 = Sbox[tmp3];
+		}
+
+		expkey[4*idx+0] = expkey[4*idx - 4*Nk + 0] ^ tmp0;
+		expkey[4*idx+1] = expkey[4*idx - 4*Nk + 1] ^ tmp1;
+		expkey[4*idx+2] = expkey[4*idx - 4*Nk + 2] ^ tmp2;
+		expkey[4*idx+3] = expkey[4*idx - 4*Nk + 3] ^ tmp3;
+	}
+}
+
+// encrypt one 128 bit block
+void Encrypt (uchar *in, uchar *expkey, uchar *out)
+{
+uchar state[Nb * 4];
+unsigned round;
+
+	memcpy (state, in, Nb * 4);
+	AddRoundKey ((unsigned *)state, (unsigned *)expkey);
+
+	for( round = 1; round < Nr + 1; round++ ) {
+		if( round < Nr )
+			MixSubColumns (state);
+		else
+			ShiftRows (state);
+
+		AddRoundKey ((unsigned *)state, (unsigned *)expkey + round * Nb);
+	}
+
+	memcpy (out, state, sizeof(state));
+}
+
+void Decrypt (uchar *in, uchar *expkey, uchar *out)
+{
+uchar state[Nb * 4];
+unsigned round;
+
+	memcpy (state, in, sizeof(state));
+
+	AddRoundKey ((unsigned *)state, (unsigned *)expkey + Nr * Nb);
+	InvShiftRows(state);
+
+	for( round = Nr; round--; )
+	{
+		AddRoundKey ((unsigned *)state, (unsigned *)expkey + round * Nb);
+		if( round )
+			InvMixSubColumns (state);
+	} 
+
+	memcpy (out, state, sizeof(state));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/aes.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,39 @@
+// advanced encryption standard
+// author: karl malbrain, malbrain@yahoo.com
+
+/*
+This work, including the source code, documentation
+and related data, is placed into the public domain.
+
+The orginal author is Karl Malbrain.
+
+THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY
+OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF
+MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE,
+ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE
+RESULTING FROM THE USE, MODIFICATION, OR
+REDISTRIBUTION OF THIS SOFTWARE.
+*/
+
+
+#ifndef		AES_MALBRAIN
+#define		AES_MALBRAIN
+
+
+// AES only supports Nb=4
+#define Nb 4			// number of columns in the state & expanded key
+
+#define Nk 4			// number of columns in a key
+#define Nr 10			// number of rounds in encryption
+
+
+typedef unsigned char uchar;
+
+
+void ExpandKey (uchar *key, uchar *expkey);
+void Encrypt (uchar *in, uchar *expkey, uchar *out);
+void Decrypt (uchar *in, uchar *expkey, uchar *out);
+
+
+#endif		/* AES_MALBRAIN */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/chunk.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,659 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- handle chunked data (multimedia messages) --
+ *
+ *				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	<unistd.h>
+#include	<string.h>
+
+#include	"purple.h"
+#include	"protocol.h"
+#include	"mxit.h"
+#include	"chunk.h"
+#include	"filexfer.h"
+
+
+/*========================================================================================================================
+ * Data-Type encoding
+ */
+
+#if	0
+#include	<byteswap.h>
+#if (__BYTE_ORDER == __BIG_ENDIAN)
+#define SWAP_64(x)  (x)
+#else
+#define SWAP_64(x)  bswap_64(x)
+#endif
+#endif
+
+/*------------------------------------------------------------------------
+ * Encode a single byte in the chunked data.
+ *
+ *  @param chunkdata		The chunked-data buffer
+ *  @param value			The byte
+ *  @return					The number of bytes added.
+ */
+static int add_int8( char* chunkdata, char value )
+{
+	*chunkdata = value;
+
+	return sizeof( char );
+}
+
+/*------------------------------------------------------------------------
+ * Encode a 16-bit value in the chunked data.
+ *
+ *  @param chunkdata		The chunked-data buffer
+ *  @param value			The 16-bit value
+ *  @return					The number of bytes added.
+ */
+static int add_int16( char* chunkdata, short value )
+{
+	value = htons( value );		/* network byte-order */
+	memcpy( chunkdata, &value, sizeof( short ) );
+
+	return sizeof( short );
+}
+
+/*------------------------------------------------------------------------
+ * Encode a 32-bit value in the chunked data.
+ *
+ *  @param chunkdata		The chunked-data buffer
+ *  @param value			The 32-bit value
+ *  @return					The number of bytes added.
+ */
+static int add_int32( char* chunkdata, int value )
+{
+	value = htonl( value );		/* network byte-order */
+	memcpy( chunkdata, &value, sizeof( int ) );
+
+	return sizeof( int );
+}
+
+#if	0
+/*------------------------------------------------------------------------
+ * Encode a 64-bit value in the chunked data.
+ *
+ *  @param chunkdata		The chunked-data buffer
+ *  @param value			The 64-bit value
+ *  @return					The number of bytes added.
+ */
+static int add_int64( char* chunkdata, int64_t value )
+{
+	value = SWAP_64( value );	/* network byte-order */
+	memcpy( chunkdata, &value, sizeof( int64_t ) );
+
+	return sizeof( int64_t );
+}
+#endif
+
+/*------------------------------------------------------------------------
+ * Encode a block of data in the chunked data.
+ *
+ *  @param chunkdata		The chunked-data buffer
+ *  @param data				The data to add
+ *  @param datalen			The length of the data to add
+ *  @return					The number of bytes added.
+ */
+static int add_data( char* chunkdata, const char* data, int datalen )
+{
+	memcpy( chunkdata, data, datalen );
+
+	return datalen;
+}
+
+/*------------------------------------------------------------------------
+ * Encode a string as UTF-8 in the chunked data.
+ *
+ *  @param chunkdata		The chunked-data buffer
+ *  @param str				The string to encode
+ *  @return					The number of bytes in the string
+ */
+static int add_utf8_string( char* chunkdata, const char* str )
+{
+	int		pos		= 0;
+	size_t	len		= strlen( str );
+
+	/* utf8 string length [2 bytes] */
+	pos += add_int16( &chunkdata[pos], len );
+
+	/* utf8 string */
+	pos += add_data( &chunkdata[pos], str, len );
+
+	return pos;
+}
+
+
+/*========================================================================================================================
+ * Data-Type decoding
+ */
+
+/*------------------------------------------------------------------------
+ * Extract a single byte from the chunked data.
+ *
+ *  @param chunkdata		The chunked-data buffer
+ *  @param value			The byte
+ *  @return					The number of bytes extracted.
+ */
+static int get_int8( const char* chunkdata, char* value )
+{
+	*value = *chunkdata;
+
+	return sizeof( char );
+}
+
+/*------------------------------------------------------------------------
+ * Extract a 16-bit value from the chunked data.
+ *
+ *  @param chunkdata		The chunked-data buffer
+ *  @param value			The 16-bit value
+ *  @return					The number of bytes extracted
+ */
+static int get_int16( const char* chunkdata, short* value )
+{
+	*value = ntohs( *( (const short*) chunkdata ) );	/* host byte-order */
+
+	return sizeof( short );
+}
+
+/*------------------------------------------------------------------------
+ * Extract a 32-bit value from the chunked data.
+ *
+ *  @param chunkdata		The chunked-data buffer
+ *  @param value			The 32-bit value
+ *  @return					The number of bytes extracted
+ */
+static int get_int32( const char* chunkdata, int* value )
+{
+	*value = ntohl( *( (const int*) chunkdata ) );	/* host byte-order */
+
+	return sizeof( int );
+}
+
+#if	0
+/*------------------------------------------------------------------------
+ * Extract a 64-bit value from the chunked data.
+ *
+ *  @param chunkdata		The chunked-data buffer
+ *  @param value			The 64-bit value
+ *  @return					The number of bytes extracted
+ */
+static int get_int64( const char* chunkdata, int64_t* value )
+{
+	*value = SWAP_64( *( (const int64_t*) chunkdata ) );	/* host byte-order */
+
+	return sizeof( int64_t );
+}
+#endif
+
+/*------------------------------------------------------------------------
+ * Copy a block of data from the chunked data.
+ *
+ *  @param chunkdata		The chunked-data buffer
+ *  @param dest				Where to store the extract data
+ *  @param datalen			The length of the data to extract
+ *  @return					The number of bytes extracted
+ */
+static int get_data( const char* chunkdata, char* dest, int datalen )
+{
+	memcpy( dest, chunkdata, datalen );
+
+	return datalen;
+}
+
+/*------------------------------------------------------------------------
+ * Extract a UTF-8 encoded string from the chunked data.
+ *
+ *  @param chunkdata		The chunked-data buffer
+ *  @param str				A pointer to extracted string.  Must be g_free()'d.
+ *  @return					The number of bytes consumed
+ */
+static int get_utf8_string( const char* chunkdata, char* str, int maxstrlen )
+{
+	int		pos = 0;
+	short	len;
+	int		skip = 0;
+
+	/* string length [2 bytes] */
+	pos += get_int16( &chunkdata[pos], &len );
+
+	if ( len > maxstrlen ) {
+		/* possible buffer overflow */
+		purple_debug_error( MXIT_PLUGIN_ID, "Buffer overflow detected (get_utf8_string)\n" );
+		skip = len - maxstrlen;
+		len = maxstrlen;
+	}
+
+	/* string data */
+	pos += get_data( &chunkdata[pos], str, len );
+	str[len] = '\0';		/* terminate string */
+
+	return pos + skip;
+}
+
+
+/*========================================================================================================================
+ * Chunked Data encoding
+ */
+
+/*------------------------------------------------------------------------
+ * Encode a "reject file" chunk.  (Chunk type 7)
+ *
+ *  @param chunkdata		Chunked-data buffer
+ *  @param fileid			A unique ID that identifies this file
+ *  @return					The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_reject( char* chunkdata, const char* fileid )
+{
+	int		pos		= 0;
+
+	/* file id [8 bytes] */
+	pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN );
+
+	/* rejection reason [1 byte] */
+	pos += add_int8( &chunkdata[pos], REJECT_BY_USER );
+
+	/* rejection description [UTF-8 (optional)] */
+	pos += add_utf8_string( &chunkdata[pos], "" );
+
+	return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "get file" request chunk.  (Chunk type 8)
+ *
+ *  @param chunkdata		Chunked-data buffer
+ *  @param fileid			A unique ID that identifies this file
+ *  @param filesize			The number of bytes to retrieve
+ *  @param offset			The start offset in the file
+ *  @return					The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_get( char* chunkdata, const char* fileid, int filesize, int offset )
+{
+	int		pos		= 0;
+
+	/* file id [8 bytes] */
+	pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN );
+
+	/* offset [4 bytes] */
+	pos += add_int32( &chunkdata[pos], offset );
+
+	/* length [4 bytes] */
+	pos += add_int32( &chunkdata[pos], filesize );
+
+	return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "received file" chunk.  (Chunk type 9)
+ *
+ *  @param chunkdata		Chunked-data buffer
+ *  @param fileid			A unique ID that identifies this file
+ *  @param status			The status of the file transfer (see chunk.h)
+ *  @return					The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_received( char* chunkdata, const char* fileid, unsigned char status )
+{
+	int		pos		= 0;
+
+	/* file id [8 bytes] */
+	pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN );
+
+	/* status [1 byte] */
+	pos += add_int8( &chunkdata[pos], status );
+
+	return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "send file direct" chunk.  (Chunk type 10)
+ *
+ *  @param chunkdata		Chunked-data buffer
+ *  @param username			The username of the recipient
+ *  @param filename			The name of the file being sent
+ *  @param data				The file contents
+ *  @param datalen			The size of the file contents
+ *  @return					The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_senddirect( char* chunkdata, const char* username, const char* filename, const unsigned char* data, int datalen )
+{
+	int			pos		= 0;
+	const char*	mime	= NULL;
+
+	/* data length [4 bytes] */
+	pos += add_int32( &chunkdata[pos], datalen );
+
+	/* number of username(s) [2 bytes] */
+	pos += add_int16( &chunkdata[pos], 1 );
+
+	/* username(s) [UTF-8] */
+	pos += add_utf8_string( &chunkdata[pos], username );
+
+	/* filename [UTF-8] */
+	pos += add_utf8_string( &chunkdata[pos], filename );
+
+	/* file mime type [UTF-8] */
+	mime = file_mime_type( filename, (const char*) data, datalen );
+	pos += add_utf8_string( &chunkdata[pos], mime );
+
+	/* human readable description [UTF-8 (optional)] */
+	pos += add_utf8_string( &chunkdata[pos], "" );
+
+	/* crc [4 bytes] (0 = optional) */
+	pos += add_int32( &chunkdata[pos], 0 );
+
+	/* the actual file data */
+	pos += add_data( &chunkdata[pos], (const char *) data, datalen );
+
+	return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "set avatar" chunk.  (Chunk type 13)
+ *
+ *  @param chunkdata		Chunked-data buffer
+ *  @param data				The avatar data
+ *  @param datalen			The size of the avatar data
+ *  @return					The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_set_avatar( char* chunkdata, const unsigned char* data, int datalen )
+{
+	const char	fileid[MXIT_CHUNK_FILEID_LEN];
+	int			pos = 0;
+
+	/* id [8 bytes] */
+	memset( &fileid, 0, sizeof( fileid ) );		/* set to 0 for file upload */
+	pos += add_data( &chunkdata[pos], fileid, MXIT_CHUNK_FILEID_LEN );
+
+	/* size [4 bytes] */
+	pos += add_int32( &chunkdata[pos], datalen );
+
+	/* crc [4 bytes] (0 = optional) */
+	pos += add_int32( &chunkdata[pos], 0 );
+
+	/* the actual file data */
+	pos += add_data( &chunkdata[pos], (const char *) data, datalen );
+
+	return pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encode a "get avatar" chunk.  (Chunk type 14)
+ *
+ *  @param chunkdata		Chunked-data buffer
+ *  @param mxitId			The username who's avatar to download
+ *  @param avatarId			The Id of the avatar image (as string)
+ *  @param imgsize			The resolution of the avatar image
+ *  @return					The number of bytes encoded in the buffer
+ */
+int mxit_chunk_create_get_avatar( char* chunkdata, const char* mxitId, const char* avatarId, unsigned int imgsize )
+{
+	int			pos = 0;
+
+	/* number of avatars [4 bytes] */
+	pos += add_int32( &chunkdata[pos], 1 );
+
+	/* username [UTF-8] */
+	pos += add_utf8_string( &chunkdata[pos], mxitId );
+
+	/* avatar id [UTF-8] */
+	pos += add_utf8_string( &chunkdata[pos], avatarId );
+
+	/* avatar format [UTF-8] */
+	pos += add_utf8_string( &chunkdata[pos], MXIT_AVATAR_TYPE );
+
+	/* avatar bit depth [1 byte] */
+	pos += add_int8( &chunkdata[pos], MXIT_AVATAR_BITDEPT );
+
+	/* number of sizes [2 bytes] */
+	pos += add_int16( &chunkdata[pos], 1 );
+
+	/* image size [4 bytes] */
+	pos += add_int32( &chunkdata[pos], imgsize );
+
+	return pos;
+}
+
+
+/*========================================================================================================================
+ * Chunked Data decoding
+ */
+
+/*------------------------------------------------------------------------
+ * Parse a received "offer file" chunk.  (Chunk 6)
+ *
+ *  @param chunkdata		Chunked data buffer
+ *  @param datalen			The length of the chunked data
+ *  @param offer			Decoded offerfile information
+ */
+void mxit_chunk_parse_offer( char* chunkdata, int datalen, struct offerfile_chunk* offer )
+{
+	int			pos			= 0;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_offer (%i bytes)\n", datalen );
+
+	/* id [8 bytes] */
+	pos += get_data( &chunkdata[pos], offer->fileid, 8);
+
+	/* from username [UTF-8] */
+	pos += get_utf8_string( &chunkdata[pos], offer->username, sizeof( offer->username ) );
+	mxit_strip_domain( offer->username );
+
+	/* file size [4 bytes] */
+	pos += get_int32( &chunkdata[pos], &(offer->filesize) );
+
+	/* filename [UTF-8] */
+	pos += get_utf8_string( &chunkdata[pos], offer->filename, sizeof( offer->filename) );
+
+	/* mime type [UTF-8] */
+	/* not used by libPurple */
+
+	/* timestamp [8 bytes] */
+	/* not used by libPurple */
+
+	/* file description [UTF-8] */
+	/* not used by libPurple */
+
+	/* file alternative [UTF-8] */
+	/* not used by libPurple */
+
+	/* flags [4 bytes] */
+	/* not used by libPurple */
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a received "get file" response chunk.  (Chunk 8)
+ *
+ *  @param chunkdata		Chunked data buffer
+ *  @param datalen			The length of the chunked data
+ *  @param offer			Decoded getfile information
+ */
+void mxit_chunk_parse_get( char* chunkdata, int datalen, struct getfile_chunk* getfile )
+{
+	int			pos			= 0;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_file (%i bytes)\n", datalen );
+
+	/* id [8 bytes] */
+	pos += get_data( &chunkdata[pos], getfile->fileid, 8 );
+
+	/* offset [4 bytes] */
+	pos += get_int32( &chunkdata[pos], &(getfile->offset) );
+
+	/* file length [4 bytes] */
+	pos += get_int32( &chunkdata[pos], &(getfile->length) );
+
+	/* crc [4 bytes] */
+	pos += get_int32( &chunkdata[pos], &(getfile->crc) );
+
+	/* file data */
+	getfile->data = &chunkdata[pos];
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a received splash screen chunk.  (Chunk 2)
+ *
+ *  @param chunkdata		Chunked data buffer
+ *  @param datalen			The length of the chunked data
+ *  @param splash			Decoded splash image information
+ */
+static void mxit_chunk_parse_splash( char* chunkdata, int datalen, struct splash_chunk* splash )
+{
+	int			pos			= 0;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_splash (%i bytes)\n", datalen );
+
+	/* anchor [1 byte] */
+	pos += get_int8( &chunkdata[pos], &(splash->anchor) );
+
+	/* time to show [1 byte] */
+	pos += get_int8( &chunkdata[pos], &(splash->showtime) );
+
+	/* background color [4 bytes] */
+	pos += get_int32( &chunkdata[pos], &(splash->bgcolor) );
+
+	/* file data */
+	splash->data = &chunkdata[pos];
+
+	/* data length */
+	splash->datalen = datalen - pos;
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a received "custom resource" chunk.  (Chunk 1)
+ *
+ *  @param chunkdata		Chunked data buffer
+ *  @param datalen			The length of the chunked data
+ *  @param offer			Decoded custom resource
+ */
+void mxit_chunk_parse_cr( char* chunkdata, int datalen, struct cr_chunk* cr )
+{
+	int			pos			= 0;
+	int			chunklen	= 0;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_cr (%i bytes)\n", datalen );
+
+	/* id [UTF-8] */
+	pos += get_utf8_string( &chunkdata[pos], cr->id, sizeof( cr->id ) );
+
+	/* handle [UTF-8] */
+	pos += get_utf8_string( &chunkdata[pos], cr->handle, sizeof( cr->handle ) );
+
+	/* operation [1 byte] */
+	pos += get_int8( &chunkdata[pos], &(cr->operation) );
+
+	/* chunk size [4 bytes] */
+	pos += get_int32( &chunkdata[pos], &chunklen );
+
+	/* parse the resource chunks */
+	while ( chunklen > 0 ) {
+		struct raw_chunk* chunkhdr = ( struct raw_chunk * ) &chunkdata[pos];
+		chunkhdr->length = ntohl( chunkhdr->length );		/* host byte-order */
+
+		/* start of chunk data */
+		pos += sizeof( struct raw_chunk );
+
+		switch ( chunkhdr->type ) {
+			case CP_CHUNK_SPLASH :			/* splash image */
+				{
+					struct splash_chunk* splash = g_new0( struct splash_chunk, 1 );
+
+					mxit_chunk_parse_splash( &chunkdata[pos], chunkhdr->length, splash );
+
+					cr->resources = g_list_append( cr->resources, splash );
+					break;
+				}
+			case CP_CHUNK_CLICK :			/* splash click */
+				{
+					struct splash_click_chunk* click = g_new0( struct splash_click_chunk, 1 );
+					
+					cr->resources = g_list_append( cr->resources, click );
+					break;
+				}
+			default:
+				purple_debug_info( MXIT_PLUGIN_ID, "Unsupported custom resource chunk received (%i)\n", chunkhdr->type );
+		}
+
+		/* skip over data to next resource chunk */
+		pos += chunkhdr->length;
+		chunklen -= ( sizeof( struct raw_chunk ) + chunkhdr->length );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a received "get avatar" response chunk.  (Chunk 14)
+ *
+ *  @param chunkdata		Chunked data buffer
+ *  @param datalen			The length of the chunked data
+ *  @param avatar			Decoded avatar information
+ */
+void mxit_chunk_parse_get_avatar( char* chunkdata, int datalen, struct getavatar_chunk* avatar )
+{
+	int			pos			= 0;
+	int			numfiles	= 0;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_chunk_parse_get_avatar (%i bytes)\n", datalen );
+
+	/* number of files [4 bytes] */
+	pos += get_int32( &chunkdata[pos], &numfiles );
+
+	if ( numfiles < 1 )		/* no data */
+		return;
+
+	/* mxitId [UTF-8 string] */
+	pos += get_utf8_string( &chunkdata[pos], avatar->mxitid, sizeof( avatar->mxitid ) );
+
+	/* avatar id [UTF-8 string] */
+	pos += get_utf8_string( &chunkdata[pos], avatar->avatarid, sizeof( avatar->avatarid ) );
+
+	/* format [UTF-8 string] */
+	pos += get_utf8_string( &chunkdata[pos], avatar->format, sizeof( avatar->format ) );
+
+	/* bit depth [1 byte] */
+	pos += get_int8( &chunkdata[pos], &(avatar->bitdepth) );
+
+	/* crc [4 bytes] */
+	pos += get_int32( &chunkdata[pos], &(avatar->crc) );
+
+	/* width [4 bytes] */
+	pos += get_int32( &chunkdata[pos], &(avatar->width) );
+
+	/* height [4 bytes] */
+	pos += get_int32( &chunkdata[pos], &(avatar->height) );
+
+	/* file length [4 bytes] */
+	pos += get_int32( &chunkdata[pos], &(avatar->length) );
+
+	/* file data */
+	avatar->data = &chunkdata[pos];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/chunk.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,140 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- handle chunked data (multimedia messages) --
+ *
+ *				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
+ */
+
+#ifndef		_MXIT_CHUNK_H_
+#define		_MXIT_CHUNK_H_
+
+
+#include	"roster.h"
+
+
+#define		MXIT_CHUNK_FILEID_LEN		8			/* bytes */
+
+/* Multimedia chunk types */
+#define		CP_CHUNK_NONE				0x00		/* (0) no chunk */
+#define		CP_CHUNK_CUSTOM				0x01		/* (1) custom resource */
+#define		CP_CHUNK_SPLASH				0x02		/* (2) splash image */
+#define		CP_CHUNK_CLICK				0x03		/* (3) splash click through */
+#define		CP_CHUNK_OFFER				0x06		/* (6) offer file */
+#define		CP_CHUNK_REJECT				0x07		/* (7) reject file */
+#define		CP_CHUNK_GET				0x08		/* (8) get file */
+#define		CP_CHUNK_RECIEVED			0x09		/* (9) received file */
+#define		CP_CHUNK_DIRECT_SND			0x0A		/* (10) send file direct */
+#define		CP_CHUNK_DIRECT_FWD			0x0B		/* (11) forward file direct */
+#define		CP_CHUNK_SKIN				0x0C		/* (12) MXit client skin */
+#define		CP_CHUNK_SET_AVATAR			0x0D		/* (13) set avatar */
+#define		CP_CHUNK_GET_AVATAR			0x0E		/* (14) get avatar */
+#define		CP_CHUNK_END				0x7E		/* (126) end */
+#define		CP_CHUNK_EXT				0x7F		/* (127) extended type */
+
+
+/* Custom Resource operations */
+#define		CR_OP_UPDATE				0
+#define		CR_OP_REMOVE				1
+
+/* File Received status */
+#define		RECV_STATUS_SUCCESS			0
+#define		RECV_STATUS_PARSE_FAIL		1
+#define		RECV_STATUS_CANNOT_OPEN		8
+#define		RECV_STATUS_BAD_CRC			9
+#define		RECV_STATUS_BAD_ID			10
+
+/* File Reject status */
+#define		REJECT_BY_USER				1
+#define		REJECT_FILETYPE				2
+#define		REJECT_NO_RESOURCES			3
+#define		REJECT_BAD_RECIPIENT		4
+
+/*
+ * a Chunk header
+ */
+struct raw_chunk {
+	guint8		type;
+	guint32		length;
+	gchar		data[0];
+} __attribute__ ((packed));
+
+struct offerfile_chunk {
+	char	fileid[MXIT_CHUNK_FILEID_LEN];
+	char	username[MXIT_CP_MAX_JID_LEN + 1];
+	int		filesize;
+	char	filename[FILENAME_MAX];
+};
+
+struct getfile_chunk {
+	char	fileid[MXIT_CHUNK_FILEID_LEN];
+	int		offset;
+	int		length;
+	int		crc;
+	char*	data;
+};
+
+struct cr_chunk {
+	char	id[64];
+	char	handle[64];
+	char	operation;
+	GList*	resources;
+};
+
+struct splash_chunk {
+	char	anchor;
+	char	showtime;
+	int		bgcolor;
+	char*	data;
+	int		datalen;
+};
+
+struct splash_click_chunk {
+	char	reserved[1];
+};
+
+struct getavatar_chunk {
+	char	mxitid[50];
+	char	avatarid[64];
+	char	format[16];
+	char	bitdepth;
+	int		crc;
+	int		width;
+	int		height;
+	int		length;
+	char*	data;
+};
+
+/* Encode chunk */
+int mxit_chunk_create_senddirect( char* chunkdata, const char* username, const char* filename, const unsigned char* data, int datalen );
+int mxit_chunk_create_reject( char* chunkdata, const char* fileid );
+int mxit_chunk_create_get( char* chunkdata, const char* fileid, int filesize, int offset );
+int mxit_chunk_create_received( char* chunkdata, const char* fileid, unsigned char status );
+int mxit_chunk_create_set_avatar( char* chunkdata, const unsigned char* data, int datalen );
+int mxit_chunk_create_get_avatar( char* chunkdata, const char* mxitId, const char* avatarId, unsigned int imgsize );
+
+/* Decode chunk */
+void mxit_chunk_parse_offer( char* chunkdata, int datalen, struct offerfile_chunk* offer );
+void mxit_chunk_parse_get( char* chunkdata, int datalen, struct getfile_chunk* getfile );
+void mxit_chunk_parse_cr( char* chunkdata, int datalen, struct cr_chunk* cr );
+void mxit_chunk_parse_get_avatar( char* chunkdata, int datalen, struct getavatar_chunk* avatar );
+
+#endif		/* _MXIT_CHUNK_H_ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/cipher.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,111 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *					-- user password encryption --
+ *
+ *				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	<unistd.h>
+#include	<string.h>
+
+#include	"purple.h"
+
+#include	"mxit.h"
+#include	"cipher.h"
+#include	"aes.h"
+
+
+/* password encryption */
+#define		INITIAL_KEY		"6170383452343567"
+#define		SECRET_HEADER	"<mxit/>"
+
+
+/*------------------------------------------------------------------------
+ * Pad the secret data using ISO10126 Padding.
+ *
+ *  @param secret	The data to pad (caller must ensure buffer has enough space for padding)
+ *  @return			The total number of 128-bit blocks used
+ */
+static int pad_secret_data( char* secret )
+{
+	int		blocks	= 0;
+	int		passlen;
+	int		padding;
+
+	passlen = strlen( secret );
+	blocks = ( passlen / 16 ) + 1;
+	padding = ( blocks * 16 ) - passlen;
+	secret[passlen] = 0x50;
+	secret[(blocks * 16) - 1] = padding;
+
+	return blocks;
+}
+
+
+/*------------------------------------------------------------------------
+ * Encrypt the user's cleartext password using the AES 128-bit (ECB)
+ *  encryption algorithm.
+ *
+ *  @param session	The MXit session object
+ *  @return			The encrypted & encoded password.  Must be g_free'd when no longer needed.
+ */
+char* mxit_encrypt_password( struct MXitSession* session )
+{
+	char		key[64];
+	char		exkey[512];
+	char		pass[64];
+	char		encrypted[64];
+	char*		base64;
+	int			blocks;
+	int			size;
+	int			i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_encrypt_password\n" );
+
+	memset( encrypted, 0x00, sizeof( encrypted ) );
+	memset( exkey, 0x00, sizeof( exkey ) );
+	memset( pass, 0x58, sizeof( pass ) );
+	pass[sizeof( pass ) - 1] = '\0';
+
+	/* build the custom AES encryption key */
+	strcpy( key, INITIAL_KEY );
+	memcpy( key, session->clientkey, strlen( session->clientkey ) );
+	ExpandKey( (unsigned char*) key, (unsigned char*) exkey );
+
+	/* build the custom data to be encrypted */
+	strcpy( pass, SECRET_HEADER );
+	strcat( pass, session->acc->password );
+
+	/* pad the secret data */
+	blocks = pad_secret_data( pass );
+	size = blocks * 16;
+
+	/* now encrypt the password. we encrypt each block separately (ECB mode) */
+	for ( i = 0; i < size; i += 16 )
+		Encrypt( (unsigned char*) pass + i, (unsigned char*) exkey, (unsigned char*) encrypted + i );
+
+	/* now base64 encode the encrypted password */
+	base64 = purple_base64_encode( (unsigned char*) encrypted, size );
+
+	return base64;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/cipher.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,36 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *					-- user password encryption --
+ *
+ *				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
+ */
+
+#ifndef		_MXIT_CIPHER_H_
+#define		_MXIT_CIPHER_H_
+
+
+struct MXitSession;
+
+
+char* mxit_encrypt_password( struct MXitSession* session );
+
+
+#endif		/* _MXIT_CIPHER_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/filexfer.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,454 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- file transfers (sending and receiving)  --
+ *
+ *				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	<unistd.h>
+#include	<string.h>
+
+#include	"purple.h"
+#include	"protocol.h"
+#include	"mxit.h"
+#include	"chunk.h"
+#include	"filexfer.h"
+
+
+#define		MIME_TYPE_OCTETSTREAM		"application/octet-stream"
+
+
+/* supported file mime types */
+static struct mime_type {
+	const char*		magic;
+	const short		magic_len;
+	const char*		mime;
+} const mime_types[] = {
+					/*	magic									length	mime					*/
+	/* images */	{	"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A",		8,		"image/png"				},		/* image png */
+					{	"\xFF\xD8",								2,		"image/jpeg"			},		/* image jpeg */
+					{	"\x3C\x3F\x78\x6D\x6C",					5,		"image/svg+xml"			},		/* image SVGansi */
+					{	"\xEF\xBB\xBF",							3,		"image/svg+xml"			},		/* image SVGutf */
+					{	"\xEF\xBB\xBF",							3,		"image/svg+xml"			},		/* image SVGZ */
+	/* mxit */		{	"\x4d\x58\x4d",							3,		"application/mxit-msgs"	},		/* mxit message */
+					{	"\x4d\x58\x44\x01",						4,		"application/mxit-mood" },		/* mxit mood */
+					{	"\x4d\x58\x45\x01",						4,		"application/mxit-emo"	},		/* mxit emoticon */
+					{	"\x4d\x58\x46\x01",						4,		"application/mxit-emof"	},		/* mxit emoticon frame */
+					{	"\x4d\x58\x53\x01",						4,		"application/mxit-skin"	},		/* mxit skin */
+	/* audio */		{	"\x4d\x54\x68\x64",						4,		"audio/midi"			},		/* audio midi */
+					{	"\x52\x49\x46\x46",						4,		"audio/wav"				},		/* audio wav */
+					{	"\xFF\xF1",								2,		"audio/aac"				},		/* audio aac1 */
+					{	"\xFF\xF9",								2,		"audio/aac"				},		/* audio aac2 */
+					{	"\xFF",									1,		"audio/mp3"				},		/* audio mp3 */
+					{	"\x23\x21\x41\x4D\x52\x0A",				6,		"audio/amr"				},		/* audio AMR */
+					{	"\x23\x21\x41\x4D\x52\x2D\x57\x42",		8,		"audio/amr-wb"			},		/* audio AMR WB */
+					{	"\x00\x00\x00",							3,		"audio/mp4"				},		/* audio mp4 */
+					{	"\x2E\x73\x6E\x64",						4,		"audio/au"				}		/* audio AU */
+};
+
+
+/*------------------------------------------------------------------------
+ * Return the MIME type matching the data file.
+ *
+ *  @param filename		The name of file
+ *  @param buf			The data
+ *  @param buflen		The length of the data
+ *  @return				A MIME type string
+ */
+const char* file_mime_type( const char* filename, const char* buf, int buflen )
+{
+	unsigned int	i;
+
+	/* check for matching magic headers */
+	for ( i = 0; i < ARRAY_SIZE( mime_types ); i++ ) {
+
+		if ( buflen < mime_types[i].magic_len )	/* data is shorter than size of magic */
+			continue;
+
+		if ( memcmp( buf, mime_types[i].magic, mime_types[i].magic_len ) == 0 )
+			return mime_types[i].mime;
+	}
+
+	/* we did not find the MIME type, so return the default (application/octet-stream) */
+	return MIME_TYPE_OCTETSTREAM;
+}
+
+
+/*------------------------------------------------------------------------
+ * Cleanup and deallocate a MXit file transfer object
+ *
+ *  @param xfer			The file transfer object
+ */
+static void mxit_xfer_free( PurpleXfer* xfer )
+{
+	struct mxitxfer*	mx		= (struct mxitxfer*) xfer->data;;
+
+	if ( mx ) {
+		g_free( mx );
+		xfer->data = NULL;
+	}
+}
+
+
+/*========================================================================================================================
+ * File Transfer callbacks
+ */
+
+/*------------------------------------------------------------------------
+ * Initialise a new file transfer.
+ *
+ *  @param xfer			The file transfer object
+ */
+static void mxit_xfer_init( PurpleXfer* xfer )
+{
+	struct mxitxfer*	mx	= (struct mxitxfer*) xfer->data;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_init\n" );
+
+	if ( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND ) {
+		/* we are trying to send a file to MXit */
+
+		if ( purple_xfer_get_size( xfer ) > CP_MAX_FILESIZE ) {
+			/* the file is too big */
+			purple_xfer_error( xfer->type, xfer->account, xfer->who, _( "The file you are trying to send is too large!" ) );
+			purple_xfer_cancel_local( xfer );
+			return;
+		}
+
+		/* start the file transfer */
+		purple_xfer_start( xfer, -1, NULL, 0 );
+	}
+	else {
+		/*
+		 * we have just accepted a file transfer request from MXit.  send a confirmation
+		 * to the MXit server so that can send us the file
+		 */
+		mxit_send_file_accept( mx->session, mx->fileid, purple_xfer_get_size( xfer ), 0 );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Start the file transfer.
+ *
+ *  @param xfer			The file transfer object
+ */
+static void mxit_xfer_start( PurpleXfer* xfer )
+{
+	unsigned char*	buffer;
+	int				size;
+	int				wrote;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_start\n" );
+
+	if ( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND ) {
+		/*
+		 * the user wants to send a file to one of his contacts. we need to create
+		 * a buffer and copy the file data into memory and then we can send it to
+		 * the contact. we will send the whole file with one go.
+		 */
+		buffer = g_malloc( xfer->bytes_remaining );
+		size = fread( buffer, xfer->bytes_remaining, 1, xfer->dest_fp );
+
+		wrote = purple_xfer_write( xfer, buffer, xfer->bytes_remaining );
+		if ( wrote > 0 )
+			purple_xfer_set_bytes_sent( xfer, wrote );
+
+		/* free the buffer */
+		g_free( buffer );
+		buffer = NULL;
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * The file transfer has ended.
+ *
+ *  @param xfer			The file transfer object
+ */
+static void mxit_xfer_end( PurpleXfer* xfer )
+{
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_end\n" );
+
+	/* deallocate object */
+	mxit_xfer_free( xfer );
+}
+
+
+/*------------------------------------------------------------------------
+ * The file transfer (to a user) has been cancelled.
+ *
+ *  @param xfer			The file transfer object
+ */
+static void mxit_xfer_cancel_send( PurpleXfer* xfer )
+{
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_cancel_send\n" );
+
+	/* deallocate object */
+	mxit_xfer_free( xfer );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send the file data.
+ *
+ *  @param buffer		The data to sent
+ *  @param size			The length of the data to send
+ *  @param xfer			The file transfer object
+ *  @return				The amount of data actually sent
+ */
+static gssize mxit_xfer_write( const guchar* buffer, size_t size, PurpleXfer* xfer )
+{
+	struct mxitxfer*	mx	= (struct mxitxfer*) xfer->data;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_write\n" );
+
+	if ( !mx ) {
+		purple_debug_warning( MXIT_PLUGIN_ID, "mxit_xfer_write: invalid internal mxit xfer data\n" );
+		return -1;
+	}
+	else if ( purple_xfer_get_type( xfer ) != PURPLE_XFER_SEND ) {
+		purple_debug_warning( MXIT_PLUGIN_ID, "mxit_xfer_write: wrong xfer type received\n" );
+		return -1;
+	}
+
+	/* create and send the packet to MXit */
+	mxit_send_file( mx->session, purple_xfer_get_remote_user( xfer ), purple_xfer_get_filename( xfer ), buffer, size );
+
+	/* the transfer is complete */
+	purple_xfer_set_completed( xfer, TRUE );
+
+	return size;
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has rejected a file offer from MXit.
+ *
+ *  @param xfer			The file transfer object
+ */
+static void mxit_xfer_request_denied( PurpleXfer* xfer )
+{
+	struct mxitxfer*	mx		= (struct mxitxfer*) xfer->data;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_request_denied\n" );
+
+	/* send file reject packet to MXit server */
+	mxit_send_file_reject( mx->session, mx->fileid );
+
+	/* deallocate object */
+	mxit_xfer_free( xfer );
+}
+
+
+/*------------------------------------------------------------------------
+ * The file transfer (from MXit) has been cancelled.
+ */
+static void mxit_xfer_cancel_recv( PurpleXfer* xfer )
+{
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_cancel_recv\n" );
+
+	/* deallocate object */
+	mxit_xfer_free( xfer );
+}
+
+
+/*========================================================================================================================
+ * Callbacks from libPurple
+ */
+
+/*------------------------------------------------------------------------
+ * Indicate if file transfers are supported to this contact.
+ * For MXit file transfers are always supported.
+ *
+ *  @param gc			The connection object
+ *  @param who			The username of the contact
+ *  @return				TRUE if file transfers are supported
+ */
+gboolean mxit_xfer_enabled( PurpleConnection* gc, const char* who )
+{
+	return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Create and initialize a new file transfer to a contact.
+ *
+ *  @param gc			The connection object
+ *  @param who			The username of the recipient
+ */
+PurpleXfer* mxit_xfer_new( PurpleConnection* gc, const char* who )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+	PurpleXfer*			xfer	= NULL;
+	struct mxitxfer*	mx		= NULL;
+
+	/* (reference: "libpurple/ft.h") */
+	xfer = purple_xfer_new( session->acc, PURPLE_XFER_SEND, who );
+
+	/* create file info and attach it to the file transfer */
+	mx = g_new0( struct mxitxfer, 1 );
+	mx->session = session;
+	xfer->data = mx;
+
+	/* configure callbacks (reference: "libpurple/ft.h") */
+	purple_xfer_set_init_fnc( xfer, mxit_xfer_init );
+	purple_xfer_set_start_fnc( xfer, mxit_xfer_start );
+	purple_xfer_set_end_fnc( xfer, mxit_xfer_end );
+	purple_xfer_set_cancel_send_fnc( xfer, mxit_xfer_cancel_send );
+	purple_xfer_set_write_fnc( xfer, mxit_xfer_write );
+
+	return xfer;
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has initiated a file transfer to a contact.
+ *
+ *  @param gc			The connection object
+ *  @param who			The username of the contact
+ *  @param filename		The filename (is NULL if request has not been accepted yet)
+ */
+void mxit_xfer_tx( PurpleConnection* gc, const char* who, const char* filename )
+{
+	PurpleXfer	*xfer	= mxit_xfer_new( gc, who );
+
+	if ( filename )
+		purple_xfer_request_accepted( xfer, filename );
+	else
+		purple_xfer_request( xfer );
+}
+
+
+/*========================================================================================================================
+ * Calls from the MXit Protocol layer
+ */
+
+/*------------------------------------------------------------------------
+ * A file transfer offer has been received from the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param usermame		The username of the sender
+ *  @param filename		The name of the file being offered
+ *  @param filesize		The size of the file being offered
+ *  @param fileid		A unique ID that identifies this file
+ */
+void mxit_xfer_rx_offer( struct MXitSession* session, const char* username, const char* filename, int filesize, const char* fileid )
+{
+	PurpleXfer*			xfer	= NULL;
+	struct mxitxfer*	mx		= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "File Offer: file=%s, from=%s, size=%i\n", filename, username, filesize );
+
+	xfer = purple_xfer_new( session->acc, PURPLE_XFER_RECEIVE, username );
+	if ( xfer ) {
+		/* create a new mxit xfer struct for internal use */
+		mx = g_new0( struct mxitxfer, 1 );
+		mx->session = session;
+		memcpy( mx->fileid, fileid, MXIT_CHUNK_FILEID_LEN );
+		xfer->data = mx;
+
+		purple_xfer_set_filename( xfer, filename );
+		if( filesize > 0 )
+			purple_xfer_set_size( xfer, filesize );
+
+		/* register file transfer callback functions */
+		purple_xfer_set_init_fnc( xfer, mxit_xfer_init );
+		purple_xfer_set_request_denied_fnc( xfer, mxit_xfer_request_denied );
+		purple_xfer_set_cancel_recv_fnc( xfer, mxit_xfer_cancel_recv );
+		purple_xfer_set_end_fnc( xfer, mxit_xfer_end );
+
+		/* give the request to the user to accept/deny */
+		purple_xfer_request( xfer );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the libPurple file-transfer object associated with a MXit transfer
+ *
+ *  @param session		The MXit session object
+ *  @param fileid		A unique ID that identifies this file
+ */
+static PurpleXfer* find_mxit_xfer( struct MXitSession* session, const char* fileid )
+{
+	GList*		item	= NULL;
+	PurpleXfer*	xfer	= NULL;
+
+	item = purple_xfers_get_all();		/* list of all active transfers */
+	while ( item ) {
+		xfer = item->data;
+
+		if ( xfer->account == session->acc ) {
+			/* transfer is associated with this MXit account */
+			struct mxitxfer* mx	= xfer->data;
+
+			/* does the fileid match? */
+			if ( ( mx ) && ( memcmp( mx->fileid, fileid, MXIT_CHUNK_FILEID_LEN ) == 0 ) )
+				break;
+		}
+
+		item = g_list_next( item );
+	}
+
+	if ( item )
+		return item->data;
+	else
+		return NULL;
+}
+
+/*------------------------------------------------------------------------
+ * A file has been received from the MXit server.
+ *
+ *  @param session		The	MXit session object
+ *  @param fileid		A unique ID that identifies this file
+ *  @param data			The file data
+ *  @param datalen		The size of the data
+ */
+void mxit_xfer_rx_file( struct MXitSession* session, const char* fileid, const char* data, int datalen )
+{
+	PurpleXfer*			xfer	= NULL;
+	struct mxitxfer*	mx		= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_xfer_rx_file: (size=%i)\n", datalen );
+
+	/* find the file-transfer object */
+	xfer = find_mxit_xfer( session, fileid );
+	if ( xfer ) {
+		mx = xfer->data;
+
+		/* this is the transfer we have been looking for */
+		purple_xfer_ref( xfer );
+		purple_xfer_start( xfer, -1, NULL, 0 );
+		fwrite( data, datalen, 1, xfer->dest_fp );
+		purple_xfer_unref( xfer );
+		purple_xfer_set_completed( xfer, TRUE );
+		purple_xfer_end( xfer );
+
+		/* inform MXit that file was successfully received */
+		mxit_send_file_received( session, fileid, RECV_STATUS_SUCCESS );
+	}
+	else {
+		/* file transfer not found */
+		mxit_send_file_received( session, fileid, RECV_STATUS_BAD_ID );
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/filexfer.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,50 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- file transfers (sending and receiving)  --
+ *
+ *				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
+ */
+
+#ifndef		_MXIT_FILEXFER_H_
+#define		_MXIT_FILEXFER_H_
+
+
+/*
+ * a MXit file transfer
+ */
+struct mxitxfer {
+	struct MXitSession*		session;
+	char					fileid[MXIT_CHUNK_FILEID_LEN];
+};
+
+const char* file_mime_type( const char* filename, const char* buf, int buflen );
+
+/* libPurple callbacks */
+gboolean mxit_xfer_enabled( PurpleConnection* gc, const char* who );
+void mxit_xfer_tx( PurpleConnection* gc, const char* who, const char* filename );
+PurpleXfer* mxit_xfer_new( PurpleConnection* gc, const char* who );
+
+/* MXit Protocol callbacks */
+void mxit_xfer_rx_offer( struct MXitSession* session, const char* username, const char* filename, int filesize, const char* fileid );
+void mxit_xfer_rx_file( struct MXitSession* session, const char* fileid, const char* data, int datalen );
+
+
+#endif		/* _MXIT_FILEXFER_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/formcmds.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,397 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *					-- MXit Forms & Commands --
+ *
+ *				Andrew Victor	<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 <string.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include "purple.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "markup.h"
+#include "formcmds.h"
+
+#undef MXIT_DEBUG_COMMANDS
+
+/*
+ * the MXit Command identifiers
+ */
+typedef enum
+{
+	MXIT_CMD_UNKNOWN = 0,		/* Unknown command */
+	MXIT_CMD_CLRSCR,			/* Clear screen (clrmsgscreen) */
+	MXIT_CMD_SENDSMS,			/* Send SMS (sendsms) */
+	MXIT_CMD_REPLY,				/* Reply (reply) */
+	MXIT_CMD_PLATREQ,			/* Platform Request (platreq) */
+	MXIT_CMD_SELECTCONTACT,		/* Select Contact (selc) */
+	MXIT_CMD_IMAGE				/* Inline image (img) */
+} MXitCommandType;
+
+
+/*
+ * object for an inline image request with an URL
+ */
+struct ii_url_request
+{
+	struct RXMsgData*	mx;
+	char*				url;
+};
+
+
+/*------------------------------------------------------------------------
+ * Callback function invoked when an inline image request to a web site completes.
+ *
+ *  @param url_data
+ *  @param user_data		The Markup message 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_ii_returned(PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message)
+{
+	struct ii_url_request*	iireq		= (struct ii_url_request*) user_data;
+	char*					ii_data;
+	int*					intptr		= NULL;
+	int						id;
+
+#ifdef	MXIT_DEBUG_COMMANDS
+	purple_debug_info(MXIT_PLUGIN_ID, "Inline Image returned from %s\n", iireq->url);
+#endif
+
+	if (!url_text) {
+		/* no reply from the WAP site */
+		purple_debug_error(MXIT_PLUGIN_ID, "Error downloading Inline Image from %s.\n", iireq->url);
+		goto done;
+	}
+
+	/* lets first see if we dont have the inline image already in cache */
+	if (g_hash_table_lookup(iireq->mx->session->iimages, iireq->url)) {
+		/* inline image found in the cache, so we just ignore this reply */
+		goto done;
+	}
+
+	/* make a copy of the data */
+	ii_data = g_malloc(len);
+	memcpy(ii_data, (const char*) url_text, len);
+
+	/* we now have the inline image, store it in the imagestore */
+	id = purple_imgstore_add_with_id(ii_data, len, NULL);
+
+	/* map the inline image id to purple image id */
+	intptr = g_malloc(sizeof(int));
+	*intptr = id;
+	g_hash_table_insert(iireq->mx->session->iimages, iireq->url, intptr);
+
+	iireq->mx->flags |= PURPLE_MESSAGE_IMAGES;
+
+done:
+	iireq->mx->img_count--;
+	if ((iireq->mx->img_count == 0) && (iireq->mx->converted)) {
+		/*
+		 * this was the last outstanding emoticon for this message,
+		 * so we can now display it to the user.
+		 */
+		mxit_show_message(iireq->mx);
+	}
+
+	g_free(iireq);
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the command identifier of this MXit Command.
+ *
+ *  @param cmd			The MXit command <key,value> map
+ *  @return				The MXit command identifier
+ */
+static MXitCommandType command_type(GHashTable* hash)
+{
+	char* op;
+	char* type;
+
+	op = g_hash_table_lookup(hash, "op");
+	if (op) {
+		if ( strcmp(op, "cmd") == 0 ) {
+			type = g_hash_table_lookup(hash, "type");
+			if (type == NULL)								/* no command provided */
+				return MXIT_CMD_UNKNOWN;
+			else if (strcmp(type, "clrmsgscreen") == 0)		/* clear the screen */
+				return MXIT_CMD_CLRSCR;
+			else if (strcmp(type, "sendsms") == 0)			/* send an SMS */
+				return MXIT_CMD_SENDSMS;
+			else if (strcmp(type, "reply") == 0)			/* list of options */
+				return MXIT_CMD_REPLY;
+			else if (strcmp(type, "platreq") == 0)			/* platform request */
+				return MXIT_CMD_PLATREQ;
+			else if (strcmp(type, "selc") == 0)				/* select contact */
+				return MXIT_CMD_SELECTCONTACT;
+		}
+		else if (strcmp(op, "img") == 0)
+				return MXIT_CMD_IMAGE;
+	}
+
+	return MXIT_CMD_UNKNOWN;
+}
+
+
+/*------------------------------------------------------------------------
+ * Tokenize a MXit Command string into a <key,value> map.
+ *
+ *  @param cmd			The MXit command string
+ *  @return				The <key,value> hash-map, or NULL on error.
+ */
+static GHashTable* command_tokenize(char* cmd)
+{
+	GHashTable* hash	= NULL;
+	gchar**		parts;
+	gchar*		part;
+	int			i		= 0;
+
+#ifdef MXIT_DEBUG_COMMANDS
+	purple_debug_info(MXIT_PLUGIN_ID, "command: '%s'\n", cmd);
+#endif
+
+	/* explode the command into parts */
+	parts = g_strsplit(cmd, "|", 0);
+
+	hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+	/* now break part into a key & value */
+	while ((part = parts[i]) != NULL) {
+		char* value;
+
+		value = strchr(parts[i], '=');		/* find start of value */
+		if (value != NULL) {
+			*value = '\0';
+			value++;
+		}
+
+#ifdef MXIT_DEBUG_COMMANDS
+		purple_debug_info(MXIT_PLUGIN_ID, "  key='%s' value='%s'\n", parts[i], value);
+#endif
+
+		g_hash_table_insert(hash, g_strdup(parts[i]), g_strdup(value));
+
+		i++;
+	}
+
+	g_strfreev(parts);
+
+	return hash;
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a ClearScreen MXit command.
+ *
+ *  @param session			The MXit session object
+ *  @param from				The sender of the message.
+ */
+static void command_clearscreen(struct MXitSession* session, const char* from)
+{
+	PurpleConversation *conv;
+
+    conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, from, session->acc);
+    if (conv == NULL) {
+        purple_debug_error(MXIT_PLUGIN_ID, "Conversation with '%s' not found\n", from);
+        return;
+    }
+
+	purple_conversation_clear_message_history(conv);			// TODO: This doesn't actually clear the screen.
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a Reply MXit command.
+ *
+ *  @param mx			The received message data object
+ *  @param hash			The MXit command <key,value> map
+ */
+static void command_reply(struct RXMsgData* mx, GHashTable* hash)
+{
+	char* replymsg;
+	char* selmsg;
+
+	selmsg = g_hash_table_lookup(hash, "selmsg");			/* find the selection message */
+	replymsg = g_hash_table_lookup(hash, "replymsg");		/* find the reply message */
+	if ((selmsg) && (replymsg)) {
+		gchar*	seltext = g_markup_escape_text(purple_url_decode(selmsg), -1);
+		gchar*	replytext = g_markup_escape_text(purple_url_decode(replymsg), -1);
+
+		mxit_add_html_link( mx, replytext, seltext );
+
+		g_free(seltext);
+		g_free(replytext);
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a PlatformRequest MXit command.
+ *
+ *  @param hash			The MXit command <key,value> map
+ *  @param msg			The message to display (as generated so far)
+ */
+static void command_platformreq(GHashTable* hash, GString* msg)
+{
+	gchar*	text	= NULL;
+	char*	selmsg;
+	char*	dest;
+
+	selmsg = g_hash_table_lookup(hash, "selmsg");			/* find the selection message */
+	if (selmsg) {
+		text = g_markup_escape_text(purple_url_decode(selmsg), -1);
+	}
+
+	dest = g_hash_table_lookup(hash, "dest");				/* find the destination */
+	if (dest) {
+		g_string_append_printf(msg, "<a href=\"%s\">%s</a>", purple_url_decode(dest), (text) ? text : "Download");		/* add link to display message */
+	}
+
+	if (text)
+		g_free(text);
+}
+
+
+/*------------------------------------------------------------------------
+ * Process an inline image MXit command.
+ *
+ *  @param mx			The received message data object
+ *  @param hash			The MXit command <key,value> map
+ *  @param msg			The message to display (as generated so far)
+ */
+static void command_image(struct RXMsgData* mx, GHashTable* hash, GString* msg)
+{
+	const char*	img;
+	const char*	reply;
+	guchar*		rawimg;
+	char		link[256];
+	gsize		rawimglen;
+	int			imgid;
+
+	img = g_hash_table_lookup(hash, "dat");
+	if (img) {
+		rawimg = purple_base64_decode(img, &rawimglen);
+		//purple_util_write_data_to_file_absolute("/tmp/mxitinline.png", (char*) rawimg, rawimglen);
+		imgid = purple_imgstore_add_with_id(rawimg, rawimglen, NULL);
+		g_snprintf(link, sizeof(link), "<img id=\"%i\">", imgid);
+		g_string_append_printf(msg, "%s", link);
+		mx->flags |= PURPLE_MESSAGE_IMAGES;
+	}
+	else {
+		img = g_hash_table_lookup(hash, "src");
+		if (img) {
+			struct ii_url_request*	iireq;
+
+			iireq = g_new0(struct ii_url_request,1);
+			iireq->url = g_strdup(purple_url_decode(img));
+			iireq->mx = mx;
+
+			g_string_append_printf(msg, "%s%s>", MXIT_II_TAG, iireq->url);
+			mx->got_img = TRUE;
+
+			/* lets first see if we dont have the inline image already in cache */
+			if (g_hash_table_lookup(mx->session->iimages, iireq->url)) {
+				/* inline image found in the cache, so we do not have to request it from the web */
+				g_free(iireq);
+			}
+			else {
+				/* send the request for the inline image */
+				purple_debug_info(MXIT_PLUGIN_ID, "sending request for inline image '%s'\n", iireq->url);
+
+				/* request the image (reference: "libpurple/util.h") */
+				purple_util_fetch_url_request(iireq->url, TRUE, NULL, TRUE, NULL, FALSE, mxit_cb_ii_returned, iireq);
+				mx->img_count++;
+			}
+		}
+	}
+
+	/* if this is a clickable image, show a click link */
+	reply = g_hash_table_lookup(hash, "replymsg");
+	if (reply) {
+		g_string_append_printf(msg, "\n");
+		mxit_add_html_link(mx, reply, "click here");
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received MXit Command message.
+ *
+ *  @param mx				The received message data object
+ *  @param message			The message text
+ *  @return					The length of the command
+ */
+//void mxit_command_received(struct MXitSession* session, const char* from, char* message, time_t timestamp)
+int mxit_parse_command(struct RXMsgData* mx, char* message)
+{
+	GHashTable* hash	= NULL;
+	char*		start;
+	char*		end;
+
+	/* ensure that this is really a command */
+	if ( ( message[0] != ':' ) || ( message[1] != ':' ) ) {
+		/* this is not a command */
+		return 0;
+	}
+
+	start = message + 2;
+	end = strstr(start, ":");
+	if (end) {
+		/* end of a command found */
+		*end = '\0';		/* terminate command string */
+
+		hash = command_tokenize(start);			/* break into <key,value> pairs */
+		if (hash) {
+			MXitCommandType type = command_type(hash);
+
+			switch (type) {
+				case MXIT_CMD_CLRSCR :
+					command_clearscreen(mx->session, mx->from);
+					break;
+				case MXIT_CMD_REPLY :
+					command_reply(mx, hash);
+					break;
+				case MXIT_CMD_PLATREQ :
+					command_platformreq(hash, mx->msg);
+					break;
+				case MXIT_CMD_IMAGE :
+					command_image(mx, hash, mx->msg);
+					break;
+				default :
+					/* command unknown, or not currently supported */
+					break;
+			}
+			g_hash_table_destroy(hash);
+		}
+		*end = ':';
+
+		return end - message;
+	}
+	else {
+		return 0;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/formcmds.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,35 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *					-- MXit Forms & Commands --
+ *
+ *				Andrew Victor	<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
+ */
+
+#ifndef		_MXIT_FORMCMDS_H_
+#define		_MXIT_FORMCMDS_H_
+
+#include	"mxit.h"
+
+
+int mxit_parse_command(struct RXMsgData* mx, char* message);
+
+
+#endif		/* _MXIT_FORMCMDS_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/http.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,331 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- MXit client protocol implementation --
+ *
+ *				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	<unistd.h>
+#include	<string.h>
+#include	<errno.h>
+
+#include	"purple.h"
+
+#include	"mxit.h"
+#include	"protocol.h"
+#include	"http.h"
+
+
+/* HTTP constants */
+#define		HTTP_11_200_OK		"HTTP/1.1 200 OK\r\n"
+#define		HTTP_11_100_CONT	"HTTP/1.1 100 Continue\r\n"
+#define		HTTP_11_SEPERATOR	"\r\n\r\n"
+#define		HTTP_CONTENT_LEN	"Content-Length: "
+
+
+/* define to enable HTTP debugging */
+#define		DEBUG_HTTP
+
+
+/*------------------------------------------------------------------------
+ * This will freeup the memory used by a HTTP request structure
+ *
+ *	@param req		The HTTP structure's resources should be freed up
+ */
+static void free_http_request( struct http_request* req )
+{
+	g_free( req->host );
+	g_free( req->data );
+	g_free( req );
+}
+
+
+/*------------------------------------------------------------------------
+ * Write the request to the HTTP server.
+ *
+ *  @param fd			The file descriptor
+ *  @param pktdata		The packet data
+ *  @param pktlen		The length of the packet data
+ *  @return				Return -1 on error, otherwise 0
+ */
+static int mxit_http_raw_write( int fd, const char* pktdata, int pktlen )
+{
+	int		written;
+	int		res;
+
+	written = 0;
+	while ( written < pktlen ) {
+		res = write( fd, &pktdata[written], pktlen - written );
+		if ( res <= 0 ) {
+			/* error on socket */
+			if ( errno == EAGAIN )
+				continue;
+
+			purple_debug_error( MXIT_PLUGIN_ID, "Error while writing packet to HTTP server (%i)\n", res );
+			return -1;
+		}
+		written += res;
+	}
+
+	return 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback when data is received from the HTTP server.
+ *
+ *  @param user_data		The MXit session object
+ *  @param source			The file-descriptor on which data was received
+ *  @param cond				Condition which caused the callback (PURPLE_INPUT_READ)
+ */
+static void mxit_cb_http_read( gpointer user_data, gint source, PurpleInputCondition cond )
+{
+	struct MXitSession*	session		= (struct MXitSession*) user_data;
+	char				buf[256];
+	int					buflen;
+	char*				body;
+	int					bodylen;
+	char*				ch;
+	int					len;
+	char*				tmp;
+	int					res;
+	char*				next;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_read\n" );
+
+	if ( session->rx_state == RX_STATE_RLEN ) {
+		/* we are reading in the HTTP headers */
+
+		/* copy partial headers if we have any part saved */
+		memcpy( buf, session->rx_dbuf, session->rx_i );
+		buflen = session->rx_i;
+
+		/* read bytes from the socket */
+		len = read( session->fd, buf + buflen, sizeof( buf ) - buflen );
+		if ( len <= 0 ) {
+			/* connection has been terminated, or error occured */
+			goto done;
+		}
+
+//nextpacket:
+
+#ifdef	DEBUG_HTTP
+		purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST READ 1: (%i)\n", len );
+		dump_bytes( session, buf + buflen, len );
+#endif
+
+		/* see if we have all the HTTP headers yet */
+		ch = strstr( buf, HTTP_11_SEPERATOR );
+		if ( !ch ) {
+			/* we need to wait for more input, so save what we have */
+			session->rx_i = buflen + len;
+			memcpy( session->rx_dbuf, buf, session->rx_i );
+			return;
+		}
+		buflen += len;
+
+		/* we have the header's end now skip over the http seperator to get the body offset */
+		ch += strlen( HTTP_11_SEPERATOR );
+		*(ch - 1) = '\0';
+		body = ch;
+
+		res = buflen - ( ch - buf );
+		if ( res > 0 ) {
+			/* we read more bytes than just the header so copy it over */
+			memcpy( session->rx_dbuf, ch, res );
+			session->rx_i = res;
+		}
+		else {
+			session->rx_i = 0;
+		}
+
+		/* test for a good response */
+		if ( ( strncmp( buf, HTTP_11_200_OK, strlen( HTTP_11_200_OK ) ) != 0 ) && ( strncmp( buf, HTTP_11_100_CONT, strlen( HTTP_11_100_CONT ) ) != 0 ) ) {
+			/* bad result */
+			purple_debug_error( MXIT_PLUGIN_ID, "HTTP error: %s\n", ch );
+			goto done;
+		}
+
+		/* find the content-length */
+		ch = (char*) purple_strcasestr( buf, HTTP_CONTENT_LEN );
+		if ( !ch ) {
+			/* bad request. it does not contain a content-length header */
+			purple_debug_error( MXIT_PLUGIN_ID, "HTTP reply received without content-length header (ignoring packet)\n" );
+			goto done;
+		}
+
+		/* parse the content-length */
+		ch += strlen( HTTP_CONTENT_LEN );
+		tmp = strchr( ch, '\r' );
+		if ( !tmp ) {
+			purple_debug_error( MXIT_PLUGIN_ID, "Received bad HTTP reply packet (ignoring packet)\n" );
+			goto done;
+		}
+		tmp = g_strndup( ch, tmp - ch );
+		bodylen = atoi( tmp );
+		g_free( tmp );
+		tmp = NULL;
+
+		if ( buflen > ( ( body - buf ) + bodylen ) ) {
+			/* we have a second packet here */
+			next = body + bodylen;
+			session->rx_res = 0;
+		}
+		else {
+			session->rx_res = bodylen - session->rx_i;
+		}
+
+		if ( session->rx_res == 0 ) {
+			/* we have read all the data */
+			session->rx_i = bodylen;
+			session->rx_state = RX_STATE_PROC;
+		}
+		else {
+			/* there is still some data outstanding */
+			session->rx_state = RX_STATE_DATA;
+		}
+	}
+	else if ( session->rx_state == RX_STATE_DATA ) {
+		/* we are reading the HTTP content (body) */
+
+		/* read bytes from the socket */
+		len = read( session->fd, &session->rx_dbuf[session->rx_i], session->rx_res );
+		if ( len <= 0 ) {
+			/* connection has been terminated, or error occured */
+			goto done;
+		}
+
+#ifdef	DEBUG_HTTP
+		purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST READ 2: (%i)\n", len );
+		dump_bytes( session, &session->rx_dbuf[session->rx_i], len );
+#endif
+		session->rx_i += len;
+		session->rx_res -= len;
+
+		if ( session->rx_res == 0 ) {
+			/* ok, so now we have read in the whole packet */
+			session->rx_state = RX_STATE_PROC;
+		}
+	}
+
+	if ( session->rx_state == RX_STATE_PROC ) {
+		mxit_parse_packet( session );
+
+#if	0
+		if ( next ) {
+			/* there is another packet of which we read some data */
+
+			/* reset input */
+			session->rx_state = RX_STATE_RLEN;
+			session->rx_lbuf[0] = '\0';
+			session->rx_i = 0;
+			session->rx_res = 0;
+
+			/* move read data */
+			len = next - buf;
+			buflen = len;
+			memcpy( buf, next, len );
+			goto nextpacket;
+		}
+#endif
+
+		/* we are done */
+		goto done;
+	}
+
+	return;
+done:
+	close( session->fd );
+	purple_input_remove( session->http_handler );
+	session->http_handler = 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback invoked once the connection has been established to the HTTP 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_http_connect( gpointer user_data, gint source, const gchar* error_message )
+{
+	struct http_request*	req	= (struct http_request*) user_data;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_connect\n" );
+
+	/* source is the file descriptor of the new connection */
+	if ( source < 0 ) {
+		purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_http_connect failed: %s\n", error_message );
+		purple_connection_error( req->session->con, _( "Unable to connect to the mxit HTTP server. Please check your server server settings." ) );
+		return;
+	}
+
+	/* we now have an open and active TCP connection to the mxit server */
+	req->session->fd = source;
+
+	/* reset the receive buffer */
+	req->session->rx_state = RX_STATE_RLEN;
+	req->session->rx_lbuf[0] = '\0';
+	req->session->rx_i = 0;
+	req->session->rx_res = 0;
+
+	/* start listening on the open connection for messages from the server (reference: "libpurple/eventloop.h") */
+	req->session->http_handler = purple_input_add( req->session->fd, PURPLE_INPUT_READ, mxit_cb_http_read, req->session );
+
+	/* actually send the request to the HTTP server */
+	mxit_http_raw_write( req->session->fd, req->data, req->datalen );
+
+	/* free up resources */
+	free_http_request( req );
+	req = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Create HTTP connection for sending a HTTP request
+ *
+ *	@param session		The MXit session object
+ *	@param host			The server name to connect to
+ *	@param port			The port number to connect to
+ *	@param data			The HTTP request data (including HTTP headers etc.)
+ *	@param datalen		The HTTP request data length
+ */
+void mxit_http_send_request( struct MXitSession* session, char* host, int port, const char* data, int datalen )
+{
+	PurpleProxyConnectData*		con	= NULL;
+	struct http_request*		req;
+
+	/* build the http request */
+	req = g_new0( struct http_request, 1 );
+	req->session = session;
+	req->host = host;
+	req->port = port;
+	req->data = g_malloc0( datalen );
+	memcpy( req->data, data, datalen );
+	req->datalen = datalen;
+
+	/* open connection to the HTTP server */
+	con = purple_proxy_connect( NULL, session->acc, host, port, mxit_cb_http_connect, req );
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/http.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,47 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- MXit client protocol implementation --
+ *
+ *				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
+ */
+
+
+#ifndef		_MXIT_HTTP_H_
+#define		_MXIT_HTTP_H_
+
+
+
+struct http_request
+{
+	struct MXitSession*		session;
+	char*					host;
+	int						port;
+	char*					data;
+	int						datalen;
+};
+
+
+void mxit_http_send_request( struct MXitSession* session, char* host, int port, const char* data, int datalen );
+
+
+
+#endif		/* _MXIT_HTTP_H_ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/login.c	Thu Nov 12 23:25:57 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 );
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/login.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,45 @@
+/*
+ *					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
+ */
+
+#ifndef		_MXIT_LOGIN_H_
+#define		_MXIT_LOGIN_H_
+
+
+struct login_data {
+	char*		wapserver;			/* direct WAP server for postback */
+	char*		sessionid;			/* unique session id */
+	guchar*		captcha;			/* actual captcha (PNG) */
+	gsize		captcha_size;		/* captcha size */
+	char*		cc;					/* country code */
+	char*		locale;				/* locale (language) */
+};
+
+
+void mxit_login( PurpleAccount* account );
+void mxit_register( PurpleAccount* account );
+void mxit_reconnect( struct MXitSession* session );
+
+
+#endif		/* _MXIT_LOGIN_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/markup.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,1192 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- convert between MXit and libPurple markup --
+ *
+ *				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	<unistd.h>
+#include	<string.h>
+
+#include	"purple.h"
+
+#include	"protocol.h"
+#include	"mxit.h"
+#include	"markup.h"
+#include	"chunk.h"
+#include	"formcmds.h"
+#include	"roster.h"
+
+
+/* define this to enable emoticon (markup) debugging */
+#undef		MXIT_DEBUG_EMO
+/* define this to enable markup conversion debugging */
+#undef		MXIT_DEBUG_MARKUP
+
+
+#define		MXIT_FRAME_MAGIC		"MXF\x01"			/* mxit emoticon magic number */
+#define		MXIT_MAX_EMO_ID			16					/* maximum emoticon ID length */
+#define		COLORCODE_LEN			6					/* colour code ID length */
+
+
+/* HTML tag types */
+#define		MXIT_TAG_COLOR			0x01				/* font color tag */
+#define		MXIT_TAG_SIZE			0x02				/* font size tag */
+#define		MXIT_MAX_MSG_TAGS		90					/* maximum tags per message (pigdin hack work around) */
+
+/*
+ * a HTML tag object
+ */
+struct tag {
+	char	type;
+	char*	value;
+};
+
+
+#define		MXIT_VIBE_MSG_COLOR		"#9933FF"
+
+/* vibes */
+static const char*	vibes[] = {
+	/* 0 */		"Cool Vibrations",
+	/* 1 */		"Purple Rain",
+	/* 2 */		"Polite",
+	/* 3 */		"Rock n Roll",
+	/* 4 */		"Summer Slumber",
+	/* 5 */		"Electric Razor",
+	/* 6 */		"S.O.S",
+	/* 7 */		"Jack Hammer",
+	/* 8 */		"Bumble Bee",
+	/* 9 */		"Ripple"
+};
+
+
+
+#ifdef	MXIT_DEBUG_EMO
+/*------------------------------------------------------------------------
+ * Dump a byte buffer as hexadecimal to the console for debugging purposes.
+ *
+ *  @param buf				The data to dump
+ *  @param len				The length of the data
+ */
+static void hex_dump( const char* buf, int len )
+{
+	char		msg[256];
+	int			pos;
+	int			i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "Dumping data (%i bytes)\n", len );
+
+	memset( msg, 0x00, sizeof( msg ) );
+	pos = 0;
+
+	for ( i = 0; i < len; i++ ) {
+
+		if ( pos == 0 )
+			pos += sprintf( &msg[pos], "%04i:  ", i );
+
+		pos += sprintf( &msg[pos], "0x%02X ", (unsigned char) buf[i] );
+
+		if ( i % 16 == 15 ) {
+			pos += sprintf( &msg[pos], "\n" );
+			purple_debug_info( MXIT_PLUGIN_ID, msg );
+			pos = 0;
+		}
+		else if ( i % 16 == 7 )
+			pos += sprintf( &msg[pos], " " );
+	}
+
+	if ( pos > 0 ) {
+		pos += sprintf( &msg[pos], "\n" );
+		purple_debug_info( MXIT_PLUGIN_ID, msg );
+		pos = 0;
+	}
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * Adds a link to a message
+ *
+ *  @param mx				The Markup message object
+ *	@param linkname			This is the what will be returned when the link gets clicked
+ *	@param displayname		This is the name for the link which will be displayed in the UI
+ */
+void mxit_add_html_link( struct RXMsgData* mx, const char* linkname, const char* displayname )
+{
+#ifdef	MXIT_LINK_CLICK
+	char	retstr[256];
+	gchar*	retstr64;
+	char	link[256];
+	int		len;
+
+	len = g_snprintf( retstr, sizeof( retstr ), "%s|%s|%s|%s|%s", MXIT_LINK_KEY, purple_account_get_username( mx->session->acc ),
+											purple_account_get_protocol_id( mx->session->acc ), mx->from, linkname );
+	retstr64 = purple_base64_encode( (const unsigned char*) retstr, len );
+	g_snprintf( link, sizeof( link ), "%s%s", MXIT_LINK_PREFIX, retstr64 );
+	g_free( retstr64 );
+
+	g_string_append_printf( mx->msg, "<a href=\"%s\">%s</a>", link, displayname );
+#else
+	g_string_append_printf( mx->msg, "<b>%s</b>", linkname );
+#endif
+}
+
+
+/*------------------------------------------------------------------------
+ * Extract an ASN.1 formatted length field from the data.
+ *
+ *  @param data				The source data
+ *  @param size				The extracted length
+ *  @return					The number of bytes extracted
+ */
+static unsigned int asn_getlength( const char* data, int* size )
+{
+	unsigned int	len		= 0;
+	unsigned char	bytes;
+	unsigned char	byte;
+	int				i;
+
+	/* first byte specifies the number of bytes in the length */
+	bytes = ( data[0] & ~0x80 );
+	if ( bytes > sizeof( unsigned int ) ) {
+		/* file too big! */
+		return -1;
+	}
+	data++;
+
+	/* parse out the actual length */
+	for ( i = 0; i < bytes; i++ ) {
+		byte = data[i];
+		len <<= 8;
+		len += byte;
+	}
+
+	*size = len;
+	return bytes + 1;
+}
+
+
+/*------------------------------------------------------------------------
+ * Extract an ASN.1 formatted UTF-8 string field from the data.
+ *
+ *  @param data				The source data
+ *  @param type				Expected type of string
+ *  @param utf8				The extracted string.  Must be deallocated by caller.
+ *  @return					The number of bytes extracted
+ */
+static int asn_getUtf8( const char* data, char type, char** utf8 )
+{
+	int		len;
+
+	/* validate the field type [1 byte] */
+	if ( data[0] != type ) {
+		/* this is not a utf-8 string! */
+		purple_debug_error( MXIT_PLUGIN_ID, "Invalid UTF-8 encoded string in ASN data (0x%02X)\n", (unsigned char) data[0] );
+		return -1;
+	}
+
+	len = data[1];						/* length field [1 bytes] */
+	*utf8 = g_malloc( len + 1 );
+	memcpy( *utf8, &data[2], len );		/* data field */
+	(*utf8)[len] = '\0';
+
+	return ( len + 2 );
+}
+
+
+/*------------------------------------------------------------------------
+ * Free data associated with a Markup message object.
+ *
+ *  @param mx				The Markup message object
+ */
+static void free_markupdata( struct RXMsgData* mx )
+{
+	if ( mx ) {
+		if ( mx->msg )
+			g_string_free( mx->msg, TRUE );
+		if ( mx->from )
+			g_free( mx->from );
+		g_free( mx );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Split the message into smaller messages and send them one at a time
+ * to pidgin to be displayed on the UI
+ *
+ *  @param mx				The received message object
+ */
+static void mxit_show_split_message( struct RXMsgData* mx )
+{
+	const char*		cont	= "<font color=\"#999999\">continuing...</font>\n";
+	GString*		msg		= NULL;
+	char*			ch		= NULL;
+	int				pos		= 0;
+	int				start	= 0;
+	int				l_nl	= 0;
+	int				l_sp	= 0;
+	int				l_gt	= 0;
+	int				stop	= 0;
+	int				tags	= 0;
+	int				segs	= 0;
+	gboolean		intag	= FALSE;
+
+	/*
+	 * awful hack to work around the awful hack in pidgin to work around GtkIMHtml's
+	 * inefficient rendering of messages with lots of formatting changes.
+	 * (reference: see the function pidgin_conv_write_conv() in gtkconv.c) the issue
+	 * is that when you have more than 100 '<' characters in the message passed to
+	 * pidgin, none of the markup (including links) are rendered and thus just dump
+	 * all the text as is to the conversation window. this message dump is very
+	 * confusing and makes it totally unusable. to work around this we will count
+	 * the amount of tags and if its more than the pidgin threshold, we will just
+	 * break the message up into smaller parts and send them seperately to pidgin.
+	 * to the user it will look like multiple messages, but at least he will be able
+	 * to use and understand it.
+	 */
+
+	ch = mx->msg->str;
+	pos = start;
+	while ( ch[pos] ) {
+
+		if ( ch[pos] == '<' ) {
+			tags++;
+			intag = TRUE;
+		}
+		else if ( ch[pos] == '\n' ) {
+			l_nl = pos;
+		}
+		else if ( ch[pos] == '>' ) {
+			l_gt = pos;
+			intag = FALSE;
+		}
+		else if ( ch[pos] == ' ' ) {
+			/* ignore spaces inside tags */
+			if ( !intag )
+				l_sp = pos;
+		}
+		else if ( ( ch[pos] == 'w' ) && ( pos + 4 < mx->msg->len ) && ( memcmp( &ch[pos], "www.", 4 ) == 0 ) ) {
+			tags += 2;
+		}
+		else if ( ( ch[pos] == 'h' ) && ( pos + 8 < mx->msg->len ) && ( memcmp( &ch[pos], "http://", 7 ) == 0 ) ) {
+			tags += 2;
+		}
+
+		if ( tags > MXIT_MAX_MSG_TAGS ) {
+			/* we have reached the maximum amount of tags pidgin (gtk) can handle per message.
+			   so its time to send what we have and then start building a new message */
+
+			/* now find the right place to break the message */
+			if ( l_nl > start ) {
+				/* break at last '\n' char */
+				stop = l_nl;
+				ch[stop] = '\0';
+				msg = g_string_new( &ch[start] );
+				ch[stop] = '\n';
+			}
+			else if ( l_sp > start ) {
+				/* break at last ' ' char */
+				stop = l_sp;
+				ch[stop] = '\0';
+				msg = g_string_new( &ch[start] );
+				ch[stop] = ' ';
+			}
+			else {
+				/* break at the last '>' char */
+				char t;
+				stop = l_gt + 1;
+				t = ch[stop];
+				ch[stop] = '\0';
+				msg = g_string_new( &ch[start] );
+				ch[stop] = t;
+				stop--;
+			}
+
+			/* build the string */
+			if ( segs )
+				g_string_prepend( msg, cont );
+
+			/* push message to pidgin */
+			serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp );
+			g_string_free( msg, TRUE );
+			msg = NULL;
+
+			tags = 0;
+			segs++;
+			start = stop + 1;
+		}
+
+		pos++;
+	}
+
+	if ( start != pos ) {
+		/* send the last part of the message */
+
+		/* build the string */
+		ch[pos] = '\0';
+		msg = g_string_new( &ch[start] );
+		ch[pos] = '\n';
+		if ( segs )
+			g_string_prepend( msg, cont );
+
+		/* push message to pidgin */
+		serv_got_im( mx->session->con, mx->from, msg->str, mx->flags, mx->timestamp );
+		g_string_free( msg, TRUE );
+		msg = NULL;
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Insert custom emoticons and inline images into the message (if there
+ * are any), then give the message to the UI to display to the user.
+ *
+ *  @param mx				The received message object
+ */
+void mxit_show_message( struct RXMsgData* mx )
+{
+	char*				pos;
+	int					start;
+	unsigned int		end;
+	int					emo_ofs;
+	char				ii[128];
+	char				tag[64];
+	int*				img_id;
+
+	if ( mx->got_img ) {
+		/* search and replace all emoticon tags with proper image tags */
+
+		while ( ( pos = strstr( mx->msg->str, MXIT_II_TAG ) ) != NULL ) {
+			start = pos - mx->msg->str;					/* offset at which MXIT_II_TAG starts */
+			emo_ofs = start + strlen( MXIT_II_TAG );	/* offset at which EMO's ID starts */
+			end = emo_ofs + 1;							/* offset at which MXIT_II_TAG ends */
+
+			while ( ( end < mx->msg->len ) && ( mx->msg->str[end] != '>' ) )
+				end++;
+
+			if ( end == mx->msg->len )			/* end of emoticon tag not found */
+				break;
+
+			memset( ii, 0x00, sizeof( ii ) );
+			memcpy( ii, &mx->msg->str[emo_ofs], end - emo_ofs );
+
+			/* remove inline image tag */
+			g_string_erase( mx->msg, start, ( end - start ) + 1 );
+
+			/* find the image entry */
+			img_id = (int*) g_hash_table_lookup( mx->session->iimages, ii );
+			if ( !img_id ) {
+				/* inline image not found, so we will just skip it */
+				purple_debug_error( MXIT_PLUGIN_ID, "inline image NOT found (%s)\n", ii );
+			}
+			else {
+				/* insert img tag */
+				g_snprintf( tag, sizeof( tag ), "<img id=\"%i\">", *img_id );
+				g_string_insert( mx->msg, start, tag );
+			}
+		}
+	}
+
+#ifdef MXIT_DEBUG_MARKUP
+	purple_debug_info( MXIT_PLUGIN_ID, "Markup RX (converted): '%s'\n", mx->msg->str );
+#endif
+
+	if ( mx->processed ) {
+		/* this message has already been taken care of, so just ignore it here */
+	}
+	else if ( mx->chatid < 0 ) {
+		/* normal chat message */
+		//serv_got_im( mx->session->con, mx->from, mx->msg->str, mx->flags, mx->timestamp );
+		mxit_show_split_message( mx );
+	}
+	else {
+		/* this is a multimx message */
+		serv_got_chat_in( mx->session->con, mx->chatid, mx->from, mx->flags, mx->msg->str, mx->timestamp);
+	}
+
+	/* freeup resource */
+	free_markupdata( mx );
+}
+
+
+/*------------------------------------------------------------------------
+ * Extract the custom emoticon ID from the message.
+ *
+ *  @param message			The input data
+ *  @param emid				The extracted emoticon ID
+ */
+static void parse_emoticon_str( const char* message, char* emid )
+{
+	int		i;
+
+	for ( i = 0; ( message[i] != '\0' && message[i] != '}' && i < MXIT_MAX_EMO_ID ); i++ ) {
+		emid[i] = message[i];
+	}
+
+	if ( message[i] == '\0' ) {
+		/* end of message reached, ignore the tag */
+		emid[0] = '\0';
+	}
+	else if ( i == MXIT_MAX_EMO_ID ) {
+		/* invalid tag length, ignore the tag */
+		emid[0] = '\0';
+	}
+	else
+		emid[i] = '\0';
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback function invoked when a custom emoticon request to the WAP site completes.
+ *
+ *  @param url_data
+ *  @param user_data		The Markup message 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 emoticon_returned( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
+{
+	struct RXMsgData*	mx			= (struct RXMsgData*) user_data;
+	const char*			data		= url_text;
+	unsigned int		pos			= 0;
+	char				emo[16];
+	int					id;
+	char*				str;
+	int					em_size		= 0;
+	char*				em_data		= NULL;
+	char*				em_id		= NULL;
+	int*				intptr		= NULL;
+	int					res;
+
+#ifdef	MXIT_DEBUG_EMO
+	purple_debug_info( MXIT_PLUGIN_ID, "emoticon_returned\n" );
+#endif
+
+	if ( !url_text ) {
+		/* no reply from the WAP site */
+		purple_debug_error( MXIT_PLUGIN_ID, "Error contacting the MXit WAP site. Please try again later (emoticon).\n" );
+		goto done;
+	}
+
+#ifdef	MXIT_DEBUG_EMO
+	hex_dump( data, len );
+#endif
+
+	/* parse out the emoticon */
+	pos = 0;
+
+	/* validate the binary data received from the wapsite */
+	if ( memcmp( MXIT_FRAME_MAGIC, &data[pos], strlen( MXIT_FRAME_MAGIC ) ) != 0 ) {
+		/* bad data, magic constant is wrong */
+		purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad magic)\n" );
+		goto done;
+	}
+	pos += strlen( MXIT_FRAME_MAGIC );
+
+	/* validate the image frame desc byte */
+	if ( data[pos] != '\x6F' ) {
+		/* bad frame desc */
+		purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad frame desc)\n" );
+		goto done;
+	}
+	pos++;
+
+	/* get the data length */
+	res = asn_getlength( &data[pos], &em_size );
+	if ( res <= 0 ) {
+		/* bad frame length */
+		purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad frame length)\n" );
+		goto done;
+	}
+	pos += res;
+#ifdef	MXIT_DEBUG_EMO
+	purple_debug_info( MXIT_PLUGIN_ID, "read the length '%i'\n", em_size );
+#endif
+
+	/* utf-8 (emoticon name) */
+	res = asn_getUtf8( &data[pos], 0x0C, &str );
+	if ( res <= 0 ) {
+		/* bad utf-8 string */
+		purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad name string)\n" );
+		goto done;
+	}
+	pos += res;
+#ifdef	MXIT_DEBUG_EMO
+	purple_debug_info( MXIT_PLUGIN_ID, "read the string '%s'\n", str );
+#endif
+	g_free( str );
+	str = NULL;
+
+	/* utf-8 (emoticon shortcut) */
+	res = asn_getUtf8( &data[pos], 0x81, &str );
+	if ( res <= 0 ) {
+		/* bad utf-8 string */
+		purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad shortcut string)\n" );
+		goto done;
+	}
+	pos += res;
+#ifdef	MXIT_DEBUG_EMO
+	purple_debug_info( MXIT_PLUGIN_ID, "read the string '%s'\n", str );
+#endif
+	em_id = str;
+
+	/* validate the image data type */
+	if ( data[pos] != '\x82' ) {
+		/* bad frame desc */
+		purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad data type)\n" );
+		g_free( em_id );
+		goto done;
+	}
+	pos++;
+
+	/* get the data length */
+	res = asn_getlength( &data[pos], &em_size );
+	if ( res <= 0 ) {
+		/* bad frame length */
+		purple_debug_error( MXIT_PLUGIN_ID, "Invalid emoticon received from wapsite (bad data length)\n" );
+		g_free( em_id );
+		goto done;
+	}
+	pos += res;
+#ifdef	MXIT_DEBUG_EMO
+	purple_debug_info( MXIT_PLUGIN_ID, "read the length '%i'\n", em_size );
+#endif
+
+	if ( g_hash_table_lookup( mx->session->iimages, em_id ) ) {
+		/* emoticon found in the table, so ignore this one */
+		goto done;
+	}
+
+	/* make a copy of the data */
+	em_data = g_malloc( em_size );
+	memcpy( em_data, &data[pos], em_size );
+
+	/* strip the mxit markup tags from the emoticon id */
+	if ( ( em_id[0] == '.' ) && ( em_id[1] == '{' ) ) {
+		parse_emoticon_str( &em_id[2], emo );
+		strcpy( em_id, emo );
+	}
+
+	/* we now have the emoticon, store it in the imagestore */
+	id = purple_imgstore_add_with_id( em_data, em_size, NULL );
+
+	/* map the mxit emoticon id to purple image id */
+	intptr = g_malloc( sizeof( int ) );
+	*intptr = id;
+	g_hash_table_insert( mx->session->iimages, em_id, intptr );
+
+	mx->flags |= PURPLE_MESSAGE_IMAGES;
+done:
+	mx->img_count--;
+	if ( ( mx->img_count == 0 ) && ( mx->converted ) ) {
+		/*
+		 * this was the last outstanding emoticon for this message,
+		 * so we can now display it to the user.
+		 */
+		mxit_show_message( mx );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a request to the MXit WAP site to download the specified emoticon.
+ *
+ *  @param mx				The Markup message object
+ *  @param id				The ID for the emoticon
+ */
+static void emoticon_request( struct RXMsgData* mx, const char* id )
+{
+	PurpleUtilFetchUrlData*	url_data;
+	const char*				wapserver;
+	char*					url;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "sending request for emoticon '%s'\n", id );
+
+	wapserver = purple_account_get_string( mx->session->acc, MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE );
+
+	/* reference: "libpurple/util.h" */
+	url = g_strdup_printf( "%s/res/?type=emo&mlh=%i&sc=%s&ts=%li", wapserver, MXIT_EMOTICON_SIZE, id, time( NULL ) );
+	url_data = purple_util_fetch_url_request( url, TRUE, NULL, TRUE, NULL, FALSE, emoticon_returned, mx );
+	g_free( url );
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse a Vibe command.
+ *
+ *  @param mx				The Markup message object
+ *  @param message			The message text (which contains the vibe)
+ *  @return id				The length of the message to skip
+ */
+static int mxit_parse_vibe( struct RXMsgData* mx, const char* message )
+{
+	int		vibeid;
+
+	vibeid = message[2] - '0';
+
+	purple_debug_info( MXIT_PLUGIN_ID, "Vibe received (%i)\n", vibeid );
+
+	if ( vibeid > ( ARRAY_SIZE( vibes ) - 1 ) ) {
+		purple_debug_warning( MXIT_PLUGIN_ID, "Unsupported vibe received (%i)\n", vibeid );
+		/* unsupported vibe */
+		return 0;
+	}
+
+	g_string_append_printf( mx->msg, "<font color=\"%s\"><i>%s Vibe...</i></font>", MXIT_VIBE_MSG_COLOR, vibes[vibeid] );
+	return 2;
+}
+
+
+/*------------------------------------------------------------------------
+ * Extract the nickname from a chatroom message and display it nicely in
+ * libPurple-style (HTML) markup.
+ *
+ *  @param mx				The received message data object
+ *  @param message			The message text
+ *  @return					The length of the message to skip
+ */
+static int mxit_extract_chatroom_nick( struct RXMsgData* mx, char* message, int len )
+{
+	int		i;
+
+	if ( message[0] == '<' ) {
+		/*
+		 * The message MIGHT contains an embedded nickname.  But we can't
+		 * be sure unless we find the end-of-nickname sequence: (>\n)
+		 * Search for it....
+		 */
+		gboolean	found	= FALSE;
+		gchar*		nickname;
+
+		for ( i = 1; i < len; i++ ) {
+			if ( ( message[i] == '\n' ) && ( message[i-1] == '>' ) ) {
+				found = TRUE;
+				message[i-1] = '\0';	/* loose the '>' */
+				i++;					/* and skip the new-line */
+				break;
+			}
+		}
+
+		if ( found ) {
+			/*
+			 * The message definitely had an embedded nickname - generate a marked-up
+			 * message to be displayed.
+			 */
+			nickname = g_markup_escape_text( &message[1], -1 );
+
+			/* add nickname within some BOLD markup to the new converted message */
+			g_string_append_printf( mx->msg, "<b>%s:</b> ", nickname );
+
+			/* free up the resources */
+			g_free( nickname );
+
+			return i;
+		}
+	}
+
+	return 0;
+}
+
+
+
+/*------------------------------------------------------------------------
+ * Convert a message containing MXit protocol markup to libPurple-style (HTML) markup.
+ *
+ *  @param mx				The received message data object
+ *  @param message			The message text
+ *  @param len				The length of the message
+ */
+void mxit_parse_markup( struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags )
+{
+	char		tmpstr1[128];
+	char*		ch;
+	int			i			= 0;
+
+	/* tags */
+	gboolean	tag_bold	= FALSE;
+	gboolean	tag_under	= FALSE;
+	gboolean	tag_italic	= FALSE;
+
+#ifdef MXIT_DEBUG_MARKUP
+	purple_debug_info( MXIT_PLUGIN_ID, "Markup RX (original): '%s'\n", message );
+#endif
+
+
+	/*
+	 * supported MXit markup:
+	 * '*'			bold
+	 * '_'			underline
+	 * '/'			italics
+	 * '$'			highlight text
+	 * '.+' 		inc font size
+	 * '.-'			dec font size
+	 * '#XXXXXX'	foreground color
+	 * '.{XX}'		custom emoticon
+	 * '\'			escape the following character
+	 * '::'			MXit commands
+	 */
+
+
+	if ( is_mxit_chatroom_contact( mx->session, mx->from ) ) {
+		/* chatroom message, so we need to extract and skip the sender's nickname
+		 * which is embedded inside the message */
+		i = mxit_extract_chatroom_nick( mx, message, len );
+	}
+
+	/* run through the message and check for custom emoticons and markup */
+	for ( ; i < len; i++ ) {
+		switch ( message[i] ) {
+
+
+			/* mxit markup parsing */
+			case '*' :
+					if ( !( msgflags & CP_MSG_MARKUP ) ) {
+						g_string_append_c( mx->msg, message[i] );
+						break;
+					}
+
+					/* bold markup */
+					if ( !tag_bold )
+						g_string_append( mx->msg, "<b>" );
+					else
+						g_string_append( mx->msg, "</b>" );
+					tag_bold = !tag_bold;
+					break;
+			case '_' :
+					if ( !( msgflags & CP_MSG_MARKUP ) ) {
+						g_string_append_c( mx->msg, message[i] );
+						break;
+					}
+
+					/* underscore markup */
+					if ( !tag_under )
+						g_string_append( mx->msg, "<u>" );
+					else
+						g_string_append( mx->msg, "</u>" );
+					tag_under = !tag_under;
+					break;
+			case '/' :
+					if ( !( msgflags & CP_MSG_MARKUP ) ) {
+						g_string_append_c( mx->msg, message[i] );
+						break;
+					}
+
+					/* italics markup */
+					if ( !tag_italic )
+						g_string_append( mx->msg, "<i>" );
+					else
+						g_string_append( mx->msg, "</i>" );
+					tag_italic = !tag_italic;
+					break;
+			case '$' :
+					if ( !( msgflags & CP_MSG_MARKUP ) ) {
+						g_string_append_c( mx->msg, message[i] );
+						break;
+					}
+					else if ( i + 1 >= len ) {
+						/* message too short for complete link */
+						g_string_append_c( mx->msg, '$' );
+						break;
+					}
+
+					/* find the end tag */
+					ch = strstr( &message[i + 1], "$" );
+					if ( ch ) {
+						/* end found */
+						*ch = '\0';
+						mxit_add_html_link( mx, &message[i + 1], &message[i + 1] );
+						*ch = '$';
+						i += ( ch - &message[i + 1] ) + 1;
+					}
+					else {
+						g_string_append_c( mx->msg, message[i] );
+					}
+					/* highlight text */
+					break;
+			case '#' :
+					if ( !( msgflags & CP_MSG_MARKUP ) ) {
+						g_string_append_c( mx->msg, message[i] );
+						break;
+					}
+					else if ( i + COLORCODE_LEN >= len ) {
+						/* message too short for complete colour code */
+						g_string_append_c( mx->msg, '#' );
+						break;
+					}
+
+					/* foreground (text) color */
+					memcpy( tmpstr1, &message[i + 1], COLORCODE_LEN );
+					tmpstr1[ COLORCODE_LEN ] = '\0';			/* terminate string */
+					if ( strcmp( tmpstr1, "??????" ) == 0 ) {
+						/* need to reset the font */
+						g_string_append( mx->msg, "</font>" );
+						i += COLORCODE_LEN;
+					}
+					else if ( strspn( tmpstr1, "0123456789abcdefABCDEF") == COLORCODE_LEN ) {
+						/* definitely a numeric colour code */
+						g_string_append_printf( mx->msg, "<font color=\"#%s\">", tmpstr1 );
+						i += COLORCODE_LEN;
+					}
+					else {
+						/* not valid colour markup */
+						g_string_append_c( mx->msg, '#' );
+					}
+					break;
+			case '.' :
+					if ( !( msgflags & CP_MSG_EMOTICON ) ) {
+						g_string_append_c( mx->msg, message[i] );
+						break;
+					}
+					else if ( i + 1 >= len ) {
+						/* message too short */
+						g_string_append_c( mx->msg, '.' );
+						break;
+					}
+
+					switch ( message[i+1] ) {
+						case '+' :
+								/* increment text size */
+								g_string_append( mx->msg, "<font size=\"+1\">" );
+								i++;
+								break;
+						case '-' :
+								/* decrement text size */
+								g_string_append( mx->msg, "<font size=\"-1\">" );
+								i++;
+								break;
+						case '{' :
+								/* custom emoticon */
+								if ( i + 2 >= len ) {
+									/* message too short */
+									g_string_append_c( mx->msg, '.' );
+									break;
+								}
+
+								parse_emoticon_str( &message[i+2], tmpstr1 );
+								if ( tmpstr1[0] != '\0' ) {
+									mx->got_img = TRUE;
+
+									if ( g_hash_table_lookup( mx->session->iimages, tmpstr1 ) ) {
+										/* emoticon found in the cache, so we do not have to request it from the WAPsite */
+									}
+									else {
+										/* request emoticon from the WAPsite */
+										mx->img_count++;
+										emoticon_request( mx, tmpstr1 );
+									}
+
+									g_string_append_printf( mx->msg, MXIT_II_TAG"%s>", tmpstr1 );
+									i += strlen( tmpstr1 ) + 2;
+								}
+								else
+									g_string_append_c( mx->msg, '.' );
+
+								break;
+						default :
+								g_string_append_c( mx->msg, '.' );
+								break;
+					}
+					break;
+			case '\\' :
+					if ( i + 1 >= len ) {
+						/* message too short for an escaped character */
+						g_string_append_c( mx->msg, '\\' );
+					}
+					else {
+						/* ignore the next character, because its been escaped */
+						g_string_append_c( mx->msg, message[i + 1] );
+						i++;
+					}
+					break;
+
+
+			/* command parsing */
+			case ':' :
+					if ( i + 1 >= len ) {
+						/* message too short */
+						g_string_append_c( mx->msg, ':' );
+						break;
+					}
+
+					if ( message[i+1] == '@' ) {
+						/* this is a vibe! */
+						int		size;
+
+						if ( i + 2 >= len ) {
+							/* message too short */
+							g_string_append_c( mx->msg, message[i] );
+							break;
+						}
+
+						size = mxit_parse_vibe( mx, &message[i] );
+						if ( size == 0 )
+							g_string_append_c( mx->msg, message[i] );
+						else
+							i += size;
+					}
+					else if ( msgtype != CP_MSGTYPE_COMMAND ) {
+						/* this is not a command message */
+						g_string_append_c( mx->msg, message[i] );
+					}
+					else if ( message[i+1] == ':' ) {
+						/* parse out the command */
+						int		size;
+
+						size = mxit_parse_command( mx, &message[i] );
+						if ( size == 0 )
+							g_string_append_c( mx->msg, ':' );
+						else
+							i += size;
+					}
+					else {
+						g_string_append_c( mx->msg, ':' );
+					}
+					break;
+
+
+			/* these aren't MXit markup, but are interpreted by libPurple */
+			case '<' :
+					g_string_append( mx->msg, "&lt;" );
+					break;
+			case '>' :
+					g_string_append( mx->msg, "&gt;" );
+					break;
+			case '&' :
+					g_string_append( mx->msg, "&amp;" );
+					break;
+			case '"' :
+					g_string_append( mx->msg, "&quot;" );
+					break;
+
+			default :
+					/* text */
+					g_string_append_c( mx->msg, message[i] );
+					break;
+		}
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Insert an inline image command.
+ *
+ *  @param mx				The message text as processed so far.
+ *  @oaram id				The imgstore ID of the inline image.
+ */
+static void inline_image_add( GString* mx, int id )
+{
+	PurpleStoredImage *image;
+	gconstpointer img_data;
+	gsize img_size;	
+	gchar* enc;
+
+	image = purple_imgstore_find_by_id( id );
+	if ( image == NULL )
+		return;
+
+	img_data = purple_imgstore_get_data( image );
+	img_size = purple_imgstore_get_size( image );
+
+	enc = purple_base64_encode( img_data, img_size );
+
+	g_string_append( mx, "::op=img|dat=" );
+	g_string_append( mx, enc );
+	g_string_append_c( mx, ':' );
+
+	g_free( enc );
+}
+
+
+/*------------------------------------------------------------------------
+ * Convert libpurple (HTML) markup to MXit protocol markup (for sending to MXit).
+ * Any MXit markup codes in the original message also need to be escaped.
+ *
+ *  @param message			The message text containing libPurple (HTML) markup
+ *  @return					The message text containing MXit markup
+ */
+char* mxit_convert_markup_tx( const char* message, int* msgtype )
+{
+	GString*			mx;
+	struct tag*			tag;
+	GList*				entry;
+	GList*				tagstack	= NULL;
+	char*				reply;
+	char				color[8];
+	int					len			= strlen ( message );
+	int					i;
+
+#ifdef MXIT_DEBUG_MARKUP
+	purple_debug_info( MXIT_PLUGIN_ID, "Markup TX (original): '%s'\n", message );
+#endif
+
+	/*
+	 * libPurple uses the following HTML markup codes:
+	 *   Bold:			<b>...</b>
+	 *   Italics:		<i>...</i>
+	 *   Underline:		<u>...</u>
+	 *   Strikethrough:	<s>...</s>					(NO MXIT SUPPORT)
+	 *   Font size:		<font size="">...</font>
+	 *   Font type:		<font face="">...</font>	(NO MXIT SUPPORT)
+	 *   Font colour:	<font color=#">...</font>
+	 *   Links:			<a href="">...</a>
+	 *   Newline:		<br>
+	 *   Inline image:  <IMG ID="">
+	 * The following characters are also encoded:
+	 *   &amp;  &quot;  &lt;  &gt;
+	 */
+
+	/* new message data */
+	mx = g_string_sized_new( len );
+
+	/* run through the message and check for HTML markup */
+	for ( i = 0; i < len; i++ ) {
+
+		switch ( message[i] ) {
+			case '<' :
+				if ( purple_str_has_prefix( &message[i], "<b>" ) || purple_str_has_prefix( &message[i], "</b>" ) ) {
+					/* bold */
+					g_string_append_c( mx, '*' );
+				}
+				else if ( purple_str_has_prefix( &message[i], "<i>" ) || purple_str_has_prefix( &message[i], "</i>" ) ) {
+					/* italics */
+					g_string_append_c( mx, '/' );
+				}
+				else if ( purple_str_has_prefix( &message[i], "<u>" ) || purple_str_has_prefix( &message[i], "</u>" ) ) {
+					/* underline */
+					g_string_append_c( mx, '_' );
+				}
+				else if ( purple_str_has_prefix( &message[i], "<br>" ) ) {
+					/* newline */
+					g_string_append_c( mx, '\n' );
+				}
+				else if ( purple_str_has_prefix( &message[i], "<font size=" ) ) {
+					/* font size */
+					tag = g_new0( struct tag, 1 );
+					tag->type = MXIT_TAG_SIZE;
+					tagstack = g_list_prepend( tagstack, tag );
+					// TODO: implement size control
+				}
+				else if ( purple_str_has_prefix( &message[i], "<font color=" ) ) {
+					/* font colour */
+					tag = g_new0( struct tag, 1 );
+					tag->type = MXIT_TAG_COLOR;
+					tagstack = g_list_append( tagstack, tag );
+					memset( color, 0x00, sizeof( color ) );
+					memcpy( color, &message[i + 13], 7 );
+					g_string_append( mx, color );
+				}
+				else if ( purple_str_has_prefix( &message[i], "</font>" ) ) {
+					/* end of font tag */
+					entry = g_list_last( tagstack );
+					if ( entry ) {
+						tag = entry->data;
+						if ( tag->type == MXIT_TAG_COLOR ) {
+							/* font color reset */
+							g_string_append( mx, "#??????" );
+						}
+						else if ( tag->type == MXIT_TAG_SIZE ) {
+							/* font size */
+							// TODO: implement size control
+						}
+						tagstack = g_list_remove( tagstack, tag );
+						g_free( tag );
+					}
+				}
+				else if ( purple_str_has_prefix( &message[i], "<IMG ID=" ) ) {
+					/* inline image */
+					int imgid;
+
+					if ( sscanf( &message[i+9], "%i", &imgid ) ) {
+						inline_image_add( mx, imgid );
+						*msgtype = CP_MSGTYPE_COMMAND;		/* inline image must be sent as a MXit command */
+					}
+				}
+
+				/* skip to end of tag ('>') */
+				for ( i++; ( i < len ) && ( message[i] != '>' ) ; i++ );
+			
+				break;
+
+			case '*' :	/* MXit bold */
+			case '_' :	/* MXit underline */
+			case '/' :	/* MXit italic */
+			case '#' :	/* MXit font color */
+			case '$' :	/* MXit highlight text */
+			case '\\' :	/* MXit escape backslash */
+				g_string_append( mx, "\\" );				/* escape character */
+				g_string_append_c( mx, message[i] );		/* character to escape */
+				break;
+
+			default:
+				g_string_append_c( mx, message[i] );
+				break;
+		}
+	}
+
+	/* unescape HTML entities to their literal characters (reference: "libpurple/utils.h") */
+	reply = purple_unescape_html( mx->str );
+
+	g_string_free( mx, TRUE );
+
+#ifdef MXIT_DEBUG_MARKUP
+	purple_debug_info( MXIT_PLUGIN_ID, "Markup TX (converted): '%s'\n", reply );
+#endif
+
+	return reply;
+}
+
+
+/*------------------------------------------------------------------------
+ * Free an emoticon entry.
+ *
+ *  @param key				MXit emoticon ID
+ *  @param value			Imagestore ID for emoticon
+ *  @param user_data		NULL (unused)
+ *  @return					TRUE
+ */
+static gboolean emoticon_entry_free( gpointer key, gpointer value, gpointer user_data )
+{
+	int* imgid = value;
+
+	/* key is a string */
+	g_free( key );
+
+	/* value is 'id' in imagestore */
+	purple_imgstore_unref_by_id( *imgid );
+	g_free( value );
+
+	return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Free all entries in the emoticon cache.
+ *
+ *  @param session			The MXit session object
+ */
+void mxit_free_emoticon_cache( struct MXitSession* session )
+{
+	g_hash_table_foreach_remove( session->iimages, emoticon_entry_free, NULL );
+	g_hash_table_destroy ( session->iimages );
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/markup.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,40 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- convert between MXit and libPurple markup --
+ *
+ *				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
+ */
+
+#ifndef		_MXIT_MARKUP_H_
+#define		_MXIT_MARKUP_H_
+
+#define		MXIT_II_TAG				"<MXII="			/* inline image placeholder string */
+
+
+void mxit_parse_markup( struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags );
+char* mxit_convert_markup_tx( const char* message, int* msgtype );
+void mxit_add_html_link( struct RXMsgData* mx, const char* linkname, const char* displayname );
+void mxit_show_message( struct RXMsgData* mx );
+
+void mxit_free_emoticon_cache( struct MXitSession* session );
+
+
+#endif		/* _MXIT_MARKUP_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/multimx.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,597 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *						-- MultiMx GroupChat --
+ *
+ *				Andrew Victor	<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 <string.h>
+#include <errno.h>
+
+#include "purple.h"
+#include "prpl.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "multimx.h"
+#include "markup.h"
+
+
+#if 0
+static void multimx_dump(struct multimx* multimx)
+{
+	purple_debug_info(MXIT_PLUGIN_ID, "MultiMX:\n");
+	purple_debug_info(MXIT_PLUGIN_ID, "  Chat ID: %i\n", multimx->chatid);
+	purple_debug_info(MXIT_PLUGIN_ID, "  Username: %s\n", multimx->roomid);
+	purple_debug_info(MXIT_PLUGIN_ID, "  Alias: %s\n", multimx->roomname);
+	purple_debug_info(MXIT_PLUGIN_ID, "  State: %i\n", multimx->state);
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * Find a MultiMx session based on libpurple chatID.
+ *
+ *  @param session		The MXit session object
+ *  @param id			The libpurple group-chat ID
+ *  @return				The MultiMX room object (or NULL if not found)
+ */
+static struct multimx* find_room_by_id(struct MXitSession* session, int id)
+{
+	GList* x = session->rooms;
+
+	while (x != NULL) {
+		struct multimx* multimx = (struct multimx *) x->data;
+
+		if (multimx->chatid == id)
+			return multimx;
+
+		x = g_list_next(x);
+	}
+
+	return NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Find a MultiMx session based on Alias
+ *
+ *  @param session		The MXit session object
+ *  @param roomname		The UI room-name 
+ *  @return				The MultiMX room object (or NULL if not found)
+ */
+static struct multimx* find_room_by_alias(struct MXitSession* session, const char* roomname)
+{
+	GList* x = session->rooms;
+
+	while (x != NULL) {
+		struct multimx* multimx = (struct multimx *) x->data;
+
+		if (!strcmp(multimx->roomname, roomname))
+			return multimx;
+
+		x = g_list_next(x);
+	}
+
+	return NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Find a MultiMx session based on Username (MXit RoomId)
+ *
+ *  @param session		The MXit session object
+ *  @param username		The MXit RoomID (MultiMX contact username)
+ *  @return				The MultiMX room object (or NULL if not found)
+ */
+static struct multimx* find_room_by_username(struct MXitSession* session, const char* username)
+{
+	GList* x = session->rooms;
+
+	while (x != NULL) {
+		struct multimx* multimx = (struct multimx *) x->data;
+
+		if (!strcmp(multimx->roomid, username))
+			return multimx;
+
+		x = g_list_next(x);
+	}
+
+	return NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Create a GroupChat room, and add to list of rooms.
+ *
+ *  @param session		The MXit session object
+ *  @param roomid		The MXit RoomID (MultiMX contact username)
+ *  @param roomname		The UI room-name
+ *  @param state		The initial state of the room (see multimx.h)
+ *  @return				The MultiMX room object
+ */
+static struct multimx* room_create(struct MXitSession* session, const char* roomid, const char* roomname, short state)
+{
+	struct multimx* multimx = NULL;
+	static int groupchatID = 1;
+
+	purple_debug_info(MXIT_PLUGIN_ID, "Groupchat create - roomid='%s' roomname='%s'\n", roomid, roomname);
+
+	/* Create a new GroupChat */
+	multimx = g_new0(struct multimx, 1);
+
+	/* Initialize groupchat */
+	g_strlcpy(multimx->roomid, roomid, sizeof(multimx->roomid));
+	g_strlcpy(multimx->roomname, roomname, sizeof(multimx->roomname));
+	multimx->chatid = groupchatID++;
+	multimx->state = state;
+
+	/* Add to GroupChat list */
+	session->rooms = g_list_append(session->rooms, multimx);
+
+	return multimx;
+}
+
+
+/*------------------------------------------------------------------------
+ * Free the Groupchat room.
+ *
+ *  @param session		The MXit session object
+ *  @param multimx		The MultiMX room object to deallocate
+ */
+static void room_remove(struct MXitSession* session, struct multimx* multimx)
+{
+	/* Remove from GroupChat list */
+	session->rooms = g_list_remove(session->rooms, multimx);
+
+	/* Deallocate it */
+	free (multimx);
+	multimx = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Another user has join the GroupChat, add them to the member-list.
+ *
+ *  @param session		The MXit session object
+ *  @param multimx		The MultiMX room object
+ *  @param nickname		The nickname of the user who joined the room
+ */
+static void member_added(struct MXitSession* session, struct multimx* multimx, const char* nickname)
+{
+	PurpleConversation *convo;
+
+	purple_debug_info(MXIT_PLUGIN_ID, "member_added: '%s'\n", nickname);
+
+	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc);
+	if (convo == NULL) {
+		purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname);
+		return;
+	}
+
+	purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo), nickname, NULL, PURPLE_CBFLAGS_NONE, TRUE);
+}
+
+
+/*------------------------------------------------------------------------
+ * Another user has left the GroupChat, remove them from the member-list.
+ *
+ *  @param session		The MXit session object
+ *  @param multimx		The MultiMX room object
+ *  @param nickname		The nickname of the user who left the room
+ */
+static void member_removed(struct MXitSession* session, struct multimx* multimx, const char* nickname)
+{
+	PurpleConversation *convo;
+
+	purple_debug_info(MXIT_PLUGIN_ID, "member_removed: '%s'\n", nickname);
+
+	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc);
+	if (convo == NULL) {
+		purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname);
+		return;
+	}
+
+	purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo), nickname, NULL);
+}
+
+
+/*------------------------------------------------------------------------
+ * Update the full GroupChat member list.
+ *
+ *  @param session		The MXit session object
+ *  @param multimx		The MultiMX room object
+ *  @param data			The nicknames of the users in the room (separated by \n)
+ */
+static void member_update(struct MXitSession* session, struct multimx* multimx, char* data)
+{
+	PurpleConversation *convo;
+	gchar** userlist;
+	int i = 0;
+
+	purple_debug_info(MXIT_PLUGIN_ID, "member_update: '%s'\n", data);
+
+	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, multimx->roomname, session->acc);
+	if (convo == NULL) {
+		purple_debug_error(MXIT_PLUGIN_ID, "Conversation '%s' not found\n", multimx->roomname);
+		return;
+	}
+
+	/* Clear list */
+	purple_conv_chat_clear_users(PURPLE_CONV_CHAT(convo));
+
+	/* Add each member */
+	data = g_strstrip(data);				/* string leading & trailing whitespace */
+	userlist = g_strsplit(data, "\n", 0);	/* tokenize string */
+	while (userlist[i] != NULL) {
+		purple_debug_info(MXIT_PLUGIN_ID, "member_update - adding: '%s'\n", userlist[i]);
+		purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo), userlist[i], NULL, PURPLE_CBFLAGS_NONE, FALSE);
+		i++;
+	}
+	g_strfreev(userlist);
+}
+
+
+/* -------------------------------------------------------------------------------------------------
+ * Calls from MXit Protocol layer
+ * ------------------------------------------------------------------------------------------------- */
+
+/*------------------------------------------------------------------------
+ * Received a Subscription Request to a MultiMX room.
+ *
+ *  @param session		The MXit session object
+ *  @param contact		The invited MultiMX room's contact information
+ *  @param creator		The nickname of the room's creator / invitor
+ */
+void multimx_invite(struct MXitSession* session, struct contact* contact, const char* creator)
+{
+	GHashTable *components;
+	struct multimx* multimx = NULL;
+
+	purple_debug_info(MXIT_PLUGIN_ID, "Groupchat invite to '%s' by '%s'\n", contact->alias, creator);
+
+	/* Create a new room */
+	multimx = room_create(session, contact->username, contact->alias, STATE_INVITED);
+
+	components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+	g_hash_table_insert(components, g_strdup("room"), g_strdup(contact->alias));
+
+	/* Call libpurple - will trigger either 'mxit_chat_join' or 'mxit_chat_reject' */
+	serv_got_chat_invite(session->con, contact->alias, creator, NULL, components);
+}
+
+
+/*------------------------------------------------------------------------
+ * MultiMX room has been added to the roster.
+ *
+ *  @param session		The MXit session object
+ *  @param contact		The MultiMX room's contact information
+ */
+void multimx_created(struct MXitSession* session, struct contact* contact)
+{
+	PurpleConnection *gc = session->con;
+	struct multimx* multimx = NULL;
+
+	purple_debug_info(MXIT_PLUGIN_ID, "Groupchat '%s' created as '%s'\n", contact->alias, contact->username);
+
+	/* Find matching MultiMX group */
+	multimx = find_room_by_username(session, contact->username);
+	if (multimx == NULL) {
+		multimx = room_create(session, contact->username, contact->alias, TRUE);
+		}
+	else if (multimx->state == STATE_INVITED) {
+		/* After successfully accepting an invitation */
+		multimx->state = STATE_JOINED;
+	}
+
+	/* Call libpurple - will trigger 'mxit_chat_join' */
+	serv_got_joined_chat(gc, multimx->chatid, multimx->roomname);
+
+	/* Send ".list" command to GroupChat server to retrieve current member-list */
+	mxit_send_message(session, multimx->roomid, ".list", FALSE);
+}
+
+
+/*------------------------------------------------------------------------
+ * Is this username a MultiMX contact?
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the contact
+ *  @return				TRUE if this contacts matches the RoomID of a MultiMX room.
+ */
+gboolean is_multimx_contact(struct MXitSession* session, const char* username)
+{
+	/* Check for username in list of open rooms */
+	return (find_room_by_username(session, username) != NULL);
+}
+
+
+/*------------------------------------------------------------------------
+ * Received a message from a MultiMX room.
+ *
+ */
+void multimx_message_received(struct RXMsgData* mx, char* msg, int msglen, short msgtype, int msgflags)
+{
+	struct multimx* multimx = NULL;
+
+	purple_debug_info(MXIT_PLUGIN_ID, "Groupchat message received: %s\n", msg);
+
+	/* Find matching multimx group */
+	multimx = find_room_by_username(mx->session, mx->from);
+	if (multimx == NULL) {
+		purple_debug_error(MXIT_PLUGIN_ID, "Groupchat '%s' not found\n", mx->from);
+		return;
+	}
+
+	/* Determine if system message or a message from a contact */
+	if (msg[0] == '<') {
+		/* Message contains embedded nickname - must be from contact */
+		unsigned int i;
+
+		for (i = 1; i < strlen(msg); i++) {		/* search for end of nickname */
+			if (msg[i] == '>') {
+				msg[i] = '\0';
+				g_free(mx->from);
+				mx->from = g_strdup(&msg[1]);
+				msg = &msg[i+2];		/* skip '>' and newline */
+				break;
+			}
+		}
+
+		/* now do markup processing on the message */
+		mx->chatid = multimx->chatid;
+		mxit_parse_markup(mx, msg, strlen(msg), msgtype, msgflags);
+	}
+	else {
+		/* Must be a service message */
+		char* ofs;
+
+		/* Determine if somebody has joined or left - update member-list */
+		if ((ofs = strstr(msg, " has joined")) != NULL) {
+			/* Somebody has joined */
+			*ofs = '\0';
+			member_added(mx->session, multimx, msg);
+			mx->processed = TRUE;
+		}
+		else if ((ofs = strstr(msg, " has left")) != NULL) {
+			/* Somebody has left */
+			*ofs = '\0';
+			member_removed(mx->session, multimx, msg);
+			mx->processed = TRUE;
+		}
+		else if (g_str_has_prefix(msg, "The following users are in this MultiMx:") == TRUE) {
+			member_update(mx->session, multimx, msg + strlen("The following users are in this MultiMx:") + 1);
+			mx->processed = TRUE;
+		}
+		else {
+			/* Display server message in chat window */
+			serv_got_chat_in(mx->session->con, multimx->chatid, "MXit", PURPLE_MESSAGE_SYSTEM, msg, mx->timestamp);
+			mx->processed = TRUE;
+		}
+	}
+}
+
+
+
+/* -------------------------------------------------------------------------------------------------
+ * Callbacks from libpurple
+ * ------------------------------------------------------------------------------------------------- */
+
+/*------------------------------------------------------------------------
+ * User has selected "Add Chat" from the main menu.
+ *
+ *  @param gc			The connection object
+ *  @return				A list of chat configuration values
+ */
+GList* mxit_chat_info(PurpleConnection *gc)
+{
+	GList *m = NULL;
+	struct proto_chat_entry *pce;
+
+	/* Configuration option: Room Name */
+	pce = g_new0(struct proto_chat_entry, 1);
+	pce->label = "_Room Name:";
+	pce->identifier = "room";
+	pce->required = TRUE;
+	m = g_list_append(m, pce);
+
+	return m;
+}
+
+
+/*------------------------------------------------------------------------
+ * User has joined a chatroom, either because they are creating it or they
+ * accepted an invite.
+ *
+ *  @param gc			The connection object
+ *  @param components	The list of chat configuration values
+ */
+void mxit_chat_join(PurpleConnection *gc, GHashTable *components)
+{
+	struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+	const char* roomname = NULL;
+	struct multimx* multimx = NULL;
+
+	purple_debug_info(MXIT_PLUGIN_ID, "mxit_chat_join\n");
+
+	/* Determine if groupchat already exists */
+	roomname = g_hash_table_lookup(components, "room");
+	multimx = find_room_by_alias(session, roomname);
+
+	if (multimx != NULL) {
+		/* The room information already exists */
+
+		if (multimx->state == STATE_INVITED) {
+			/* Invite is pending */
+			purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i accept sent\n", multimx->chatid);
+
+			/* Send Subscription Accept to MXit */
+			mxit_send_allow_sub(session, multimx->roomid, multimx->roomname);
+		}
+		else {
+			/* Join existing room */
+			purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i rejoined\n", multimx->chatid);
+
+			serv_got_joined_chat(gc, multimx->chatid, multimx->roomname);
+		}
+	}
+	else {
+		/* Send Groupchat Create to MXit */
+		mxit_send_groupchat_create(session, roomname, 0, NULL);
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * User has rejected an invite to join a MultiMX room.
+ *
+ *  @param gc			The connection object
+ *  @param components	The list of chat configuration values
+ */
+void mxit_chat_reject(PurpleConnection *gc, GHashTable* components)
+{
+	struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+	const char* roomname = NULL;
+	struct multimx* multimx = NULL;
+
+	purple_debug_info(MXIT_PLUGIN_ID, "mxit_chat_reject\n");
+
+	roomname = g_hash_table_lookup(components, "room");
+	multimx = find_room_by_alias(session, roomname);
+	if (multimx == NULL) {
+		purple_debug_error(MXIT_PLUGIN_ID, "Groupchat '%s' not found\n", roomname);
+		return;
+	}
+
+	/* Send Subscription Reject to MXit */
+	mxit_send_deny_sub(session, multimx->roomid);
+
+	/* Remove from our list of rooms */
+	room_remove(session, multimx);
+}
+
+
+/*------------------------------------------------------------------------
+ * Return name of chatroom (on mouse hover)
+ *
+ *  @param components	The list of chat configuration values.
+ *  @return				The name of the chat room
+ */
+char* mxit_chat_name(GHashTable *components)
+{
+	return g_strdup(g_hash_table_lookup(components, "room"));
+}
+
+
+/*------------------------------------------------------------------------
+ * User has selected to invite somebody to a chatroom.
+ *
+ *  @param gc			The connection object
+ *  @param id			The chat room ID
+ *  @param msg			The invitation message entered by the user
+ *  @param name			The username of the person to invite
+ */
+void mxit_chat_invite(PurpleConnection *gc, int id, const char *msg, const char *username)
+{
+	struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+	struct multimx* multimx = NULL;
+
+	purple_debug_info(MXIT_PLUGIN_ID, "Groupchat invite to '%s'\n", username);
+
+	/* Find matching MultiMX group */
+	multimx = find_room_by_id(session, id);
+	if (multimx == NULL) {
+		purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id);
+		return;
+	}
+
+	/* Send invite to MXit */
+	mxit_send_groupchat_invite(session, multimx->roomid, 1, &username);
+}
+
+
+/*------------------------------------------------------------------------
+ * User as closed the chat window, and the chatroom is not marked as persistent.
+ *
+ *  @param gc			The connection object
+ *  @param id			The chat room ID
+ */
+void mxit_chat_leave(PurpleConnection *gc, int id)
+{
+	struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+	struct multimx* multimx = NULL;
+
+	purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i leave\n", id);
+
+	/* Find matching multimx group */
+	multimx = find_room_by_id(session, id);
+	if (multimx == NULL) {
+		purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id);
+		return;
+	}
+
+	/* Send Remove Groupchat to MXit */
+	mxit_send_remove(session, multimx->roomid);
+
+	/* Remove from our list of rooms */
+	room_remove(session, multimx);
+}
+
+
+/*------------------------------------------------------------------------
+ * User has entered a message in a chatroom window, send it to the MXit server.
+ *
+ *  @param gc			The connection object
+ *  @param id			The chat room ID
+ *  @param message		The sent message data
+ *  @param flags		The message flags
+ *  @return				Indicates success / failure
+ */
+int mxit_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags)
+{
+	struct MXitSession* session = (struct MXitSession*) gc->proto_data;
+	struct multimx* multimx = NULL;
+	const char* nickname;
+
+	purple_debug_info(MXIT_PLUGIN_ID, "Groupchat %i message send: '%s'\n", id, message);
+
+	/* Find matching MultiMX group */
+	multimx = find_room_by_id(session, id);
+	if (multimx == NULL) {
+		purple_debug_error(MXIT_PLUGIN_ID, "Could not find groupchat %i\n", id);
+		return -1;
+	}
+
+	/* Send packet to MXit */
+	mxit_send_message(session, multimx->roomid, message, TRUE);
+	
+	/* Determine our nickname to display */
+	if (session->profile && (session->profile->nickname[0] != '\0'))		/* default is profile name (since that's what everybody else sees) */
+		 nickname = session->profile->nickname;
+	else
+		nickname = purple_account_get_alias(purple_connection_get_account(gc));		/* local alias */
+
+	/* Display message in chat window */
+	serv_got_chat_in(gc, id, nickname, flags, message, time(NULL));
+
+	return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/multimx.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,104 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *						-- MultiMx GroupChat --
+ *
+ *				Andrew Victor	<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
+ */
+
+#ifndef		_MXIT_MULTIMX_H_
+#define		_MXIT_MULTIMX_H_
+
+#include	"roster.h"
+
+
+/* GroupChat Room state */
+#define		STATE_CREATOR	0
+#define		STATE_INVITED	1
+#define		STATE_JOINED	2
+
+/*
+ * a MultiMX room
+ */
+struct multimx {
+	char	roomname[MXIT_CP_MAX_ALIAS_LEN];	/* name of the room */
+	char	roomid[MXIT_CP_MAX_JID_LEN];		/* internal JID for room */
+	int		chatid;								/* libpurple chat ID */
+	short	state;								/* state */
+};
+
+
+/*
+ * Received a Subscription Request to a MultiMX room.
+ */
+void multimx_invite(struct MXitSession* session, struct contact* contact, const char* creator);
+
+/*
+ * MultiMX room has been added to the roster.
+ */
+void multimx_created(struct MXitSession* session, struct contact* contact);
+
+/*
+ * Is this username a MultiMX contact?
+ */
+gboolean is_multimx_contact(struct MXitSession* session, const char* username);
+
+/*
+ * Received a message from a MultiMX room.
+ */
+void multimx_message_received(struct RXMsgData* mx, char* message, int len, short msgtype, int msgflags);
+
+/*
+ * User has selected "Add Chat" from the main menu.
+ */
+GList* mxit_chat_info(PurpleConnection *gc);
+
+/*
+ * User has joined a chatroom, either because they are creating it or they accepted an invite.
+ */
+void mxit_chat_join(PurpleConnection *gc, GHashTable *data);
+
+/*
+ * User has rejected an invite to join a MultiMX room.
+ */
+void mxit_chat_reject(PurpleConnection *gc, GHashTable* components);
+
+/*
+ * Return name of chatroom (on mouse hover)
+ */
+char* mxit_chat_name(GHashTable *data);
+
+/*
+ * User has selected to invite somebody to a chatroom.
+ */
+void mxit_chat_invite(PurpleConnection *gc, int id, const char *msg, const char *name);
+
+/*
+ * User as closed the chat window, and the chatroom is not marked as persistent.
+ */
+void mxit_chat_leave(PurpleConnection *gc, int id);
+
+/*
+ * User has entered a message in a chatroom window, send it to the MXit server.
+ */
+int mxit_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags);
+
+
+#endif		/* _MXIT_MULTIMX_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/mxit.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,694 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *					--  MXit libPurple plugin API --
+ *
+ *				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	<glib.h>
+#include	<stdio.h>
+#include	<string.h>
+
+#include	"purple.h"
+#include	"notify.h"
+#include	"plugin.h"
+#include	"version.h"
+
+#include	"mxit.h"
+#include	"protocol.h"
+#include	"login.h"
+#include	"roster.h"
+#include	"chunk.h"
+#include	"filexfer.h"
+#include	"actions.h"
+#include	"multimx.h"
+
+
+#ifdef	MXIT_LINK_CLICK
+
+
+/* pidgin callback function pointers for URI click interception */
+static void *(*mxit_pidgin_uri_cb)(const char *uri);
+static PurpleNotifyUiOps* mxit_nots_override_original;
+static PurpleNotifyUiOps mxit_nots_override;
+static int not_link_ref_count = 0;
+
+
+/*------------------------------------------------------------------------
+ * Handle an URI clicked on the UI
+ *
+ * @param link	the link name which has been clicked
+ */
+static void* mxit_link_click( const char* link64 )
+{
+	PurpleAccount*		account;
+	PurpleConnection*	con;
+	gchar**				parts	= NULL;
+	gchar*				link	= NULL;
+	unsigned int		len;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_link_click (%s)\n", link64 );
+
+	if ( g_ascii_strncasecmp( link64, MXIT_LINK_PREFIX, strlen( MXIT_LINK_PREFIX ) ) != 0 ) {
+		/* this is not for us */
+		goto skip;
+	}
+
+	/* decode the base64 payload */
+	link = (gchar*) purple_base64_decode( link64 + strlen( MXIT_LINK_PREFIX ), &len );
+	purple_debug_info( MXIT_PLUGIN_ID, "Clicked Link: '%s'\n", link );
+
+	parts = g_strsplit( link, "|", 5 );
+
+	/* check if this is a valid mxit link */
+	if ( ( !parts ) || ( !parts[0] ) || ( !parts[1] ) || ( !parts[2] ) || ( !parts[3] ) || ( !parts[4] ) ) {
+		/* this is not for us */
+		goto skip;
+	}
+	else if ( g_ascii_strcasecmp( parts[0], MXIT_LINK_KEY ) != 0 ) {
+		/* this is not for us */
+		goto skip;
+	}
+
+	/* find the account */
+	account = purple_accounts_find( parts[1], parts[2] );
+	if ( !account )
+		goto skip;
+	con = purple_account_get_connection( account );
+
+	/* send click message back to MXit */
+	mxit_send_message( con->proto_data, parts[3], parts[4], FALSE );
+
+	g_free( link );
+	link = NULL;
+	g_strfreev( parts );
+	parts = NULL;
+
+	return (void*) link64;
+
+skip:
+	/* this is not an internal mxit link */
+
+	if ( link )
+		g_free( link );
+	link = NULL;
+
+	if ( parts )
+		g_strfreev( parts );
+	parts = NULL;
+
+	if ( mxit_pidgin_uri_cb )
+		return mxit_pidgin_uri_cb( link64 );
+	else
+		return (void*) link64;
+}
+
+
+/*------------------------------------------------------------------------
+ * Register MXit to receive URI click notifications from the UI
+ */
+void mxit_register_uri_handler()
+{
+	not_link_ref_count++;
+	if ( not_link_ref_count == 1 ) {
+		/* make copy of notifications */
+		mxit_nots_override_original = purple_notify_get_ui_ops();
+		memcpy( &mxit_nots_override, mxit_nots_override_original, sizeof( PurpleNotifyUiOps ) );
+
+		/* save previously configured callback function pointer */
+		mxit_pidgin_uri_cb = mxit_nots_override.notify_uri;
+
+		/* override the URI function call with MXit's own one */
+		mxit_nots_override.notify_uri = mxit_link_click;
+		purple_notify_set_ui_ops( &mxit_nots_override );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Unegister MXit from receiving URI click notifications from the UI
+ */
+static void mxit_unregister_uri_handler()
+{
+	not_link_ref_count--;
+	if ( not_link_ref_count == 0 ) {
+		/* restore the notifications to its original state */
+		purple_notify_set_ui_ops( mxit_nots_override_original );
+	}
+}
+
+#endif
+
+
+/*------------------------------------------------------------------------
+ * This gets called when a new chat conversation is opened by the user
+ *
+ *  @param conv				The conversation object
+ *  @param session			The MXit session object
+ */
+static void mxit_cb_chat_created( PurpleConversation* conv, struct MXitSession* session )
+{
+	PurpleConnection*	gc;
+	struct contact*		contact;
+	PurpleBuddy*		buddy;
+	const char*			who;
+
+	gc = purple_conversation_get_gc( conv );
+	if ( session->con != gc ) {
+		/* not our conversation */
+		return;
+	}
+	else if ( purple_conversation_get_type( conv ) != PURPLE_CONV_TYPE_IM ) {
+		/* wrong type of conversation */
+		return;
+	}
+
+	/* get the contact name */
+	who = purple_conversation_get_name( conv );
+	if ( !who )
+		return;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "Conversation started with '%s'\n", who );
+
+	/* find the buddy object */
+	buddy = purple_find_buddy( session->acc, who );
+	if ( ( !buddy ) || ( !buddy->proto_data ) )
+		return;
+
+	/* we ignore all conversations with which we have chatted with in this session */
+	if ( find_active_chat( session->active_chats, who ) )
+		return;
+
+	/* determite if this buddy is a MXit service */
+	contact = buddy->proto_data;
+	switch ( contact->type ) {
+		case MXIT_TYPE_BOT :
+		case MXIT_TYPE_CHATROOM :
+		case MXIT_TYPE_GALLERY :
+		case MXIT_TYPE_INFO :
+				serv_got_im( session->con, who, "<font color=\"#999999\">Loading menu...</font>\n", PURPLE_MESSAGE_NOTIFY, time( NULL ) );
+				mxit_send_message( session, who, " ", FALSE );
+		default :
+				break;
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Enable some signals to handled by our plugin
+ *
+ *  @param session			The MXit session object
+ */
+void mxit_enable_signals( struct MXitSession* session )
+{
+	/* enable the signal when a new conversation is opened by the user */
+	purple_signal_connect_priority( purple_conversations_get_handle(), "conversation-created", session, PURPLE_CALLBACK( mxit_cb_chat_created ),
+			session, PURPLE_SIGNAL_PRIORITY_HIGHEST );
+}
+
+
+/*------------------------------------------------------------------------
+ * Disable some signals handled by our plugin
+ *
+ *  @param session			The MXit session object
+ */
+static void mxit_disable_signals( struct MXitSession* session )
+{
+	/* disable the signal when a new conversation is opened by the user */
+	purple_signal_disconnect( purple_conversations_get_handle(), "conversation-created", session, PURPLE_CALLBACK( mxit_cb_chat_created ) );
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the base icon name.
+ *
+ *  @param account	The MXit account object
+ *  @param buddy	The buddy
+ *  @return			The icon name (excluding extension)
+ */
+static const char* mxit_list_icon( PurpleAccount* account, PurpleBuddy* buddy )
+{
+	return "mxit";
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the emblem icon name.
+ *
+ *  @param buddy	The buddy
+ *  @return			The icon name (excluding extension)
+ */
+static const char* mxit_list_emblem( PurpleBuddy* buddy )
+{
+	struct contact*	contact = buddy->proto_data;
+
+	if ( !contact )
+		return NULL;
+
+	switch ( contact-> type ) {
+		case MXIT_TYPE_JABBER :			/* external contacts via MXit */
+		case MXIT_TYPE_MSN :
+		case MXIT_TYPE_YAHOO :
+		case MXIT_TYPE_ICQ :
+		case MXIT_TYPE_AIM :
+		case MXIT_TYPE_QQ :
+		case MXIT_TYPE_WV :
+			return "external";
+
+		case MXIT_TYPE_BOT :			/* MXit services */
+		case MXIT_TYPE_GALLERY :
+		case MXIT_TYPE_INFO :
+			return "bot";
+
+		case MXIT_TYPE_CHATROOM :		/* MXit group chat services */
+		case MXIT_TYPE_MULTIMX :
+		default:
+			return NULL;
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Return short string representing buddy's status for display on buddy list.
+ * Returns status message (if one is set), or otherwise the mood.
+ *
+ *  @param buddy	The buddy.
+ *  @return			The status text
+ */
+char* mxit_status_text( PurpleBuddy* buddy )
+{
+	struct contact*	contact = buddy->proto_data;
+
+	if ( !contact )
+		return NULL;
+
+	if ( contact->statusMsg ) {
+		/* status message */
+		return g_strdup( contact-> statusMsg );
+	}
+	else {
+		/* mood */
+		return g_strdup( mxit_convert_mood_to_name( contact->mood ) );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Return UI tooltip information for a buddy when hovering in buddy list.
+ *
+ *  @param buddy	The buddy
+ *  @param info		The tooltip info being returned
+ *  @param full		Return full or summarized information
+ */
+static void mxit_tooltip( PurpleBuddy* buddy, PurpleNotifyUserInfo* info, gboolean full )
+{
+	struct contact*	contact = buddy->proto_data;
+
+	if ( !contact )
+		return;
+
+	/* status (reference: "libpurple/notify.h") */
+	if ( contact->presence != MXIT_PRESENCE_OFFLINE )
+		purple_notify_user_info_add_pair( info, _( "Status" ), mxit_convert_presence_to_name( contact->presence ) );
+
+	/* status message */
+	if ( contact->statusMsg )
+		purple_notify_user_info_add_pair( info, _( "Status Message" ), contact->statusMsg );
+
+	/* mood */
+	if ( contact->mood != MXIT_MOOD_NONE )
+		purple_notify_user_info_add_pair( info, _( "Mood" ), mxit_convert_mood_to_name( contact->mood ) );
+
+	/* subscription type */
+	if ( contact->subtype != 0 )
+		purple_notify_user_info_add_pair( info, _( "Subscription" ), mxit_convert_subtype_to_name( contact->subtype ) );
+
+	/* hidden number */
+	if ( contact->flags & MXIT_CFLAG_HIDDEN )
+		purple_notify_user_info_add_pair( info, _( "Hidden Number" ), "Yes" );
+}
+
+
+/*------------------------------------------------------------------------
+ * Initiate the logout sequence, close the connection and clear the session data.
+ *
+ *  @param gc	The connection object
+ */
+static void mxit_close( PurpleConnection* gc )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+
+	/* disable signals */
+	mxit_disable_signals( session );
+
+	/* close the connection */
+	mxit_close_connection( session );
+
+#ifdef		MXIT_LINK_CLICK
+	/* unregister for uri click notification */
+	mxit_unregister_uri_handler();
+#endif
+
+	purple_debug_info( MXIT_PLUGIN_ID, "Releasing the session object..\n" );
+
+	/* free the session memory */
+	g_free( session );
+	session = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a message to a contact
+ *
+ *  @param gc		The connection object
+ *  @param who		The username of the recipient
+ *  @param message	The message text
+ *  @param flags	Message flags (defined in conversation.h)
+ *  @return			Positive value (success, and echo to conversation window)
+					Zero (success, no echo)
+					Negative value (error)
+ */
+static int mxit_send_im( PurpleConnection* gc, const char* who, const char* message, PurpleMessageFlags flags )
+{
+	purple_debug_info( MXIT_PLUGIN_ID, "Sending message '%s' to buddy '%s'\n", message, who );
+
+	mxit_send_message( gc->proto_data, who, message, TRUE );
+
+	return 1;		/* echo to conversation window */
+}
+
+
+/*------------------------------------------------------------------------
+ * The user changed their current presence state.
+ *
+ *  @param account	The MXit account object
+ *  @param status	The new status (libPurple status type)
+ */
+static void mxit_set_status( PurpleAccount* account, PurpleStatus* status )
+{
+	struct MXitSession*		session =	purple_account_get_connection( account )->proto_data;
+	const char*				statusid;
+	int						presence;
+	char*					statusmsg1;
+	char*					statusmsg2;
+
+	/* get the status id (reference: "libpurple/status.h") */
+	statusid = purple_status_get_id( status );
+
+	/* convert the purple status to a mxit status */
+	presence = mxit_convert_presence( statusid );
+	if ( presence < 0 ) {
+		/* error, status not found */
+		purple_debug_info( MXIT_PLUGIN_ID, "Presence status NOT found! (id = %s)\n", statusid );
+		return;
+	}
+
+	statusmsg1 = purple_markup_strip_html( purple_status_get_attr_string( status, "message" ) );
+	statusmsg2 = g_strndup( statusmsg1, CP_MAX_STATUS_MSG );
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_set_status: '%s'\n", statusmsg2 );
+
+	/* update presence state */
+	mxit_send_presence( session, presence, statusmsg2 );
+
+	g_free( statusmsg1 );
+	g_free( statusmsg2 );
+}
+
+
+/*------------------------------------------------------------------------
+ * MXit supports messages to offline contacts.
+ *
+ *  @param buddy	The buddy
+ */
+static gboolean mxit_offline_message( const PurpleBuddy *buddy )
+{
+	return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Free the resources used to store a buddy.
+ *
+ *  @param buddy	The buddy
+ */
+static void mxit_free_buddy( PurpleBuddy* buddy )
+{
+	struct contact*		contact;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_free_buddy\n" );
+
+	contact = buddy->proto_data;
+	if ( contact ) {
+		if ( contact->statusMsg )
+			g_free( contact->statusMsg );
+		if ( contact->avatarId )
+			g_free( contact->avatarId );
+		g_free( contact );
+	}
+	buddy->proto_data = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Periodic task called every KEEPALIVE_INTERVAL (30 sec) to to maintain
+ * idle connections, timeouts and the transmission queue to the MXit server.
+ *
+ *  @param gc		The connection object
+ */
+static void mxit_keepalive( PurpleConnection *gc )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+
+	/* if not logged in, there is nothing to do */
+	if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) )
+		return;
+
+	/* pinging is only for socket connections (HTTP does polling) */
+	if ( session->http )
+		return;
+
+	if ( session->last_tx <= time( NULL ) - MXIT_PING_INTERVAL ) {
+		/*
+		 * this connection has been idle for too long, better ping
+		 * the server before it kills our connection.
+		 */
+		mxit_send_ping( session );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Set or clear our Buddy icon.
+ *
+ *  @param gc		The connection object
+ *  @param img		The buddy icon data
+ */
+static void mxit_set_buddy_icon( PurpleConnection *gc, PurpleStoredImage *img )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+
+	if ( img == NULL )
+		mxit_set_avatar( session, NULL, 0 );
+	else
+		mxit_set_avatar( session, purple_imgstore_get_data( img ), purple_imgstore_get_size( img ) );
+}
+
+
+/*------------------------------------------------------------------------
+ * Request profile information for another MXit contact.
+ *
+ *  @param gc		The connection object
+ *  @param who		The username of the contact.		
+ */
+static void mxit_get_info( PurpleConnection *gc, const char *who )
+{
+	struct MXitSession*		session			= (struct MXitSession*) gc->proto_data;
+	const char*				profilelist[]	= { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME,
+												CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL };
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_info: '%s'\n", who );
+
+
+	/* send profile request */
+	mxit_send_extprofile_request( session, who, ARRAY_SIZE( profilelist ), profilelist );
+}
+
+
+/*------------------------------------------------------------------------
+ * Return a list of labels to be used by Pidgin for assisting the user.
+ */
+static GHashTable* mxit_get_text_table( PurpleAccount* acc )
+{
+	GHashTable* table;
+
+	table = g_hash_table_new( g_str_hash, g_str_equal );
+
+	g_hash_table_insert( table, "login_label", _( "Your Mobile Number..." ) );
+
+	return table;
+}
+
+/*========================================================================================================================*/
+
+static PurplePluginProtocolInfo proto_info = {
+	OPT_PROTO_REGISTER_NOSCREENNAME | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_IM_IMAGE,			/* options */
+	NULL,					/* user_splits */
+	NULL,					/* protocol_options */
+	{						/* icon_spec */
+		"png",												/* format */
+		32, 32,												/* min width & height */
+		MXIT_AVATAR_SIZE,									/* max width */
+		MXIT_AVATAR_SIZE,									/* max height */
+		100000,												/* max filezize */
+		PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY	/* scaling rules */
+	},
+	mxit_list_icon,			/* list_icon */
+	mxit_list_emblem,		/* list_emblem */
+	mxit_status_text,		/* status_text */
+	mxit_tooltip,			/* tooltip_text */
+	mxit_status_types,		/* status types				[roster.c] */
+	NULL,					/* blist_node_menu */
+	mxit_chat_info,			/* chat_info				[multimx.c] */
+	NULL,					/* chat_info_defaults */
+	mxit_login,				/* login					[login.c] */
+	mxit_close,				/* close */
+	mxit_send_im,			/* send_im */
+	NULL,					/* set_info */
+	NULL,					/* send_typing */
+	mxit_get_info,			/* get_info */
+	mxit_set_status,		/* set_status */
+	NULL,					/* set_idle */
+	NULL,					/* change_passwd */
+	mxit_add_buddy,			/* add_buddy				[roster.c] */
+	NULL,					/* add_buddies */
+	mxit_remove_buddy,		/* remove_buddy				[roster.c] */
+	NULL,					/* remove_buddies */
+	NULL,					/* add_permit */
+	NULL,					/* add_deny */
+	NULL,					/* rem_permit */
+	NULL,					/* rem_deny */
+	NULL,					/* set_permit_deny */
+	mxit_chat_join,			/* join_chat				[multimx.c] */
+	mxit_chat_reject,		/* reject chat invite		[multimx.c] */
+	mxit_chat_name,			/* get_chat_name			[multimx.c] */
+	mxit_chat_invite,		/* chat_invite				[multimx.c] */
+	mxit_chat_leave,		/* chat_leave				[multimx.c] */
+	NULL,					/* chat_whisper */
+	mxit_chat_send,			/* chat_send				[multimx.c] */
+	mxit_keepalive,			/* keepalive */
+	mxit_register,			/* register_user */
+	NULL,					/* get_cb_info */
+	NULL,					/* get_cb_away */
+	mxit_buddy_alias,		/* alias_buddy				[roster.c] */
+	mxit_buddy_group,		/* group_buddy				[roster.c] */
+	mxit_rename_group,		/* rename_group				[roster.c] */
+	mxit_free_buddy,		/* buddy_free */
+	NULL,					/* convo_closed */
+	NULL,					/* normalize */
+	mxit_set_buddy_icon,	/* set_buddy_icon */
+	NULL,					/* remove_group */			// TODO: Add function to move all contacts out of this group (cmd=30 - remove group)?
+	NULL,					/* get_cb_real_name */
+	NULL,					/* set_chat_topic */
+	NULL,					/* find_blist_chat */
+	NULL,					/* roomlist_get_list */
+	NULL,					/* roomlist_cancel */
+	NULL,					/* roomlist_expand_category */
+	mxit_xfer_enabled,		/* can_receive_file			[filexfer.c] */
+	mxit_xfer_tx,			/* send_file				[filexfer.c */
+	mxit_xfer_new,			/* new_xfer					[filexfer.c] */
+	mxit_offline_message,	/* offline_message */
+	NULL,					/* whiteboard_prpl_ops */
+	NULL,					/* send_raw */
+	NULL,					/* roomlist_room_serialize */
+	NULL,					/* unregister_user */
+	NULL,					/* send_attention */
+	NULL,					/* attention_types */
+	sizeof( PurplePluginProtocolInfo ),		/* struct_size */
+	mxit_get_text_table,	/* get_account_text_table */
+	NULL,
+	NULL
+};
+
+
+static PurplePluginInfo plugin_info = {
+	PURPLE_PLUGIN_MAGIC,								/* purple magic, this must always be PURPLE_PLUGIN_MAGIC */
+	PURPLE_MAJOR_VERSION,								/* libpurple version */
+	PURPLE_MINOR_VERSION,								/* libpurple version */
+	PURPLE_PLUGIN_PROTOCOL,								/* plugin type (connecting to another network) */
+	NULL,												/* UI requirement (NULL for core plugin) */
+	0,													/* plugin flags (zero is default) */
+	NULL,												/* plugin dependencies (set this value to NULL no matter what) */
+	PURPLE_PRIORITY_DEFAULT,							/* libpurple priority */
+
+	MXIT_PLUGIN_ID,										/* plugin id (must be unique) */
+	MXIT_PLUGIN_NAME,									/* plugin name (this will be displayed in the UI) */
+	MXIT_PLUGIN_VERSION,								/* version of the plugin */
+
+	MXIT_PLUGIN_SUMMARY,								/* short summary of the plugin */
+	MXIT_PLUGIN_DESC,									/* description of the plugin (can be long) */
+	MXIT_PLUGIN_EMAIL,									/* plugin author name and email address */
+	MXIT_PLUGIN_WWW,									/* plugin website (to find new versions and reporting of bugs) */
+
+	NULL,												/* function pointer for loading the plugin */
+	NULL,												/* function pointer for unloading the plugin */
+	NULL,												/* function pointer for destroying the plugin */
+
+	NULL,												/* pointer to an UI-specific struct */
+	&proto_info,										/* pointer to either a PurplePluginLoaderInfo or PurplePluginProtocolInfo struct */
+	NULL,												/* pointer to a PurplePluginUiInfo struct */
+	mxit_actions,										/* function pointer where you can define plugin-actions */
+
+	/* padding */
+	NULL,												/* pointer reserved for future use */
+	NULL,												/* pointer reserved for future use */
+	NULL,												/* pointer reserved for future use */
+	NULL												/* pointer reserved for future use */
+};
+
+
+/*------------------------------------------------------------------------
+ * Initialising the MXit plugin.
+ *
+ *  @param plugin	The plugin object
+ */
+static void init_plugin( PurplePlugin* plugin )
+{
+	PurpleAccountOption*	option;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "Loading MXit libPurple plugin...\n" );
+
+	/* Configuration options */
+
+	/* WAP server (reference: "libpurple/accountopt.h") */
+	option = purple_account_option_string_new( _( "WAP Server" ), MXIT_CONFIG_WAPSERVER, DEFAULT_WAPSITE );
+	proto_info.protocol_options = g_list_append( proto_info.protocol_options, option );
+
+	option = purple_account_option_bool_new( _( "Connect via HTTP" ), MXIT_CONFIG_USE_HTTP, FALSE );
+	proto_info.protocol_options = g_list_append( proto_info.protocol_options, option );
+
+	option = purple_account_option_bool_new( _( "Enable splash-screen popup" ), MXIT_CONFIG_SPLASHPOPUP, FALSE );
+	proto_info.protocol_options = g_list_append( proto_info.protocol_options, option );
+
+	g_assert( sizeof( struct raw_chunk ) == 5 );
+}
+
+PURPLE_INIT_PLUGIN( mxit, init_plugin, plugin_info );
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/mxit.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,197 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *					--  MXit libPurple plugin API --
+ *
+ *				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
+ */
+
+#ifndef		_MXIT_H_
+#define		_MXIT_H_
+
+
+/* internationalize feedback strings */
+#ifndef		_
+#ifdef		GETTEXT_PACKAGE
+#include	<glib/gi18n-lib.h>
+#else
+#define		_( x )						( x )
+#endif
+#endif
+
+
+#if defined( __APPLE__ )
+/* apple architecture */
+#ifndef HOST_NAME_MAX
+#define		HOST_NAME_MAX				512
+#endif
+#elif defined( _WIN32 )
+/* windows architecture */
+#define		HOST_NAME_MAX				512
+#include	"libc_interface.h"
+#elif defined( __linux__ )
+/* linux architecture */
+#include	<net/if.h>
+#include	<sys/ioctl.h>
+#include	<sys/socket.h>
+#include	<netinet/in.h>
+#include	<arpa/inet.h>
+#else
+/* other architecture */
+#ifndef HOST_NAME_MAX
+#define		HOST_NAME_MAX				512
+#endif
+#endif
+
+
+#include	"protocol.h"
+#include	"profile.h"
+
+
+/* Plugin details */
+#define		MXIT_PLUGIN_ID				"prpl-loubserp-mxit"
+#define		MXIT_PLUGIN_NAME			"MXit"
+#define		MXIT_PLUGIN_VERSION			"2.2.0"
+#define		MXIT_PLUGIN_EMAIL			"Pieter Loubser <libpurple@mxit.com>"
+#define		MXIT_PLUGIN_WWW				"http://www.mxit.com"
+#define		MXIT_PLUGIN_SUMMARY			"MXit Protocol Plugin"
+#define		MXIT_PLUGIN_DESC			"MXit"
+
+#define		MXIT_HTTP_USERAGENT			"libpurple-"MXIT_PLUGIN_VERSION
+
+
+/* default connection settings */
+#define		DEFAULT_SERVER				"stream.mxit.co.za"
+#define		DEFAULT_PORT				9119
+#define		DEFAULT_WAPSITE				"http://www.mxit.com"
+#define		DEFAULT_HTTP_SERVER			"http://int.poll.mxit.com:80/mxit"
+
+
+/* Purple account configuration variable names */
+#define		MXIT_CONFIG_STATE			"state"
+#define		MXIT_CONFIG_WAPSERVER		"wap_server"
+#define		MXIT_CONFIG_DISTCODE		"distcode"
+#define		MXIT_CONFIG_CLIENTKEY		"clientkey"
+#define		MXIT_CONFIG_DIALCODE		"dialcode"
+#define		MXIT_CONFIG_SERVER_ADDR		"server"
+#define		MXIT_CONFIG_SERVER_PORT		"port"
+#define		MXIT_CONFIG_HTTPSERVER		"httpserver"
+#define		MXIT_CONFIG_SPLASHID		"splashid"
+#define		MXIT_CONFIG_SPLASHCLICK		"splashclick"
+#define		MXIT_CONFIG_SPLASHPOPUP		"splashpopup"
+#define		MXIT_CONFIG_COUNTRYCODE		"cc"
+#define		MXIT_CONFIG_LOCALE			"locale"
+#define		MXIT_CONFIG_USE_HTTP		"use_http"
+
+
+/* account states */
+#define		MXIT_STATE_LOGIN			0x00
+#define		MXIT_STATE_REGISTER1		0x01
+#define		MXIT_STATE_REGISTER2		0x02
+
+
+/* Client session flags */
+#define		MXIT_FLAG_CONNECTED			0x01		/* established connection to the server */
+#define		MXIT_FLAG_LOGGEDIN			0x02		/* user currently logged in */
+#define		MXIT_FLAG_FIRSTROSTER		0x04		/* set to true once the first roster update has been recevied and processed */
+
+
+/* define this to enable the link clicking support */
+#define		MXIT_LINK_CLICK
+
+
+#ifdef		MXIT_LINK_CLICK
+#define		MXIT_LINK_PREFIX			"gopher://"
+#define		MXIT_LINK_KEY				"MXIT"
+#endif
+
+
+#define		ARRAY_SIZE( x )				( sizeof( x ) / sizeof( x[0] ) )
+
+
+/*
+ * data structure containing all MXit session information 
+ */
+struct MXitSession {
+	/* socket connection */
+	char				server[HOST_NAME_MAX];		/* MXit server name to connect to */
+	int					port;						/* MXit server port to connect on */
+	int					fd;							/* connection file descriptor */
+
+	/* http connection */
+	gboolean			http;						/* connect to MXit via HTTP and not by socket */
+	char				http_server[HOST_NAME_MAX];	/* MXit HTTP server */
+	unsigned int		http_sesid;					/* HTTP session id */
+	unsigned int		http_seqno;					/* HTTP request sequence number */
+	guint				http_timer_id;				/* timer resource id (pidgin) */
+	int					http_interval;				/* poll inverval */
+	time_t				http_last_poll;				/* the last time a poll has been sent */
+	guint				http_handler;				/* HTTP connection handler */
+	void*				http_out_req;				/* HTTP outstanding request */
+
+	/* client */
+	struct login_data*	logindata;
+	char*				encpwd;						/* encrypted password */
+	char				distcode[64];				/* distribution code */
+	char				clientkey[16];				/* client key */
+	char				dialcode[8];				/* dialing code */
+	short				flags;						/* client session flags (see above) */
+
+	/* personal (profile) */
+	struct MXitProfile*	profile;					/* user's profile information */
+	int					mood;						/* user's current mood */
+
+	/* libpurple */
+	PurpleAccount*		acc;						/* pointer to the libpurple internal account struct */
+	PurpleConnection*	con;						/* pointer to the libpurple internal connection struct */
+
+	/* transmit */
+	struct tx_queue		queue;						/* transmit packet queue (FIFO mode) */
+	time_t				last_tx;					/* timestamp of last packet sent */
+	int					outack;						/* outstanding ack packet */
+	guint				q_timer;					/* timer handler for managing queue */
+
+	/* receive */
+	char				rx_lbuf[16];				/* receive byte buffer (socket packet length) */
+	char				rx_dbuf[CP_MAX_PACKET];		/* receive byte buffer (raw data) */
+	unsigned int		rx_i;						/* receive buffer current index */
+	int					rx_res;						/* amount of bytes still outstanding for the current packet */
+	char				rx_state;					/* current receiver state */
+	time_t				last_rx;					/* timestamp of last packet received */
+	GList*				active_chats;				/* list of all our contacts we received messages from (active chats) */
+
+	/* groupchat */
+	GList*				rooms;						/* active groupchat rooms */
+
+	/* inline images */
+	GHashTable*			iimages;					/* table which maps inline images (including emoticons) to purple's imgstore id's */
+};
+
+
+char* mxit_status_text( PurpleBuddy* buddy );
+void mxit_enable_signals( struct MXitSession* session );
+
+#ifdef	MXIT_LINK_CLICK
+void mxit_register_uri_handler();
+#endif
+
+
+#endif		/* _MXIT_H_ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/profile.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,160 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *					-- user profile's --
+ *
+ *				Andrew Victor	<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	<ctype.h>
+#include	<string.h>
+
+#include	"purple.h"
+
+#include	"mxit.h"
+#include	"profile.h"
+#include	"roster.h"
+
+
+/*------------------------------------------------------------------------
+ * Returns true if it is a valid date.
+ *
+ * @param bday		Date-of-Birth string
+ * @return			TRUE if valid, else FALSE
+ */
+gboolean validateDate( const char* bday )
+{
+	struct tm*	tm;
+	time_t		t;
+	int			cur_year;
+	int			max_days[13]	= { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+	char		date[16];
+	int			year;
+	int			month;
+	int			day;
+
+	/* validate length */
+	if ( strlen( bday ) != 10 ) {
+		return FALSE;
+	}
+
+	/* validate the format */
+	if (	( !isdigit( bday[0] ) ) || ( !isdigit( bday[1] ) ) || ( !isdigit( bday[2] ) ) || ( !isdigit( bday[3] ) ) ||		/* year */
+			( bday[4] != '-' ) ||
+			( !isdigit( bday[5] ) ) || ( !isdigit( bday[6] ) ) ||															/* month */
+			( bday[7] != '-' ) ||
+			( !isdigit( bday[8] ) ) || ( !isdigit( bday[9] ) ) ) { 															/* day */
+		return FALSE;
+	}
+
+	/* convert */
+	t = time( NULL );
+	tm = gmtime( &t );
+	cur_year = tm->tm_year + 1900;
+	memcpy( date, bday, 10 );
+	date[4] = '\0';
+	date[7] = '\0';
+	date[10] = '\0';
+	year = atoi( &date[0] );
+	month = atoi( &date[5] );
+	day = atoi( &date[8] );
+
+	/* validate month */
+	if ( ( month < 1 ) || ( month > 12 ) ) {
+		return FALSE;
+	}
+
+	/* validate day */
+	if ( ( day < 1 ) || ( day > max_days[month] ) ) {
+		return FALSE;
+	}
+
+	/* validate year */
+	if ( ( year < ( cur_year - 100 ) ) || ( year >= cur_year ) ) {
+		/* you are either tooo old or tooo young to join mxit... sorry */
+		return FALSE;
+	}
+
+	/* special case leap-year */
+	if ( ( year % 4 != 0 ) && ( month == 2 ) && ( day == 29 ) ) {
+		/* cannot have 29 days in February in non leap-years! */
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Display the profile information.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username who's profile information this is
+ *  @param profile		The profile
+ */
+void mxit_show_profile( struct MXitSession* session, const char* username, struct MXitProfile* profile )
+{
+	PurpleNotifyUserInfo*	info		= purple_notify_user_info_new();
+	struct contact*			contact		= NULL;
+	PurpleBuddy*			buddy;
+
+	buddy = purple_find_buddy( session->acc, username );
+	if ( buddy ) {
+		purple_notify_user_info_add_pair( info, _( "Alias" ), buddy->alias );
+		purple_notify_user_info_add_section_break( info );
+		contact = buddy->proto_data;
+	}
+
+	purple_notify_user_info_add_pair( info, _( "Nick Name" ), profile->nickname );
+	purple_notify_user_info_add_pair( info, _( "Birthday" ), profile->birthday );
+	purple_notify_user_info_add_pair( info, _( "Gender" ), profile->male ? _( "Male" ) : _( "Female" ) );
+	purple_notify_user_info_add_pair( info, _( "Hidden Number" ), profile->hidden ? _( "Yes" ) : _( "No" ) );
+
+	purple_notify_user_info_add_section_break( info );
+
+	/* optional information */
+	purple_notify_user_info_add_pair( info, _( "Title" ), profile->title );
+	purple_notify_user_info_add_pair( info, _( "First Name" ), profile->firstname );
+	purple_notify_user_info_add_pair( info, _( "Last Name" ), profile->lastname );
+	purple_notify_user_info_add_pair( info, _( "Email" ), profile->email );
+
+	purple_notify_user_info_add_section_break( info );
+
+	if ( contact ) {
+		/* presence */
+		purple_notify_user_info_add_pair( info, _( "Status" ), mxit_convert_presence_to_name( contact->presence ) );
+
+		/* mood */
+		if ( contact->mood != MXIT_MOOD_NONE )   
+			purple_notify_user_info_add_pair( info, _( "Mood" ), mxit_convert_mood_to_name( contact->mood ) );
+		else
+			purple_notify_user_info_add_pair( info, _( "Mood" ), _( "None" ) );
+
+		/* status message */
+		if ( contact->statusMsg )
+			purple_notify_user_info_add_pair( info, _( "Status Message" ), contact->statusMsg );
+
+		/* subscription type */
+		purple_notify_user_info_add_pair( info, _( "Subscription" ), mxit_convert_subtype_to_name( contact->subtype ) );
+	}
+
+	purple_notify_userinfo( session->con, username, info, NULL, NULL );
+	purple_notify_user_info_destroy( info );
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/profile.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,56 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *					-- user profile's --
+ *
+ *				Andrew Victor	<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
+ */
+
+#ifndef		_MXIT_PROFILE_H_
+#define		_MXIT_PROFILE_H_
+
+#include	<glib.h>
+
+
+struct MXitProfile {
+	/* required */
+	char		loginname[64];						/* name user uses to log into MXit with (aka 'mxitid') */
+	char		nickname[64];						/* user's own display name (aka 'nickname', aka 'fullname', aka 'alias') in MXit */
+	char		birthday[16];						/* user's birthday "YYYY-MM-DD" */
+	gboolean	male;								/* true if the user's gender is male (otherwise female) */
+	char		pin[16];							/* user's password */
+
+	/* optional */
+	char		title[32];							/* user's title */
+	char		firstname[64];						/* user's first name */
+	char		lastname[64];						/* user's last name (aka 'surname') */
+	char		email[64];							/* user's email address */
+	char		mobilenr[21];						/* user's mobile number */
+
+	gboolean	hidden;								/* set if the user's msisdn should remain hidden */
+};
+
+struct MXitSession;
+void mxit_show_profile( struct MXitSession* session, const char* username, struct MXitProfile* profile );
+
+gboolean validateDate( const char* bday );
+
+
+#endif		/* _MXIT_PROFILE_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/protocol.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,2442 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- MXit client protocol implementation --
+ *
+ *				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	<unistd.h>
+#include	<string.h>
+#include	<errno.h>
+
+#include	"purple.h"
+
+#include	"protocol.h"
+#include	"mxit.h"
+#include	"roster.h"
+#include	"chunk.h"
+#include	"filexfer.h"
+#include	"markup.h"
+#include	"multimx.h"
+#include	"splashscreen.h"
+#include	"login.h"
+#include	"formcmds.h"
+#include	"http.h"
+
+
+#define		MXIT_MS_OFFSET		3
+
+/* configure the right record terminator char to use */
+#define		CP_REC_TERM			( ( session->http ) ? CP_HTTP_REC_TERM : CP_SOCK_REC_TERM )
+
+
+
+/*------------------------------------------------------------------------
+ * Display a notification popup message to the user.
+ *
+ *  @param type			The type of notification:
+ *		- info:		PURPLE_NOTIFY_MSG_INFO
+ *		- warning:	PURPLE_NOTIFY_MSG_WARNING
+ *		- error:	PURPLE_NOTIFY_MSG_ERROR
+ *  @param heading		Heading text
+ *  @param message		Message text
+ */
+void mxit_popup( int type, const char* heading, const char* message )
+{
+	/* (reference: "libpurple/notify.h") */
+	purple_notify_message( NULL, type, _( MXIT_POPUP_WIN_NAME ), heading, message, NULL, NULL );
+}
+
+
+/*------------------------------------------------------------------------
+ * For compatibility with legacy clients, all usernames are sent from MXit with a domain
+ *  appended.  For MXit contacts, this domain is set to "@m".  This function strips
+ *  those fake domains.
+ *
+ *  @param username		The username of the contact
+ */
+void mxit_strip_domain( char* username )
+{
+	if ( g_str_has_suffix( username, "@m" ) )
+		username[ strlen(username) - 2 ] = '\0';
+}
+
+
+/*------------------------------------------------------------------------
+ * Dump a byte buffer to the console for debugging purposes.
+ *
+ *  @param buf			The data
+ *  @param len			The data length
+ */
+void dump_bytes( struct MXitSession* session, const char* buf, int len )
+{
+	char		msg[( len * 3 ) + 1];
+	int			i;
+
+	memset( msg, 0x00, sizeof( msg ) );
+
+	for ( i = 0; i < len; i++ ) {
+		if ( buf[i] == CP_REC_TERM )		/* record terminator */
+			msg[i] = '!';
+		else if ( buf[i] == CP_FLD_TERM )	/* field terminator */
+			msg[i] = '^';
+		else if ( buf[i] == CP_PKT_TERM )	/* packet terminator */
+			msg[i] = '@';
+		else if ( buf[i] < 0x20 )
+			msg[i] = '_';
+		else
+			msg[i] = buf[i];
+
+	}
+
+	purple_debug_info( MXIT_PLUGIN_ID, "DUMP: '%s'\n", msg );
+}
+
+
+/*------------------------------------------------------------------------
+ * Determine if we have an active chat with a specific contact
+ *
+ *  @param session		The MXit session object
+ *  @param who			The contact name
+ *  @return				Return true if we have an active chat with the contact
+ */
+gboolean find_active_chat( const GList* chats, const char* who )
+{
+	const GList*	list	= chats;
+	const char*		chat	= NULL;
+
+	while ( list ) {
+		chat = (const char*) list->data;
+
+		if ( strcmp( chat, who ) == 0 )
+			return TRUE;
+
+		list = g_list_next( list );
+	}
+
+	return FALSE;
+}
+
+
+/*========================================================================================================================
+ * Low-level Packet transmission
+ */
+
+/*------------------------------------------------------------------------
+ * Remove next packet from transmission queue.
+ *
+ *  @param session		The MXit session object
+ *  @return				The next packet for transmission (or NULL)
+ */
+static struct tx_packet* pop_tx_packet( struct MXitSession* session )
+{
+	struct tx_packet*	packet	= NULL;
+
+	if ( session->queue.count > 0 ) {
+		/* dequeue the next packet */
+		packet = session->queue.packets[session->queue.rd_i];
+		session->queue.packets[session->queue.rd_i] = NULL;
+		session->queue.rd_i = ( session->queue.rd_i + 1 ) % MAX_QUEUE_SIZE;
+		session->queue.count--;
+	}
+
+	return packet;
+}
+
+
+/*------------------------------------------------------------------------
+ * Add packet to transmission queue.
+ *
+ *  @param session		The MXit session object
+ *  @param packet		The packet to transmit
+ *  @return				Return TRUE if packet was enqueue, or FALSE if queue is full.
+ */
+static gboolean push_tx_packet( struct MXitSession* session, struct tx_packet* packet )
+{
+	if ( session->queue.count < MAX_QUEUE_SIZE ) {
+		/* enqueue packet */
+		session->queue.packets[session->queue.wr_i] = packet;
+		session->queue.wr_i = ( session->queue.wr_i + 1 ) % MAX_QUEUE_SIZE;
+		session->queue.count++;
+		return TRUE;
+	}
+	else
+		return FALSE;		/* queue is full */
+}
+
+
+/*------------------------------------------------------------------------
+ * Deallocate transmission packet.
+ *
+ *  @param packet		The packet to deallocate.
+ */
+static void free_tx_packet( struct tx_packet* packet )
+{
+	g_free( packet->data );
+	g_free( packet );
+	packet = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Flush all the packets from the tx queue and release the resources.
+ *
+ *  @param session		The MXit session object
+ */
+static void flush_queue( struct MXitSession* session )
+{
+	struct tx_packet*	packet;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "flushing the tx queue\n" );
+
+	while ( (packet = pop_tx_packet( session ) ) != NULL )
+		free_tx_packet( packet );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 3: Write the packet data to the TCP connection.
+ *
+ *  @param fd			The file descriptor
+ *  @param pktdata		The packet data
+ *  @param pktlen		The length of the packet data
+ *  @return				Return -1 on error, otherwise 0
+ */
+static int mxit_write_sock_packet( int fd, const char* pktdata, int pktlen )
+{
+	int		written;
+	int		res;
+
+	written = 0;
+	while ( written < pktlen ) {
+		res = write( fd, &pktdata[written], pktlen - written );
+		if ( res <= 0 ) {
+			/* error on socket */
+			if ( errno == EAGAIN )
+				continue;
+
+			purple_debug_error( MXIT_PLUGIN_ID, "Error while writing packet to MXit server (%i)\n", res );
+			return -1;
+		}
+		written += res;
+	}
+
+	return 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback called for handling a HTTP GET response
+ *
+ *  @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 (could be NULL if error)
+ *  @param len				The length of the data returned (0 if error)
+ *  @param error_message	Descriptive error message
+ */
+static void mxit_cb_http_rx( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
+{
+	struct MXitSession*		session		= (struct MXitSession*) user_data;
+
+	/* clear outstanding request */
+	session->http_out_req = NULL;
+
+	if ( ( !url_text ) || ( len == 0 ) ) {
+		/* error with request */
+		purple_debug_error( MXIT_PLUGIN_ID, "HTTP response error (%s)\n", error_message );
+		return;
+	}
+
+	/* convert the HTTP result */
+	memcpy( session->rx_dbuf, url_text, len );
+	session->rx_i = len;
+
+	mxit_parse_packet( session );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 3: Write the packet data to the HTTP connection (GET style).
+ *
+ *  @param session		The MXit session object
+ *  @param pktdata		The packet data
+ *  @param pktlen		The length of the packet data
+ *  @return				Return -1 on error, otherwise 0
+ */
+static void mxit_write_http_get( struct MXitSession* session, struct tx_packet* packet )
+{
+	char*		part	= NULL;
+	char*		url		= NULL;
+
+	if ( packet->datalen > 0 ) {
+		char*	tmp		= NULL;
+
+		tmp = g_strndup( packet->data, packet->datalen );
+		part = g_strdup( purple_url_encode( tmp ) );
+		g_free( tmp );
+	}
+
+	url = g_strdup_printf( "%s?%s%s", session->http_server, purple_url_encode( packet->header ), ( !part ) ? "" : part );
+
+#ifdef	DEBUG_PROTOCOL
+	purple_debug_info( MXIT_PLUGIN_ID, "HTTP GET: '%s'\n", url );
+#endif
+
+	/* send the HTTP request */
+	session->http_out_req = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_http_rx, session );
+
+	g_free( url );
+	if ( part )
+		g_free( part );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 3: Write the packet data to the HTTP connection (POST style).
+ *
+ *  @param session		The MXit session object
+ *  @param pktdata		The packet data
+ *  @param pktlen		The length of the packet data
+ *  @return				Return -1 on error, otherwise 0
+ */
+static void mxit_write_http_post( struct MXitSession* session, struct tx_packet* packet )
+{
+	char		request[256 + packet->datalen];
+	int			reqlen;
+	char*		host_name;
+	int			host_port;
+	gboolean	ok;
+
+	/* extract the HTTP host name and host port number to connect to */
+	ok = purple_url_parse( session->http_server, &host_name, &host_port, NULL, NULL, NULL );
+	if ( !ok ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "HTTP POST error: (host name '%s' not valid)\n", session->http_server );
+	}
+
+	/* strip off the last '&' from the header */
+	packet->header[packet->headerlen - 1] = '\0';
+	packet->headerlen--;
+
+	/* build the HTTP request packet */
+	reqlen = g_snprintf( request, 256,
+					"POST %s?%s HTTP/1.1\r\n"
+			  		"User-Agent: " MXIT_HTTP_USERAGENT "\r\n"
+					"Content-Type: application/octet-stream\r\n"
+			 		"Host: %s\r\n"
+					"Content-Length: %" G_GSIZE_FORMAT "\r\n"
+					"\r\n",
+					session->http_server,
+					purple_url_encode( packet->header ),
+					host_name,
+					packet->datalen - MXIT_MS_OFFSET
+	);
+
+	/* copy over the packet body data (could be binary) */
+	memcpy( request + reqlen, packet->data + MXIT_MS_OFFSET, packet->datalen - MXIT_MS_OFFSET );
+	reqlen += packet->datalen;
+
+#ifdef	DEBUG_PROTOCOL
+	purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST:\n" );
+	dump_bytes( session, request, reqlen );
+#endif
+
+	/* send the request to the HTTP server */
+	mxit_http_send_request( session, host_name, host_port, request, reqlen );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 2: Handle the transmission of the packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param packet		The packet to transmit
+ */
+static void mxit_send_packet( struct MXitSession* session, struct tx_packet* packet )
+{
+	int		res;
+
+	if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
+		/* we are not connected so ignore all packets to be send */
+		purple_debug_error( MXIT_PLUGIN_ID, "Dropping TX packet (we are not connected)\n" );
+		return;
+	}
+
+	purple_debug_info( MXIT_PLUGIN_ID, "Packet send CMD:%i (%i)\n", packet->cmd, packet->headerlen + packet->datalen );
+#ifdef	DEBUG_PROTOCOL
+	dump_bytes( session, packet->header, packet->headerlen );
+	dump_bytes( session, packet->data, packet->datalen );
+#endif
+
+	if ( !session->http ) {
+		/* socket connection */
+		char		data[packet->datalen + packet->headerlen];
+		int			datalen;
+
+		/* create raw data buffer */
+		memcpy( data, packet->header, packet->headerlen );
+		memcpy( data + packet->headerlen, packet->data, packet->datalen );
+		datalen = packet->headerlen + packet->datalen;
+
+		res = mxit_write_sock_packet( session->fd, data, datalen );
+		if ( res < 0 ) {
+			/* we must have lost the connection, so terminate it so that we can reconnect */
+			purple_connection_error( session->con, _( "We have lost the connection to MXit. Please reconnect." ) );
+		}
+	}
+	else {
+		/* http connection */
+
+		if ( packet->cmd == CP_CMD_MEDIA ) {
+			/* multimedia packets must be send with a HTTP POST */
+			mxit_write_http_post( session, packet );
+		}
+		else {
+			mxit_write_http_get( session, packet );
+		}
+	}
+
+	/* update the timestamp of the last-transmitted packet */
+	session->last_tx = time( NULL );
+
+	/*
+	 * we need to remember that we are still waiting for the ACK from
+	 * the server on this request
+	 */
+	session->outack = packet->cmd;
+
+	/* free up the packet resources */
+	free_tx_packet( packet );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 1: Create a new Tx packet and queue it for sending.
+ *
+ *  @param session		The MXit session object
+ *  @param data			The packet data (payload)
+ *  @param datalen		The length of the packet data
+ *  @param cmd			The MXit command for this packet
+ */
+static void mxit_queue_packet( struct MXitSession* session, const char* data, int datalen, int cmd )
+{
+	struct tx_packet*	packet;
+	char				header[256];
+	int					hlen;
+
+	/* create a packet for sending */
+	packet = g_new0( struct tx_packet, 1 );
+	packet->data = g_malloc0( datalen );
+	packet->cmd = cmd;
+	packet->headerlen = 0;
+
+	/* create generic packet header */
+	hlen = sprintf( header,	"id=%s%c", session->acc->username, CP_REC_TERM );			/* client msisdn */
+
+	if ( session->http ) {
+		/* http connection only */
+		hlen += sprintf( header + hlen,	"s=" );
+		if ( session->http_sesid > 0 ) {
+			hlen += sprintf( header + hlen,	"%u%c", session->http_sesid, CP_FLD_TERM );	/* http session id */
+		}
+		session->http_seqno++;
+		hlen += sprintf( header + hlen,	"%u%c", session->http_seqno, CP_REC_TERM );		/* http request sequence id */
+	}
+
+	hlen += sprintf( header + hlen,	"cm=%i%c", cmd, CP_REC_TERM ); 						/* packet command */
+
+	if ( !session->http ) {
+		/* socket connection only */
+		packet->headerlen += sprintf( packet->header, "ln=%i%c", ( datalen + hlen ), CP_REC_TERM );		/* packet length */
+	}
+
+	/* copy the header to packet */
+	memcpy( packet->header + packet->headerlen, header, hlen );
+	packet->headerlen += hlen;
+
+	/* copy payload to packet */
+	if ( datalen > 0 )
+		memcpy( packet->data, data, datalen );
+	packet->datalen = datalen;
+
+
+	/*
+	 * shortcut: first check if there are any commands still outstanding.
+	 * if not, then we might as well just write this packet directly and
+	 * skip the whole queueing thing
+	 */
+	if ( session->outack == 0 ) {
+		/* no outstanding ACKs, so we might as well write it directly */
+		mxit_send_packet( session, packet );
+	}
+	else {
+		/* ACK still outstanding, so we need to queue this request until we have the ACK */
+
+		if ( ( packet->cmd == CP_CMD_PING ) || ( packet->cmd == CP_CMD_POLL ) ) {
+			/* we do NOT queue HTTP poll nor socket ping packets */
+			free_tx_packet( packet );
+			return;
+		}
+
+		purple_debug_info( MXIT_PLUGIN_ID, "queueing packet for later sending cmd=%i\n", cmd );
+		if ( !push_tx_packet( session, packet ) ) {
+			/* packet could not be queued for transmission */
+			mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Message Send Error" ), _( "Unable to process your request at this time" ) );
+			free_tx_packet( packet );
+		}
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback to manage the packet send queue (send next packet, timeout's, etc).
+ *
+ *  @param session		The MXit session object
+ */
+gboolean mxit_manage_queue( gpointer user_data )
+{
+	struct MXitSession* session		= (struct MXitSession*) user_data;
+	struct tx_packet*	packet		= NULL;
+
+	if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
+		/* we are not connected, so ignore the queue */
+		return TRUE;
+	}
+	else if ( session->outack > 0 ) {
+		/* we are still waiting for an outstanding ACK from the MXit server */
+		if ( session->last_tx <= time( NULL ) - MXIT_ACK_TIMEOUT ) {
+			/* ack timeout! so we close the connection here */
+			purple_debug_info( MXIT_PLUGIN_ID, "mxit_manage_queue: Timeout awaiting ACK for command '%X'\n", session->outack );
+			purple_connection_error( session->con, _( "Timeout while waiting for a response from the MXit server." ) );
+		}
+		return TRUE;
+	}
+
+	packet = pop_tx_packet( session );
+	if ( packet != NULL ) {
+		/* there was a packet waiting to be sent to the server, now is the time to do something about it */
+
+		/* send the packet to MXit server */
+		mxit_send_packet( session, packet );
+	}
+
+	return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback to manage HTTP server polling (HTTP connections ONLY)
+ *
+ *  @param session		The MXit session object
+ */
+gboolean mxit_manage_polling( gpointer user_data )
+{
+	struct MXitSession* session		= (struct MXitSession*) user_data;
+	gboolean			poll		= FALSE;
+	time_t				now			= time( NULL );
+	int					polldiff;
+	int					rxdiff;
+
+	if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) {
+		/* we only poll if we are actually logged in */
+		return TRUE;
+	}
+
+	/* calculate the time differences */
+	rxdiff = now - session->last_rx;
+	polldiff = now - session->http_last_poll;
+
+	if ( rxdiff < MXIT_HTTP_POLL_MIN ) {
+		/* we received some reply a few moments ago, so reset the poll interval */
+		session->http_interval = MXIT_HTTP_POLL_MIN;
+	}
+	else if ( session->http_last_poll < ( now - session->http_interval ) ) {
+		/* time to poll again */
+		poll = TRUE;
+
+		/* back-off some more with the polling */
+		session->http_interval = session->http_interval + ( session->http_interval / 2 );
+		if ( session->http_interval > MXIT_HTTP_POLL_MAX )
+			session->http_interval = MXIT_HTTP_POLL_MAX;
+	}
+
+	/* debugging */
+	//purple_debug_info( MXIT_PLUGIN_ID, "POLL TIMER: %i (%i,%i)\n", session->http_interval, rxdiff, polldiff );
+
+	if ( poll ) {
+		/* send poll request */
+		session->http_last_poll = time( NULL );
+		mxit_send_poll( session );
+	}
+
+	return TRUE;
+}
+
+
+/*========================================================================================================================
+ * Send MXit operations.
+ */
+
+/*------------------------------------------------------------------------
+ * Send a ping/keepalive packet to MXit server.
+ *
+ *  @param session		The MXit session object
+ */
+void mxit_send_ping( struct MXitSession* session )
+{
+	/* queue packet for transmission */
+	mxit_queue_packet( session, NULL, 0, CP_CMD_PING );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a poll request to the HTTP server (HTTP connections ONLY).
+ *
+ *  @param session		The MXit session object
+ */
+void mxit_send_poll( struct MXitSession* session )
+{
+	/* queue packet for transmission */
+	mxit_queue_packet( session, NULL, 0, CP_CMD_POLL );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a logout packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ */
+void mxit_send_logout( struct MXitSession* session )
+{
+	/* queue packet for transmission */
+	mxit_queue_packet( session, NULL, 0, CP_CMD_LOGOUT );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a register packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ */
+void mxit_send_register( struct MXitSession* session )
+{
+	struct MXitProfile*	profile		= session->profile;
+	const char*			locale;
+	char				data[CP_MAX_PACKET];
+	int					datalen;
+
+	locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%s%c%i%c%s%c"		/* "ms"=password\1version\1maxreplyLen\1name\1 */
+								"%s%c%i%c%s%c%s%c"			/* dateOfBirth\1gender\1location\1capabilities\1 */
+								"%s%c%i%c%s%c%s",			/* dc\1features\1dialingcode\1locale */
+								session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, CP_MAX_FILESIZE, CP_FLD_TERM, profile->nickname, CP_FLD_TERM,
+								profile->birthday, CP_FLD_TERM, ( profile->male ) ? 1 : 0, CP_FLD_TERM, MXIT_DEFAULT_LOC, CP_FLD_TERM, MXIT_CP_CAP, CP_FLD_TERM, 
+								session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM, session->dialcode, CP_FLD_TERM, locale
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_REGISTER );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a login packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ */
+void mxit_send_login( struct MXitSession* session )
+{
+	const char*	splashId;
+	const char*	locale;
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%s%c%i%c"			/* "ms"=password\1version\1getContacts\1 */
+								"%s%c%s%c%i%c"				/* capabilities\1dc\1features\1 */
+								"%s%c%s",					/* dialingcode\1locale */
+								session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, 1, CP_FLD_TERM,
+								MXIT_CP_CAP, CP_FLD_TERM, session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM,
+								session->dialcode, CP_FLD_TERM, locale
+	);
+
+	/* include "custom resource" information */
+	splashId = splash_current( session );
+	if ( splashId != NULL )
+		datalen += sprintf( data + datalen, "%ccr=%s", CP_REC_TERM, splashId );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_LOGIN );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a chat message packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param to			The username of the recipient
+ *  @param msg			The message text
+ */
+void mxit_send_message( struct MXitSession* session, const char* to, const char* msg, gboolean parse_markup )
+{
+	char		data[CP_MAX_PACKET];
+	char*		markuped_msg;
+	int			datalen;
+	int			msgtype = CP_MSGTYPE_NORMAL;
+
+	/* first we need to convert the markup from libPurple to MXit format */
+	if ( parse_markup )
+		markuped_msg = mxit_convert_markup_tx( msg, &msgtype );
+	else
+		markuped_msg = g_strdup( msg );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%s%c%i%c%i",		/* "ms"=jid\1msg\1type\1flags */
+								to, CP_FLD_TERM, markuped_msg, CP_FLD_TERM, msgtype, CP_FLD_TERM, CP_MSG_MARKUP | CP_MSG_EMOTICON
+	);
+
+	/* free the resources */
+	g_free( markuped_msg );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_TX_MSG );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a extended profile request packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param username		Username who's profile is being requested (NULL = our own)
+ *  @param nr_attribs	Number of attributes being requested
+ *  @param attributes	The names of the attributes
+ */
+void mxit_send_extprofile_request( struct MXitSession* session, const char* username, unsigned int nr_attrib, const char* attribute[] )
+{
+	char			data[CP_MAX_PACKET];
+	int				datalen;
+	unsigned int	i;
+
+	datalen = sprintf( data,	"ms=%s%c%i",		/* "ms="mxitid\1nr_attributes */
+								(username ? username : ""), CP_FLD_TERM, nr_attrib);
+
+	/* add attributes */
+	for ( i = 0; i < nr_attrib; i++ )
+		datalen += sprintf(	data + datalen, "%c%s", CP_FLD_TERM, attribute[i] );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_GET );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an update profile packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param password		The new password to be used for logging in (optional)
+ *	@param nr_attrib	The number of attributes
+ *	@param attributes	String containing the attributes and settings seperated by '0x01'
+ */
+void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes )
+{
+	char			data[CP_MAX_PACKET];
+	gchar**			parts;
+	int				datalen;
+	unsigned int	i;
+
+	parts = g_strsplit( attributes, "\01", ( MXIT_MAX_ATTRIBS * 3 ) );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%i",	/* "ms"=password\1nr_attibutes  */
+								( password ) ? password : "", CP_FLD_TERM, nr_attrib
+	);
+
+	/* add attributes */
+	for ( i = 1; i < nr_attrib * 3; i+=3 )
+		datalen += sprintf(	data + datalen, "%c%s%c%s%c%s",		/* \1name\1type\1value  */
+								CP_FLD_TERM, parts[i], CP_FLD_TERM, parts[i + 1], CP_FLD_TERM, parts[i + 2] );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_SET );
+
+	/* freeup the memory */
+	g_strfreev( parts );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a presence update packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param presence		The presence (as per MXit types)
+ *  @param statusmsg	The status message (can be NULL)
+ */
+void mxit_send_presence( struct MXitSession* session, int presence, const char* statusmsg )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%i%c",					/* "ms"=show\1status */
+								presence, CP_FLD_TERM
+	);
+
+	/* append status message (if one is set) */
+	if ( statusmsg )
+		datalen += sprintf( data + datalen, "%s", statusmsg );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_STATUS );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a mood update packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param mood			The mood (as per MXit types)
+ */
+void mxit_send_mood( struct MXitSession* session, int mood )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%i",	/* "ms"=mood */
+								mood
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MOOD );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an invite contact packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the contact being invited
+ *  @param alias		Our alias for the contact
+ *  @param groupname	Group in which contact should be stored.
+ */
+void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%s%c%s%c%i%c%s",	/* "ms"=group\1username\1alias\1type\1msg */
+								groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias,
+								CP_FLD_TERM, MXIT_TYPE_MXIT, CP_FLD_TERM, ""
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_INVITE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a remove contact packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the contact being removed
+ */
+void mxit_send_remove( struct MXitSession* session, const char* username )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s",	/* "ms"=username */
+								username
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_REMOVE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an accept subscription (invite) packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the contact being accepted
+ *  @param alias		Our alias for the contact
+ */
+void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%s%c%s",	/* "ms"=username\1group\1alias */
+								username, CP_FLD_TERM, "", CP_FLD_TERM, alias
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_ALLOW );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an deny subscription (invite) packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the contact being denied
+ */
+void mxit_send_deny_sub( struct MXitSession* session, const char* username )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s",	/* "ms"=username */
+								username
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_DENY );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an update contact packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the contact being denied
+ *  @param alias		Our alias for the contact
+ *  @param groupname	Group in which contact should be stored.
+ */
+void mxit_send_update_contact( struct MXitSession* session, const char* username, const char* alias, const char* groupname )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%s%c%s",	/* "ms"=groupname\1username\1alias */
+								groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_UPDATE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a splash-screen click event packet.
+ *
+ *  @param session		The MXit session object
+ *  @param splashid		The identifier of the splash-screen
+ */
+void mxit_send_splashclick( struct MXitSession* session, const char* splashid )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s",	/* "ms"=splashId */
+								splashid
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_SPLASHCLICK );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send packet to create a MultiMX room.
+ *
+ *  @param session		The MXit session object
+ *  @param groupname	Name of the room to create
+ *  @param nr_usernames	Number of users in initial invite
+ *  @param usernames	The usernames of the users in the initial invite
+ */
+void mxit_send_groupchat_create( struct MXitSession* session, const char* groupname, int nr_usernames, const char* usernames[] )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+	int			i;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%i",	/* "ms"=roomname\1nr_jids\1jid0\1..\1jidN */
+								groupname, CP_FLD_TERM, nr_usernames
+	);
+
+	/* add usernames */
+	for ( i = 0; i < nr_usernames; i++ )
+		datalen += sprintf(	data + datalen, "%c%s", CP_FLD_TERM, usernames[i] );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_CREATE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send packet to invite users to existing MultiMX room.
+ *
+ *  @param session		The MXit session object
+ *  @param roomid		The unique RoomID for the MultiMx room.
+ *  @param nr_usernames	Number of users being invited
+ *  @param usernames	The usernames of the users being invited
+ */
+
+void mxit_send_groupchat_invite( struct MXitSession* session, const char* roomid, int nr_usernames, const char* usernames[] )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+	int			i;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%i",	/* "ms"=roomid\1nr_jids\1jid0\1..\1jidN */
+								roomid, CP_FLD_TERM, nr_usernames
+	);
+
+	/* add usernames */
+	for ( i = 0; i < nr_usernames; i++ )
+		datalen += sprintf(	data + datalen, "%c%s", CP_FLD_TERM, usernames[i] );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_INVITE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "send file direct" multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the recipient
+ *  @param filename		The name of the file being sent
+ *  @param buf			The content of the file
+ *  @param buflen		The length of the file contents
+ */
+void mxit_send_file( struct MXitSession* session, const char* username, const char* filename, const unsigned char* buf, int buflen )
+{
+	char				data[CP_MAX_PACKET];
+	int					datalen		= 0;
+	struct raw_chunk*	chunk;
+	int					size;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "SENDING FILE '%s' of %i bytes to user '%s'\n", filename, buflen, username );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data, "ms=" );
+
+	/* map chunk header over data buffer */
+	chunk = (struct raw_chunk *) &data[datalen];
+
+	size = mxit_chunk_create_senddirect( chunk->data, username, filename, buf, buflen );
+	if ( size < 0 ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Error creating senddirect chunk (%i)\n", size );
+		return;
+	}
+
+	chunk->type = CP_CHUNK_DIRECT_SND;
+	chunk->length = htonl( size );
+	datalen += sizeof( struct raw_chunk ) + size;
+
+	/* send the byte stream to the mxit server */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "reject file" multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param fileid		A unique ID that identifies this file
+ */
+void mxit_send_file_reject( struct MXitSession* session, const char* fileid )
+{
+	char				data[CP_MAX_PACKET];
+	int					datalen		= 0;
+	struct raw_chunk*	chunk;
+	int					size;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_reject\n" );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data, "ms=" );
+
+	/* map chunk header over data buffer */
+	chunk = (struct raw_chunk *) &data[datalen];
+
+	size = mxit_chunk_create_reject( chunk->data, fileid );
+	if ( size < 0 ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Error creating reject chunk (%i)\n", size );
+		return;
+	}
+
+	chunk->type = CP_CHUNK_REJECT;
+	chunk->length = htonl( size );
+	datalen += sizeof( struct raw_chunk ) + size;
+
+	/* send the byte stream to the mxit server */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "get file" multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param fileid		A unique ID that identifies this file
+ *  @param filesize		The number of bytes to retrieve
+ *  @param offset		Offset in file at which to start retrieving
+ */
+void mxit_send_file_accept( struct MXitSession* session, const char* fileid, int filesize, int offset )
+{
+	char				data[CP_MAX_PACKET];
+	int					datalen		= 0;
+	struct raw_chunk*	chunk;
+	int					size;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_accept\n" );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data, "ms=" );
+
+	/* map chunk header over data buffer */
+	chunk = (struct raw_chunk *) &data[datalen];
+
+	size = mxit_chunk_create_get( chunk->data, fileid, filesize, offset );
+	if ( size < 0 ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Error creating getfile chunk (%i)\n", size );
+		return;
+	}
+
+	chunk->type = CP_CHUNK_GET;
+	chunk->length = htonl( size );
+	datalen += sizeof( struct raw_chunk ) + size;
+
+	/* send the byte stream to the mxit server */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "received file" multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param status		The status of the file-transfer
+ */
+void mxit_send_file_received( struct MXitSession* session, const char* fileid, short status )
+{
+	char				data[CP_MAX_PACKET];
+	int					datalen		= 0;
+	struct raw_chunk*	chunk;
+	int					size;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_received\n" );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data, "ms=" );
+
+	/* map chunk header over data buffer */
+	chunk = (struct raw_chunk *) &data[datalen];
+
+	size = mxit_chunk_create_received( chunk->data, fileid, status );
+	if ( size < 0 ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Error creating received chunk (%i)\n", size );
+		return;
+	}
+
+	chunk->type = CP_CHUNK_RECIEVED;
+	chunk->length = htonl( size );
+	datalen += sizeof( struct raw_chunk ) + size;
+
+	/* send the byte stream to the mxit server */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "set avatar" multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param data			The avatar data
+ *  @param buflen		The length of the avatar data
+ */
+void mxit_set_avatar( struct MXitSession* session, const unsigned char* avatar, int avatarlen )
+{
+	char				data[CP_MAX_PACKET];
+	int					datalen		= 0;
+	struct raw_chunk*	chunk;
+	int					size;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_set_avatar: %i bytes\n", avatarlen );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data, "ms=" );
+
+	/* map chunk header over data buffer */
+	chunk = (struct raw_chunk *) &data[datalen];
+
+	size = mxit_chunk_create_set_avatar( chunk->data, avatar, avatarlen );
+	if ( size < 0 ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Error creating set avatar chunk (%i)\n", size );
+		return;
+	}
+
+	chunk->type = CP_CHUNK_SET_AVATAR;
+	chunk->length = htonl( size );
+	datalen += sizeof( struct raw_chunk ) + size;
+
+	/* send the byte stream to the mxit server */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "get avatar" multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param mxitId		The username who's avatar to request
+ *  @param avatarId		The id of the avatar image (as string)
+ *  @param data			The avatar data
+ *  @param buflen		The length of the avatar data
+ */
+void mxit_get_avatar( struct MXitSession* session, const char* mxitId, const char* avatarId )
+{
+	char				data[CP_MAX_PACKET];
+	int					datalen		= 0;
+	struct raw_chunk*	chunk;
+	int					size;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_avatar: %s\n", mxitId );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data, "ms=" );
+
+	/* map chunk header over data buffer */
+	chunk = (struct raw_chunk *) &data[datalen];
+
+	size = mxit_chunk_create_get_avatar( chunk->data, mxitId, avatarId, MXIT_AVATAR_SIZE );
+	if ( size < 0 ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Error creating get avatar chunk (%i)\n", size );
+		return;
+	}
+
+	chunk->type = CP_CHUNK_GET_AVATAR;
+	chunk->length = htonl( size );
+	datalen += sizeof( struct raw_chunk ) + size;
+
+	/* send the byte stream to the mxit server */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a login message packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_login( struct MXitSession* session, struct record** records, int rcount )
+{
+	PurpleStatus*	status;
+	int				presence;
+	const char*		profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME,
+									CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL,
+									CP_PROFILE_MOBILENR };
+
+	purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+
+	/* we were not yet logged in so we need to complete the login sequence here */
+	session->flags |= MXIT_FLAG_LOGGEDIN;
+	purple_connection_update_progress( session->con, _( "Successfully Logged In..." ), 3, 4 );
+	purple_connection_set_state( session->con, PURPLE_CONNECTED );
+
+	/* display the current splash-screen */
+	if ( splash_popup_enabled( session ) )
+		splash_display( session );
+
+	/* update presence status */
+	status = purple_account_get_active_status( session->acc );
+	presence = mxit_convert_presence( purple_status_get_id( status ) );
+	if ( presence != MXIT_PRESENCE_ONLINE ) {
+		/* when logging into MXit, your default presence is online. but with the UI, one can change
+		 * the presence to whatever. in the case where its changed to a different presence setting
+		 * we need to send an update to the server, otherwise the user's presence will be out of
+		 * sync between the UI and MXit.
+		 */
+		mxit_send_presence( session, presence, purple_status_get_attr_string( status, "message" ) );
+	}
+
+	/* save extra info if this is a HTTP connection */
+	if ( session->http ) {
+		/* save the http server to use for this session */
+		g_strlcpy( session->http_server, records[1]->fields[3]->data, sizeof( session->http_server ) );
+
+		/* save the session id */
+		session->http_sesid = atoi( records[0]->fields[0]->data );
+	}
+
+	/* retrieve our MXit profile */
+	mxit_send_extprofile_request( session, NULL, ARRAY_SIZE( profilelist ), profilelist );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received message packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_message( struct MXitSession* session, struct record** records, int rcount )
+{
+	struct RXMsgData*	mx			= NULL;
+	char*				message		= NULL;
+	int					msglen		= 0;
+	int					msgflags	= 0;
+	int					msgtype		= 0;
+
+	if ( ( rcount == 1 ) || ( records[0]->fcount < 2 ) || ( records[1]->fcount == 0 ) || ( records[1]->fields[0]->len == 0 ) ) {
+		/* packet contains no message or an empty message */
+		return;
+	}
+
+	message = records[1]->fields[0]->data;
+	msglen = strlen( message );
+
+	/* strip off dummy domain */
+	mxit_strip_domain( records[0]->fields[0]->data );
+
+#ifdef	DEBUG_PROTOCOL
+	purple_debug_info( MXIT_PLUGIN_ID, "Message received from '%s'\n", records[0]->fields[0]->data );
+#endif
+
+	/* decode message flags (if any) */
+	if ( records[0]->fcount >= 5 )
+		msgflags = atoi( records[0]->fields[4]->data );
+	msgtype = atoi( records[0]->fields[2]->data );
+
+	if ( msgflags & CP_MSG_ENCRYPTED ) {
+		/* this is an encrypted message. we do not currently support those so ignore it */
+		PurpleBuddy*	buddy;
+		const char*		name;
+		char			msg[128];
+
+		buddy = purple_find_buddy( session->acc, records[0]->fields[0]->data );
+		if ( buddy )
+			name = purple_buddy_get_alias( buddy );
+		else
+			name = records[0]->fields[0]->data;
+		g_snprintf( msg, sizeof( msg ), "%s sent you an encrypted message, but it is not supported on this client.", name );
+		mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( msg ) );
+		return;
+	}
+
+	/* create and initialise new markup struct */
+	mx = g_new0( struct RXMsgData, 1 );
+	mx->msg = g_string_sized_new( msglen );
+	mx->session = session;
+	mx->from = g_strdup( records[0]->fields[0]->data );
+	mx->timestamp = atoi( records[0]->fields[1]->data );
+	mx->got_img = FALSE;
+	mx->chatid = -1;
+	mx->img_count = 0;
+
+	/* update list of active chats */
+	if ( !find_active_chat( session->active_chats, mx->from ) ) {
+		session->active_chats = g_list_append( session->active_chats, g_strdup( mx->from ) );
+	}
+
+	if ( is_multimx_contact( session, mx->from ) ) {
+		/* this is a MultiMx chatroom message */
+		multimx_message_received( mx, message, msglen, msgtype, msgflags );
+	}
+	else {
+		mxit_parse_markup( mx, message, msglen, msgtype, msgflags );
+	}
+
+	/* we are now done parsing the message */
+	mx->converted = TRUE;
+	if ( mx->img_count == 0 ) {
+		/* we have all the data we need for this message to be displayed now. */
+		mxit_show_message( mx );
+	}
+	else {
+		/* this means there are still images outstanding for this message and
+		 * still need to wait for them before we can display the message.
+		 * so the image received callback function will eventually display
+		 * the message. */
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received subscription request packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_new_sub( struct MXitSession* session, struct record** records, int rcount )
+{
+	struct contact*		contact;
+	struct record*		rec;
+	int					i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_new_sub (%i recs)\n", rcount );
+
+	for ( i = 0; i < rcount; i++ ) {
+		rec = records[i];
+
+		if ( rec->fcount < 4 ) {
+			purple_debug_error( MXIT_PLUGIN_ID, "BAD SUBSCRIPTION RECORD! %i fields\n", rec->fcount );
+			break;
+		}
+
+		/* build up a new contact info struct */
+		contact = g_new0( struct contact, 1 );
+
+		strcpy( contact->username, rec->fields[0]->data );
+		mxit_strip_domain( contact->username );				/* remove dummy domain */
+		strcpy( contact->alias, rec->fields[1]->data );
+		contact->type = atoi( rec->fields[2]->data );
+
+		if ( rec->fcount >= 5 ) {
+			/* there is a personal invite message attached */
+			contact->msg = strdup( rec->fields[4]->data );
+		}
+		else
+			contact->msg = NULL;
+
+		/* handle the subscription */
+		if ( contact-> type == MXIT_TYPE_MULTIMX ) {		/* subscription to a MultiMX room */
+			char* creator = NULL;
+
+			if ( rec->fcount >= 6 )
+				creator = rec->fields[5]->data;
+
+			multimx_invite( session, contact, creator );
+		}
+		else
+			mxit_new_subscription( session, contact );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received contact update packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_contact( struct MXitSession* session, struct record** records, int rcount )
+{
+	struct contact*		contact	= NULL;
+	struct record*		rec;
+	int					i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_contact (%i recs)\n", rcount );
+
+	for ( i = 0; i < rcount; i++ ) {
+		rec = records[i];
+
+		if ( rec->fcount < 6 ) {
+			purple_debug_error( MXIT_PLUGIN_ID, "BAD CONTACT RECORD! %i fields\n", rec->fcount );
+			break;
+		}
+
+		/* build up a new contact info struct */
+		contact = g_new0( struct contact, 1 );
+
+		strcpy( contact->groupname, rec->fields[0]->data );
+		strcpy( contact->username, rec->fields[1]->data );
+		mxit_strip_domain( contact->username );				/* remove dummy domain */
+		strcpy( contact->alias, rec->fields[2]->data );
+
+		contact->presence = atoi( rec->fields[3]->data );
+		contact->type = atoi( rec->fields[4]->data );
+		contact->mood = atoi( rec->fields[5]->data );
+
+		if ( rec->fcount > 6 ) {
+			/* added in protocol 5.9.0 - flags & subtype */
+			contact->flags = atoi( rec->fields[6]->data );
+			contact->subtype = rec->fields[7]->data[0];
+		}
+
+		/* add the contact to the buddy list */
+		if ( contact-> type == MXIT_TYPE_MULTIMX )			/* contact is a MultiMX room */
+			multimx_created( session, contact );
+		else
+			mxit_update_contact( session, contact );
+	}
+
+	if ( !( session->flags & MXIT_FLAG_FIRSTROSTER ) ) {
+		session->flags |= MXIT_FLAG_FIRSTROSTER;
+		mxit_update_blist( session );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received presence update packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_presence( struct MXitSession* session, struct record** records, int rcount )
+{
+	struct record*		rec;
+	int					i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_presence (%i recs)\n", rcount );
+
+	for ( i = 0; i < rcount; i++ ) {
+		rec = records[i];
+
+		if ( rec->fcount < 6 ) {
+			purple_debug_error( MXIT_PLUGIN_ID, "BAD PRESENCE RECORD! %i fields\n", rec->fcount );
+			break;
+		}
+
+		/*
+		 * The format of the record is:
+		 * contactAddressN\1presenceN\1\moodN\1customMoodN\1statusMsgN\1avatarIdN
+		 */
+		mxit_strip_domain( rec->fields[0]->data );		/* contactAddress */
+
+		mxit_update_buddy_presence( session, rec->fields[0]->data, atoi( rec->fields[1]->data ), atoi( rec->fields[2]->data ),
+				rec->fields[3]->data, rec->fields[4]->data, rec->fields[5]->data );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received extended profile packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_extprofile( struct MXitSession* session, struct record** records, int rcount )
+{
+	const char*				mxitId		= records[0]->fields[0]->data;
+	struct MXitProfile*		profile		= NULL;
+	int						count;
+	int						i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_extprofile: profile for '%s'\n", mxitId );
+
+	profile = g_new0( struct MXitProfile, 1 );
+
+	/* set the count for attributes */
+	count = atoi( records[0]->fields[1]->data );
+
+	for ( i = 0; i < count; i++ ) {
+		char* fname;
+		char* fvalue;
+		char* fstatus;
+		int f = ( i * 3 ) + 2;
+
+		fname = records[0]->fields[f]->data;		/* field name */
+		fvalue = records[0]->fields[f + 1]->data;	/* field value */
+		fstatus = records[0]->fields[f + 2]->data;	/* field status */
+
+		/* first check the status on the returned attribute */
+		if ( fstatus[0] != '0' ) {
+			/* error: attribute requested was NOT found */
+			purple_debug_error( MXIT_PLUGIN_ID, "Bad profile status on attribute '%s' \n", fname );
+			continue;
+		}
+
+		if ( strcmp( CP_PROFILE_BIRTHDATE, fname ) == 0 ) {
+			/* birthdate */
+			if ( records[0]->fields[f + 1]->len > 10 ) {
+				fvalue[10] = '\0';
+				records[0]->fields[f + 1]->len = 10;
+			}
+			memcpy( profile->birthday, fvalue, records[0]->fields[f + 1]->len );
+		}
+		else if ( strcmp( CP_PROFILE_GENDER, fname ) == 0 ) {
+			/* gender */
+			profile->male = ( fvalue[0] == '1' );
+		}
+		else if ( strcmp( CP_PROFILE_HIDENUMBER, fname ) == 0 ) {
+			/* hide number */
+			profile->hidden = ( fvalue[0] == '1' );
+		}
+		else if ( strcmp( CP_PROFILE_FULLNAME, fname ) == 0 ) {
+			/* nickname */
+			g_strlcpy( profile->nickname, fvalue, sizeof( profile->nickname ) );
+		}
+		else if ( strcmp( CP_PROFILE_AVATAR, fname ) == 0 ) {
+			/* avatar id, we just ingore it cause we dont need it */
+		}
+		else if ( strcmp( CP_PROFILE_TITLE, fname ) == 0 ) {
+			/* title */
+			g_strlcpy( profile->title, fvalue, sizeof( profile->title ) );
+		}
+		else if ( strcmp( CP_PROFILE_FIRSTNAME, fname ) == 0 ) {
+			/* first name */
+			g_strlcpy( profile->firstname, fvalue, sizeof( profile->firstname ) );
+		}
+		else if ( strcmp( CP_PROFILE_LASTNAME, fname ) == 0 ) {
+			/* last name */
+			g_strlcpy( profile->lastname, fvalue, sizeof( profile->lastname ) );
+		}
+		else if ( strcmp( CP_PROFILE_EMAIL, fname ) == 0 ) {
+			/* email address */
+			g_strlcpy( profile->email, fvalue, sizeof( profile->email ) );
+		}
+		else if ( strcmp( CP_PROFILE_MOBILENR, fname ) == 0 ) {
+			/* mobile number */
+			g_strlcpy( profile->mobilenr, fvalue, sizeof( profile->mobilenr ) );
+		}
+		else {
+			/* invalid profile attribute */
+			purple_debug_error( MXIT_PLUGIN_ID, "Invalid profile attribute received '%s' \n", fname );
+		}
+	}
+
+	if ( records[0]->fields[0]->len == 0 ) {
+		/* no MXit id provided, so this must be our own profile information */
+		if ( session->profile )
+			g_free( session->profile );
+		session->profile = profile;
+	}
+	else {
+		/* display other user's profile */
+		mxit_show_profile( session, mxitId, profile );
+
+		/* cleanup */
+		g_free( profile );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the length of a multimedia chunk
+ *
+ * @return		The actual chunk data length in bytes
+ */
+static int get_chunk_len( const char* chunkdata )
+{
+	int*	sizeptr;
+
+	sizeptr = (int*) &chunkdata[1];		/* we skip the first byte (type field) */
+
+	return ntohl( *sizeptr );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_media( struct MXitSession* session, struct record** records, int rcount )
+{
+	char	type;
+	int		size;
+
+	type = records[0]->fields[0]->data[0];
+	size = get_chunk_len( records[0]->fields[0]->data );
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_media (%i records) (%i bytes)\n", rcount, size );
+
+	/* supported chunked data types */
+	switch ( type ) {
+		case CP_CHUNK_CUSTOM :				/* custom resource */
+			{
+				struct cr_chunk chunk;
+
+				/* decode the chunked data */
+				memset( &chunk, 0, sizeof( struct cr_chunk ) );
+				mxit_chunk_parse_cr( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+				purple_debug_info( MXIT_PLUGIN_ID, "chunk info id=%s handle=%s op=%i\n", chunk.id, chunk.handle, chunk.operation );
+
+				/* this is a splash-screen operation */
+				if ( strcmp( chunk.handle, HANDLE_SPLASH2 ) == 0 ) {
+					if ( chunk.operation == CR_OP_UPDATE ) {		/* update the splash-screen */
+						struct splash_chunk *splash = chunk.resources->data;			// TODO: Fix - assuming 1st resource is splash
+						gboolean clickable = ( g_list_length( chunk.resources ) > 1 );	// TODO: Fix - if 2 resources, then is clickable
+
+						if ( splash != NULL )
+							splash_update( session, chunk.id, splash->data, splash->datalen, clickable );
+					}
+					else if ( chunk.operation == CR_OP_REMOVE )		/* remove the splash-screen */
+						splash_remove( session );
+				}
+
+				/* cleanup custom resources */
+				g_list_foreach( chunk.resources, (GFunc)g_free, NULL );
+
+			}
+			break;
+
+		case CP_CHUNK_OFFER :				/* file offer */
+			{
+				struct offerfile_chunk chunk;
+
+				/* decode the chunked data */
+				memset( &chunk, 0, sizeof( struct offerfile_chunk ) );
+				mxit_chunk_parse_offer( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+				/* process the offer */
+				mxit_xfer_rx_offer( session, chunk.username, chunk.filename, chunk.filesize, chunk.fileid );
+			}
+			break;
+
+		case CP_CHUNK_GET :					/* get file response */
+			{
+				struct getfile_chunk chunk;
+
+				/* decode the chunked data */
+				memset( &chunk, 0, sizeof( struct getfile_chunk ) );
+				mxit_chunk_parse_get( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+				/* process the getfile */
+				mxit_xfer_rx_file( session, chunk.fileid, chunk.data, chunk.length );
+			}
+			break;
+
+		case CP_CHUNK_GET_AVATAR :			/* get avatars */
+			{
+				struct getavatar_chunk chunk;
+
+				/* decode the chunked data */
+				memset( &chunk, 0, sizeof ( struct getavatar_chunk ) );
+				mxit_chunk_parse_get_avatar( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+				/* update avatar image */
+				if ( chunk.data ) {
+					purple_debug_info( MXIT_PLUGIN_ID, "updating avatar for contact '%s'\n", chunk.mxitid );
+					purple_buddy_icons_set_for_user( session->acc, chunk.mxitid, g_memdup( chunk.data, chunk.length), chunk.length, chunk.avatarid );
+				}
+
+			}
+			break;
+
+		case CP_CHUNK_SET_AVATAR :
+			/* this is a reply packet to a set avatar request. no action is required */
+			break;
+
+		case CP_CHUNK_DIRECT_SND :
+			/* this is a ack for a file send. no action is required */
+			break;
+
+		case CP_CHUNK_RECIEVED :
+			/* this is a ack for a file received. no action is required */
+			break;
+
+		default :
+			purple_debug_error( MXIT_PLUGIN_ID, "Unsupported chunked data packet type received (%i)\n", type );
+			break;
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Handle a redirect sent from the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param url			The redirect information
+ */
+static void mxit_perform_redirect( struct MXitSession* session, const char* url )
+{
+	gchar**		parts;
+	gchar**		host;
+	int			type;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s\n", url );
+
+	/* tokenize the URL string */
+	parts = g_strsplit( url, ";", 0 );
+
+	/* Part 1: protocol://host:port */
+	host = g_strsplit( parts[0], ":", 4 );
+	if ( strcmp( host[0], "socket" ) == 0 ) {
+		/* redirect to a MXit socket proxy */
+		g_strlcpy( session->server, &host[1][2], sizeof( session->server ) );
+		session->port = atoi( host[2] );
+	}
+	else {
+		purple_connection_error( session->con, _( "Cannot perform redirect using the specified protocol" ) );
+		goto redirect_fail;
+	}
+
+	/* Part 2: type of redirect */
+	type = atoi( parts[1] );
+	if ( type == CP_REDIRECT_PERMANENT ) {
+		/* permanent redirect, so save new MXit server and port */
+		purple_account_set_string( session->acc, MXIT_CONFIG_SERVER_ADDR, session->server );
+		purple_account_set_int( session->acc, MXIT_CONFIG_SERVER_PORT, session->port );
+	}
+
+	/* Part 3: message (optional) */
+	if ( parts[2] != NULL )
+		purple_connection_notice( session->con, parts[2] );
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s redirect to %s:%i\n",
+			( type == CP_REDIRECT_PERMANENT ) ? "Permanent" : "Temporary", session->server, session->port );
+
+	/* perform the re-connect to the new MXit server */
+	mxit_reconnect( session );
+
+redirect_fail:
+	g_strfreev( parts );
+	g_strfreev( host );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a success response received from the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param packet		The received packet
+ */
+static int process_success_response( struct MXitSession* session, struct rx_packet* packet )
+{
+	/* ignore ping/poll packets */
+	if ( ( packet->cmd != CP_CMD_PING ) && ( packet->cmd != CP_CMD_POLL ) )
+		session->last_rx = time( NULL );
+
+	/*
+	 * when we pass the packet records to the next level for parsing
+	 * we minus 3 records because 1) the first record is the packet
+	 * type 2) packet reply status 3) the last record is bogus
+	 */
+
+	/* packet command */
+	switch ( packet->cmd ) {
+
+		case CP_CMD_REGISTER :
+				/* fall through, when registeration successful, MXit will auto login */
+		case CP_CMD_LOGIN :
+				/* login response */
+				if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) {
+					mxit_parse_cmd_login( session, &packet->records[2], packet->rcount - 3 );
+				}
+				break;
+
+		case CP_CMD_LOGOUT :
+				/* logout response */
+				session->flags &= ~MXIT_FLAG_LOGGEDIN;
+				purple_account_disconnect( session->acc );
+
+				/* note:
+				 * we do not prompt the user here for a reconnect, because this could be the user
+				 * logging in with his phone. so we just disconnect the account otherwise
+				 * mxit will start to bounce between the phone and pidgin. also could be a valid
+				 * disconnect selected by the user.
+				 */
+				return -1;
+
+		case CP_CMD_CONTACT :
+				/* contact update */
+				mxit_parse_cmd_contact( session, &packet->records[2], packet->rcount - 3 );
+				break;
+
+		case CP_CMD_PRESENCE :
+				/* presence update */
+				mxit_parse_cmd_presence(session, &packet->records[2], packet->rcount - 3 );
+				break;
+
+		case CP_CMD_RX_MSG :
+				/* incoming message (no bogus record) */
+				mxit_parse_cmd_message( session, &packet->records[2], packet->rcount - 2 );
+				break;
+
+		case CP_CMD_NEW_SUB :
+				/* new subscription request */
+				mxit_parse_cmd_new_sub( session, &packet->records[2], packet->rcount - 3 );
+				break;
+
+		case CP_CMD_MEDIA :
+				/* multi-media message */
+				mxit_parse_cmd_media( session, &packet->records[2], packet->rcount - 2 );
+				break;
+
+		case CP_CMD_EXTPROFILE_GET :
+				/* profile update */
+				mxit_parse_cmd_extprofile( session, &packet->records[2], packet->rcount - 2 );
+				break;
+
+		case CP_CMD_MOOD :
+				/* mood update */
+		case CP_CMD_UPDATE :
+				/* update contact information */
+		case CP_CMD_ALLOW :
+				/* allow subscription ack */
+		case CP_CMD_DENY :
+				/* deny subscription ack */
+		case CP_CMD_INVITE :
+				/* invite contact ack */
+		case CP_CMD_REMOVE :
+				/* remove contact ack */
+		case CP_CMD_TX_MSG :
+				/* outgoing message ack */
+		case CP_CMD_STATUS :
+				/* presence update ack */
+		case CP_CMD_GRPCHAT_CREATE :
+				/* create groupchat */
+		case CP_CMD_GRPCHAT_INVITE :
+				/* groupchat invite */
+		case CP_CMD_PING :
+				/* ping reply */
+		case CP_CMD_POLL :
+				/* HTTP poll reply */
+		case CP_CMD_EXTPROFILE_SET :
+				/* profile update */
+		case CP_CMD_SPLASHCLICK :
+				/* splash-screen clickthrough */
+				break;
+
+		default :
+			/* unknown packet */
+			purple_debug_error( MXIT_PLUGIN_ID, "Received unknown client packet (cmd = %i)\n", packet->cmd );
+	}
+
+	return 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Process an error response received from the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param packet		The received packet
+ */
+static int process_error_response( struct MXitSession* session, struct rx_packet* packet )
+{
+	char			errmsg[256];
+	const char*		errdesc;
+
+	/* set the error description to be shown to the user */
+	if ( packet->errmsg )
+		errdesc = packet->errmsg;
+	else
+		errdesc = "An internal MXit server error occurred.";
+
+	purple_debug_info( MXIT_PLUGIN_ID, "Error Reply %i:%s\n", packet->errcode, errdesc );
+
+	if ( packet->errcode == MXIT_ERRCODE_LOGGEDOUT ) {
+		/* we are not currently logged in, so we need to reconnect */
+		purple_connection_error( session->con, _( errmsg ) );
+	}
+
+	/* packet command */
+	switch ( packet->cmd ) {
+
+		case CP_CMD_REGISTER :
+		case CP_CMD_LOGIN :
+				if ( packet->errcode == MXIT_ERRCODE_REDIRECT ) {
+					mxit_perform_redirect( session, packet->errmsg );
+					return 0;
+				}
+				else {
+					sprintf( errmsg, "Login error: %s (%i)", errdesc, packet->errcode );
+					purple_connection_error( session->con, _( errmsg ) );
+					return -1;
+				}
+		case CP_CMD_LOGOUT :
+				sprintf( errmsg, "Logout error: %s (%i)", errdesc, packet->errcode );
+				purple_connection_error_reason( session->con, PURPLE_CONNECTION_ERROR_NAME_IN_USE, _( errmsg ) );
+				return -1;
+		case CP_CMD_CONTACT :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_RX_MSG :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_TX_MSG :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Sending Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_STATUS :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Status Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_MOOD :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Mood Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_KICK :
+				/*
+				 * the MXit server sends this packet if we were idle for too long.
+				 * to stop the server from closing this connection we need to resend
+				 * the login packet.
+				 */
+				mxit_send_login( session );
+				break;
+		case CP_CMD_INVITE :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Invitation Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_REMOVE :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Removal Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_ALLOW :
+		case CP_CMD_DENY :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Subscription Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_UPDATE :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Update Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_MEDIA :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "File Transfer Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_GRPCHAT_CREATE :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Cannot create MultiMx room" ), _( errdesc ) );
+				break;
+		case CP_CMD_GRPCHAT_INVITE :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "MultiMx Invitation Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_EXTPROFILE_GET :
+		case CP_CMD_EXTPROFILE_SET :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Profile Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_SPLASHCLICK :
+				/* ignore error */
+				break;
+		case CP_CMD_PING :
+		case CP_CMD_POLL :
+				break;
+		default :
+				mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Error" ), _( errdesc ) );
+				break;
+	}
+
+	return 0;
+}
+
+
+/*========================================================================================================================
+ * Low-level Packet receive
+ */
+
+#ifdef	DEBUG_PROTOCOL
+/*------------------------------------------------------------------------
+ * Dump a received packet structure.
+ *
+ *  @param p			The received packet
+ */
+static void dump_packet( struct rx_packet* p )
+{
+	struct record*		r	= NULL;
+	struct field*		f	= NULL;
+	int					i;
+	int					j;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "PACKET DUMP: (%i records)\n", p->rcount );
+
+	for ( i = 0; i < p->rcount; i++ ) {
+		r = p->records[i];
+		purple_debug_info( MXIT_PLUGIN_ID, "RECORD: (%i fields)\n", r->fcount );
+
+		for ( j = 0; j < r->fcount; j++ ) {
+			f = r->fields[j];
+			purple_debug_info( MXIT_PLUGIN_ID, "\tFIELD: (len=%i) '%s' \n", f->len, f->data );
+		}
+	}
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * Free up memory used by a packet structure.
+ *
+ *  @param p			The received packet
+ */
+static void free_rx_packet( struct rx_packet* p )
+{
+	struct record*		r	= NULL;
+	struct field*		f	= NULL;
+	int					i;
+	int					j;
+
+	for ( i = 0; i < p->rcount; i++ ) {
+		r = p->records[i];
+
+		for ( j = 0; j < r->fcount; j++ ) {
+			g_free( f );
+		}
+		g_free( r->fields );
+		g_free( r );
+	}
+	g_free( p->records );
+}
+
+
+/*------------------------------------------------------------------------
+ * Add a new field to a record.
+ *
+ *  @param r			Parent record object
+ *  @return				The newly created field
+ */
+static struct field* add_field( struct record* r )
+{
+	struct field*	field;
+
+	field = g_new0( struct field, 1 );
+
+	r->fields = realloc( r->fields, sizeof( struct field* ) * ( r->fcount + 1 ) );
+	r->fields[r->fcount] = field;
+	r->fcount++;
+
+	return field;
+}
+
+
+/*------------------------------------------------------------------------
+ * Add a new record to a packet.
+ *
+ *  @param p			The packet object
+ *  @return				The newly created record
+ */
+static struct record* add_record( struct rx_packet* p )
+{
+	struct record*	rec;
+
+	rec = g_new0( struct record, 1 );
+
+	p->records = realloc( p->records, sizeof( struct record* ) * ( p->rcount + 1 ) );
+	p->records[p->rcount] = rec;
+	p->rcount++;
+
+	return rec;
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse the received byte stream into a proper client protocol packet.
+ *
+ *  @param session		The MXit session object
+ *  @return				Success (0) or Failure (!0)
+ */
+int mxit_parse_packet( struct MXitSession* session )
+{
+	struct rx_packet	packet;
+	struct record*		rec;
+	struct field*		field;
+	gboolean			pbreak;
+	unsigned int		i;
+	int					res	= 0;
+
+#ifdef	DEBUG_PROTOCOL
+	purple_debug_info( MXIT_PLUGIN_ID, "Received packet (%i bytes)\n", session->rx_i );
+	dump_bytes( session, session->rx_dbuf, session->rx_i );
+#endif
+
+	i = 0;
+	while ( i < session->rx_i ) {
+
+		/* create first record and field */
+		rec = NULL;
+		field = NULL;
+		memset( &packet, 0x00, sizeof( struct rx_packet ) );
+		rec = add_record( &packet );
+		pbreak = FALSE;
+
+		/* break up the received packet into fields and records for easy parsing */
+		while ( ( i < session->rx_i ) && ( !pbreak ) ) {
+
+			switch ( session->rx_dbuf[i] ) {
+				case CP_SOCK_REC_TERM :
+						/* new record */
+						if ( packet.rcount == 1 ) {
+							/* packet command */
+							packet.cmd = atoi( packet.records[0]->fields[0]->data );
+						}
+						else if ( packet.rcount == 2 ) {
+							/* special case: binary multimedia packets should not be parsed here */
+							if ( packet.cmd == CP_CMD_MEDIA ) {
+								/* add the chunked to new record */
+								rec = add_record( &packet );
+								field = add_field( rec );
+								field->data = &session->rx_dbuf[i + 1];
+								field->len = session->rx_i - i;
+								/* now skip the binary data */
+								res = get_chunk_len( field->data );
+								/* determine if we have more packets */
+								if ( res + 6 + i < session->rx_i ) {
+									/* we have more than one packet in this stream */
+									i += res + 6;
+									pbreak = TRUE;
+								}
+								else {
+									i = session->rx_i;
+								}
+							}
+						}
+						else if ( !field ) {
+							field = add_field( rec );
+							field->data = &session->rx_dbuf[i];
+						}
+						session->rx_dbuf[i] = '\0';
+						rec = add_record( &packet );
+						field = NULL;
+
+						break;
+				case CP_FLD_TERM :
+						/* new field */
+						session->rx_dbuf[i] = '\0';
+						if ( !field ) {
+							field = add_field( rec );
+							field->data = &session->rx_dbuf[i];
+						}
+						field = NULL;
+						break;
+				case CP_PKT_TERM :
+						/* packet is done! */
+						session->rx_dbuf[i] = '\0';
+						pbreak = TRUE;
+						break;
+				default :
+						/* skip non special characters */
+						if ( !field ) {
+							field = add_field( rec );
+							field->data = &session->rx_dbuf[i];
+						}
+						field->len++;
+						break;
+			}
+
+			i++;
+		}
+
+		if ( packet.rcount < 2 ) {
+			/* bad packet */
+			purple_connection_error( session->con, _( "Invalid packet received from MXit." ) );
+			free_rx_packet( &packet );
+			continue;
+		}
+
+		session->rx_dbuf[session->rx_i] = '\0';
+		packet.errcode = atoi( packet.records[1]->fields[0]->data );
+
+		purple_debug_info( MXIT_PLUGIN_ID, "Packet received CMD:%i (%i)\n", packet.cmd, packet.errcode );
+#ifdef	DEBUG_PROTOCOL
+		/* debug */
+		dump_packet( &packet );
+#endif
+
+		/* reset the out ack */
+		if ( session->outack == packet.cmd ) {
+			/* outstanding ack received from mxit server */
+			session->outack = 0;
+		}
+
+		/* check packet status */
+		if ( packet.errcode != MXIT_ERRCODE_SUCCESS ) {
+			/* error reply! */
+			if ( ( packet.records[1]->fcount > 1 ) && ( packet.records[1]->fields[1]->data ) )
+				packet.errmsg = packet.records[1]->fields[1]->data;
+			else
+				packet.errmsg = NULL;
+
+			res = process_error_response( session, &packet );
+		}
+		else {
+			/* success reply! */
+			res = process_success_response( session, &packet );
+		}
+
+		/* free up the packet resources */
+		free_rx_packet( &packet );
+	}
+
+	if ( session->outack == 0 )
+			mxit_manage_queue( session );
+
+	return res;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback when data is received from the MXit server.
+ *
+ *  @param user_data		The MXit session object
+ *  @param source			The file-descriptor on which data was received
+ *  @param cond				Condition which caused the callback (PURPLE_INPUT_READ)
+ */
+void mxit_cb_rx( gpointer user_data, gint source, PurpleInputCondition cond )
+{
+	struct MXitSession*	session		= (struct MXitSession*) user_data;
+	char				ch;
+	int					res;
+	int					len;
+
+	if ( session->rx_state == RX_STATE_RLEN ) {
+		/* we are reading in the packet length */
+		len = read( session->fd, &ch, 1 );
+		if ( len < 0 ) {
+			/* connection error */
+			purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x01)" ) );
+			return;
+		}
+		else if ( len == 0 ) {
+			/* connection closed */
+			purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x02)" ) );
+			return;
+		}
+		else {
+			/* byte read */
+			if ( ch == CP_REC_TERM ) {
+				/* the end of the length record found */
+				session->rx_lbuf[session->rx_i] = '\0';
+				session->rx_res = atoi( &session->rx_lbuf[3] );
+				if ( session->rx_res > CP_MAX_PACKET ) {
+					purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x03)" ) );
+				}
+				session->rx_state = RX_STATE_DATA;
+				session->rx_i = 0;
+			}
+			else {
+				/* still part of the packet length record */
+				session->rx_lbuf[session->rx_i] = ch;
+				session->rx_i++;
+				if ( session->rx_i >= sizeof( session->rx_lbuf ) ) {
+					/* malformed packet length record (too long) */
+					purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x04)" ) );
+					return;
+				}
+			}
+		}
+	}
+	else if ( session->rx_state == RX_STATE_DATA ) {
+		/* we are reading in the packet data */
+		len = read( session->fd, &session->rx_dbuf[session->rx_i], session->rx_res );
+		if ( len < 0 ) {
+			/* connection error */
+			purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x05)" ) );
+			return;
+		}
+		else if ( len == 0 ) {
+			/* connection closed */
+			purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x06)" ) );
+			return;
+		}
+		else {
+			/* data read */
+			session->rx_i += len;
+			session->rx_res -= len;
+
+			if ( session->rx_res == 0 ) {
+				/* ok, so now we have read in the whole packet */
+				session->rx_state = RX_STATE_PROC;
+			}
+		}
+	}
+
+	if ( session->rx_state == RX_STATE_PROC ) {
+		/* we have a full packet, which we now need to process */
+		res = mxit_parse_packet( session );
+
+		if ( res == 0 ) {
+			/* we are still logged in */
+			session->rx_state = RX_STATE_RLEN;
+			session->rx_res = 0;
+			session->rx_i = 0;
+		}
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Log the user off MXit and close the connection
+ *
+ *  @param session		The MXit session object
+ */
+void mxit_close_connection( struct MXitSession* session )
+{
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_close_connection\n" );
+
+	if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
+		/* we are already closed */
+		return;
+	}
+	else if ( session->flags & MXIT_FLAG_LOGGEDIN ) {
+		/* we are currently logged in so we need to send a logout packet */
+		if ( !session->http ) {
+			mxit_send_logout( session );
+		}
+		session->flags &= ~MXIT_FLAG_LOGGEDIN;
+	}
+	session->flags &= ~MXIT_FLAG_CONNECTED;
+
+	/* cancel outstanding HTTP request */
+	if ( ( session->http ) && ( session->http_out_req ) ) {
+		purple_util_fetch_url_cancel( (PurpleUtilFetchUrlData*) session->http_out_req );
+		session->http_out_req = NULL;
+	}
+
+	/* remove the input cb function */
+	if ( session->con->inpa ) {
+		purple_input_remove( session->con->inpa );
+		session->con->inpa = 0;
+	}
+
+	/* remove HTTP poll timer */
+	if ( session->http_timer_id > 0 )
+		purple_timeout_remove( session->http_timer_id );
+
+	/* remove queue manager timer */
+	if ( session->q_timer > 0 )
+		purple_timeout_remove( session->q_timer );
+
+	/* remove all groupchat rooms */
+	while ( session->rooms != NULL ) {
+		struct multimx* multimx = (struct multimx *) session->rooms->data;
+
+		session->rooms = g_list_remove( session->rooms, multimx );
+
+		free( multimx );
+	}
+	g_list_free( session->rooms );
+	session->rooms = NULL;
+
+	/* remove all rx chats names */
+	while ( session->active_chats != NULL ) {
+		char* chat = (char*) session->active_chats->data;
+
+		session->active_chats = g_list_remove( session->active_chats, chat );
+
+		g_free( chat );
+	}
+	g_list_free( session->active_chats );
+	session->active_chats = NULL;
+
+	/* free profile information */
+	if ( session->profile )
+		free( session->profile );
+
+	/* free custom emoticons */
+	mxit_free_emoticon_cache( session );
+
+	/* free allocated memory */
+	g_free( session->encpwd );
+	session->encpwd = NULL;
+
+	/* flush all the commands still in the queue */
+	flush_queue( session );
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/protocol.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,304 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- MXit client protocol implementation --
+ *
+ *				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
+ */
+
+#ifndef		_MXIT_PROTO_H_
+#define		_MXIT_PROTO_H_
+
+
+/* Client protocol constants */
+#define		CP_SOCK_REC_TERM		'\x00'				/* socket record terminator */
+#define		CP_HTTP_REC_TERM		'\x26'				/* http record terminator '&' */
+#define		CP_FLD_TERM				'\x01'				/* field terminator */
+#define		CP_PKT_TERM				'\x02'				/* packet terminator */
+
+
+#define		CP_MAX_PACKET			( 1024 * 1024 )		/* maximum client protocol packet size (1 MiB) */
+#define		CP_MAX_FILESIZE			( 150 * 1000 )		/* maximum client protocol file transfer size (150 KB) */
+#define		MXIT_EMOTICON_SIZE		18					/* icon size for custom emoticons */
+#define		CP_MAX_STATUS_MSG		250					/* maximum status message length (in characters) */
+
+/* Avatars */
+#define		MXIT_AVATAR_SIZE		96					/* default avatar image size 96x96 */
+#define		MXIT_AVATAR_TYPE		"PNG"				/* request avatars in this file type (only a suggestion) */
+#define		MXIT_AVATAR_BITDEPT		24					/* request avatars with this bit depth (only a suggestion) */
+
+/* Protocol error codes */
+#define		MXIT_ERRCODE_SUCCESS	0
+#define		MXIT_ERRCODE_REDIRECT	16
+#define		MXIT_ERRCODE_LOGGEDOUT	42
+
+/* MXit client features */
+#define		MXIT_CF_NONE			0x000000
+#define		MXIT_CF_FORMS			0x000001
+#define		MXIT_CF_FILE_TRANSFER	0x000002
+#define		MXIT_CF_CAMERA			0x000004
+#define		MXIT_CF_COMMANDS		0x000008
+#define		MXIT_CF_SMS				0x000010
+#define		MXIT_CF_FILE_ACCESS		0x000020
+#define		MXIT_CF_MIDP2			0x000040
+#define		MXIT_CF_SKINS			0x000080
+#define		MXIT_CF_AUDIO			0x000100
+#define		MXIT_CF_ENCRYPTION		0x000200
+#define		MXIT_CF_VOICE_REC		0x000400
+#define		MXIT_CF_VECTOR_GFX		0x000800
+#define		MXIT_CF_IMAGES			0x001000
+#define		MXIT_CF_MARKUP			0x002000
+#define		MXIT_CF_VIBES			0x004000
+#define		MXIT_CF_SELECT_CONTACT	0x008000
+#define		MXIT_CF_CUSTOM_EMO		0x010000
+#define		MXIT_CF_ALERT_PROFILES	0x020000
+#define		MXIT_CF_EXT_MARKUP		0x040000
+#define		MXIT_CF_PLAIN_PWD		0x080000
+#define		MXIT_CF_NO_GATEWAYS		0x100000
+
+/* Client features supported by this implementation */
+#define		MXIT_CP_FEATURES		( MXIT_CF_FILE_TRANSFER | MXIT_CF_FILE_ACCESS | MXIT_CF_AUDIO | MXIT_CF_MARKUP | MXIT_CF_EXT_MARKUP | MXIT_CF_NO_GATEWAYS | MXIT_CF_IMAGES | MXIT_CF_COMMANDS | MXIT_CF_VIBES | MXIT_CF_MIDP2 )
+
+
+#define		MXIT_PING_INTERVAL		( 5 * 60 )				/* ping the server after X seconds of being idle (5 minutes) */
+#define		MXIT_ACK_TIMEOUT		( 30 )					/* timeout after waiting X seconds for an ack from the server (30 seconds) */
+
+/* MXit client version */
+#define		MXIT_CP_DISTCODE		"P"						/* client distribution code (magic, do not touch!) */
+#define		MXIT_CP_RELEASE			"5.9.0"					/* client protocol release version supported */
+#define		MXIT_CP_ARCH			"Y"						/* client architecture series (Y not for Yoda but for PC-client) */
+#define		MXIT_CLIENT_ID			"LP"					/* client ID as specified by MXit */
+#define		MXIT_CP_PLATFORM		"PURPLE"				/* client platform */
+#define		MXIT_CP_VERSION			MXIT_CP_DISTCODE"-"MXIT_CP_RELEASE"-"MXIT_CP_ARCH"-"MXIT_CP_PLATFORM
+
+/* set operating system name */
+#if defined( __APPLE__ )
+#define		MXIT_CP_OS				"apple"
+#elif defined( _WIN32 )
+#define		MXIT_CP_OS				"windows"
+#elif defined( __linux__ )
+#define		MXIT_CP_OS				"linux"
+#else
+#define		MXIT_CP_OS				"unknown"
+#endif
+
+/* Client capabilities */
+#define		MXIT_CP_CAP				"utf8=true;cid="MXIT_CLIENT_ID
+
+/* Client settings */
+#define		MAX_QUEUE_SIZE			( 1 << 4 )				/* tx queue size (16 packets) */
+#define		MXIT_POPUP_WIN_NAME		"MXit Notification"		/* popup window name */
+#define		MXIT_MAX_ATTRIBS		10						/* maximum profile attributes supported */
+#define		MXIT_DEFAULT_LOCALE		"en"					/* default locale setting */
+#define		MXIT_DEFAULT_LOC		"planetpurple"			/* the default location for registration */
+
+/* Client protocol commands */
+#define		CP_CMD_LOGIN			0x0001					/* (1) login */
+#define		CP_CMD_LOGOUT			0x0002					/* (2) logout */
+#define		CP_CMD_CONTACT			0x0003					/* (3) get contacts */
+#define		CP_CMD_UPDATE			0x0005					/* (5) update contact information */
+#define		CP_CMD_INVITE			0x0006					/* (6) subscribe to new contact */
+#define		CP_CMD_PRESENCE			0x0007					/* (7) get presence */
+#define		CP_CMD_REMOVE			0x0008					/* (8) remove contact */
+#define		CP_CMD_RX_MSG			0x0009					/* (9) get new messages */
+#define		CP_CMD_TX_MSG			0x000A					/* (10) send new message */
+#define		CP_CMD_REGISTER			0x000B					/* (11) register */
+//#define	CP_CMD_PROFILE_SET		0x000C					/* (12) set profile (DEPRECATED see CP_CMD_EXTPROFILE_SET) */
+#define		CP_CMD_POLL				0x0011					/* (17) poll the HTTP server for an update */
+//#define	CP_CMD_PROFILE_GET		0x001A					/* (26) get profile (DEPRECATED see CP_CMD_EXTPROFILE_GET) */
+#define		CP_CMD_MEDIA			0x001B					/* (27) get multimedia message */
+#define		CP_CMD_SPLASHCLICK		0x001F					/* (31) splash-screen clickthrough */
+#define		CP_CMD_STATUS			0x0020					/* (32) set shown presence & status */
+#define		CP_CMD_MOOD				0x0029					/* (41) set mood */
+#define		CP_CMD_KICK				0x002B					/* (43) login kick */
+#define		CP_CMD_GRPCHAT_CREATE	0x002C					/* (44) create new groupchat */
+#define		CP_CMD_GRPCHAT_INVITE	0x002D					/* (45) add new groupchat member */
+#define		CP_CMD_NEW_SUB			0x0033					/* (51) get new subscription */
+#define		CP_CMD_ALLOW			0x0034					/* (52) allow subscription */
+#define		CP_CMD_DENY				0x0037					/* (55) deny subscription */
+#define		CP_CMD_EXTPROFILE_GET	0x0039					/* (57) get extended profile */
+#define		CP_CMD_EXTPROFILE_SET	0x003A					/* (58) set extended profile */
+#define		CP_CMD_PING				0x03E8					/* (1000) ping (keepalive) */
+
+/* HTTP connection */
+#define		MXIT_HTTP_POLL_MIN		7						/* minimum time between HTTP polls (seconds) */
+#define		MXIT_HTTP_POLL_MAX		( 10 * 60 )				/* maximum time between HTTP polls (seconds) */
+
+/* receiver states */
+#define		RX_STATE_RLEN			0x01					/* reading packet length section */
+#define		RX_STATE_DATA			0x02					/* reading packet data section */
+#define		RX_STATE_PROC			0x03					/* process read data */
+
+/* message flags */
+#define		CP_MSG_ENCRYPTED		0x0010					/* message is encrypted */
+#define		CP_MSG_MARKUP			0x0200					/* message may contain markup */
+#define		CP_MSG_EMOTICON			0x0400					/* message may contain custom emoticons */
+
+/* redirect types */
+#define		CP_REDIRECT_PERMANENT	1						/* permanent redirect */
+#define		CP_REDIRECT_TEMPORARY	2						/* temporary redirect */
+
+/* message tx types */
+#define		CP_MSGTYPE_NORMAL		0x01					/* normal message */
+#define		CP_MSGTYPE_CHAT			0x02					/* chat message */
+#define		CP_MSGTYPE_HEADLINE		0x03					/* headline message */
+#define		CP_MSGTYPE_ERROR		0x04					/* error message */
+#define		CP_MSGTYPE_GROUPCHAT	0x05					/* groupchat message */
+#define		CP_MSGTYPE_FORM			0x06					/* mxit custom form */
+#define		CP_MSGTYPE_COMMAND		0x07					/* mxit command */
+
+
+/* extended profile attribute fields */
+#define		CP_PROFILE_BIRTHDATE	"birthdate"				/* Birthdate (String - ISO 8601 format) */
+#define		CP_PROFILE_GENDER		"gender"				/* Gender (Boolean - 0=female, 1=male) */
+#define		CP_PROFILE_HIDENUMBER	"hidenumber"			/* Hide Number (Boolean - 0=false, 1=true) */
+#define		CP_PROFILE_FULLNAME		"fullname"				/* Fullname (UTF8 String) */
+#define		CP_PROFILE_STATUS		"statusmsg"				/* Status Message (UTF8 String) */
+#define		CP_PROFILE_PREVSTATUS	"prevstatusmsgs"		/* Previous Status Messages (UTF8 String) */
+#define		CP_PROFILE_AVATAR		"avatarid"				/* Avatar ID (String) */
+#define		CP_PROFILE_MODIFIED		"lastmodified"			/* Last-Modified timestamp */
+#define		CP_PROFILE_TITLE		"title"					/* Title (UTF8 String) */
+#define		CP_PROFILE_FIRSTNAME	"firstname"				/* First name (UTF8 String) */
+#define		CP_PROFILE_LASTNAME		"lastname"				/* Last name (UTF8 String) */
+#define		CP_PROFILE_EMAIL		"email"					/* Email address (UTF8 String) */
+#define		CP_PROFILE_MOBILENR		"mobilenumber"			/* Mobile Number (UTF8 String) */
+
+/* extended profile field types */
+#define		CP_PROF_TYPE_BOOL		0x02					/* boolean profile attribute type */
+#define		CP_PROF_TYPE_INT		0x05					/* integer profile attribute type */
+#define		CP_PROF_TYPE_UTF8		0x0A					/* UTF8 string profile attribute type */
+#define		CP_PROF_TYPE_DATE		0x0B					/* date-time profile attribute type */
+
+
+/* define this to enable protocol debugging (very verbose logging) */
+#define		DEBUG_PROTOCOL
+
+
+/* ======================================================================================= */
+
+struct MXitSession;
+
+/*------------------------------------------*/
+
+struct field {
+	char*				data;
+	int					len;
+};
+
+struct record {
+	struct field**		fields;
+	int					fcount;
+};
+
+struct rx_packet {
+	int					cmd;
+	int					errcode;
+	char*				errmsg;
+	struct record**		records;
+	int					rcount;
+};
+
+struct tx_packet {
+	int					cmd;
+	char				header[256];
+	int					headerlen;
+	char*				data;
+	int					datalen;
+};
+
+/*------------------------------------------*/
+
+
+/*
+ * A received message data object
+ */
+struct RXMsgData {
+	struct MXitSession*		session;					/* MXit session object */
+	char*					from;						/* the sender's name */
+	time_t					timestamp;					/* time at which the message was sent */
+	GString*				msg;						/* newly created message converted to libPurple formatting */
+	gboolean				got_img;					/* flag to say if this message got any images/emoticons embedded */
+	short					img_count;					/* the amount of images/emoticons still outstanding for the message */
+	int						chatid;						/* multimx chatroom id */
+	int						flags;						/* libPurple conversation flags */
+	gboolean				converted;					/* true if the message has been completely parsed and converted to libPurple markup */
+	gboolean				processed;					/* the message has been processed completely and should be freed up */
+};
+
+
+
+/*
+ * The packet transmission queue.
+ */
+struct tx_queue {
+	struct tx_packet*	packets[MAX_QUEUE_SIZE];		/* array of packet pointers */
+	int					count;							/* number of packets queued */
+	int					rd_i;							/* queue current read index (queue offset for reading a packet) */
+	int					wr_i;							/* queue current write index (queue offset for adding new packet) */
+};
+
+
+/* ======================================================================================= */
+
+void mxit_popup( int type, const char* heading, const char* message );
+void mxit_strip_domain( char* username );
+gboolean find_active_chat( const GList* chats, const char* who );
+
+void mxit_cb_rx( gpointer data, gint source, PurpleInputCondition cond );
+gboolean mxit_manage_queue( gpointer user_data );
+gboolean mxit_manage_polling( gpointer user_data );
+
+void mxit_send_register( struct MXitSession* session );
+void mxit_send_login( struct MXitSession* session );
+void mxit_send_logout( struct MXitSession* session );
+void mxit_send_ping( struct MXitSession* session );
+void mxit_send_poll( struct MXitSession* session );
+
+void mxit_send_presence( struct MXitSession* session, int presence, const char* statusmsg );
+void mxit_send_mood( struct MXitSession* session, int mood );
+void mxit_send_message( struct MXitSession* session, const char* to, const char* msg, gboolean parse_markup );
+
+void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes );
+void mxit_send_extprofile_request( struct MXitSession* session, const char* username, unsigned int nr_attrib, const char* attribute[] );
+
+void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname );
+void mxit_send_remove( struct MXitSession* session, const char* username );
+void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias );
+void mxit_send_deny_sub( struct MXitSession* session, const char* username );
+void mxit_send_update_contact( struct MXitSession* session, const char* username, const char* alias, const char* groupname );
+void mxit_send_splashclick( struct MXitSession* session, const char* splashid );
+
+void mxit_send_file( struct MXitSession* session, const char* username, const char* filename, const unsigned char* buf, int buflen );
+void mxit_send_file_reject( struct MXitSession* session, const char* fileid );
+void mxit_send_file_accept( struct MXitSession* session, const char* fileid, int filesize, int offset );
+void mxit_send_file_received( struct MXitSession* session, const char* fileid, short status );
+void mxit_set_avatar( struct MXitSession* session, const unsigned char* avatar, int avatarlen );
+void mxit_get_avatar( struct MXitSession* session, const char* mxitId, const char* avatarId );
+
+void mxit_send_groupchat_create( struct MXitSession* session, const char* groupname, int nr_usernames, const char* usernames[] );
+void mxit_send_groupchat_invite( struct MXitSession* session, const char* roomid, int nr_usernames, const char* usernames[] );
+
+int mxit_parse_packet( struct MXitSession* session );
+void dump_bytes( struct MXitSession* session, const char* buf, int len );
+void mxit_close_connection( struct MXitSession* session );
+
+
+#endif		/* _MXIT_PROTO_H_ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/roster.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,722 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- user roster management (mxit contacts) --
+ *
+ *				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	<unistd.h>
+#include	<string.h>
+
+#include	"purple.h"
+
+#include	"protocol.h"
+#include	"mxit.h"
+#include	"roster.h"
+
+
+struct contact_invite {
+	struct MXitSession*		session;		/* MXit session object */
+	struct contact*			contact;		/* The contact performing the invite */
+};
+
+
+/*========================================================================================================================
+ * Presence / Status
+ */
+
+/* statuses (reference: libpurple/status.h) */
+static struct status
+{
+	PurpleStatusPrimitive	primative;
+	int						mxit;
+	const char*				id;
+	const char*				name;
+} const mxit_statuses[] = {
+		/*	primative,						no,							id,			name					*/
+		{	PURPLE_STATUS_OFFLINE,			MXIT_PRESENCE_OFFLINE,		"offline",	"Offline"			},	/* 0 */
+		{	PURPLE_STATUS_AVAILABLE,		MXIT_PRESENCE_ONLINE,		"online",	"Available"			},	/* 1 */
+		{	PURPLE_STATUS_AWAY,				MXIT_PRESENCE_AWAY,			"away",		"Away"				},	/* 2 */
+		{	PURPLE_STATUS_AVAILABLE,		MXIT_PRESENCE_AVAILABLE,	"chat",		"Chatty"			},	/* 3 */
+		{	PURPLE_STATUS_UNAVAILABLE,		MXIT_PRESENCE_DND,			"dnd",		"Do Not Disturb"	}	/* 4 */
+};
+
+
+/*------------------------------------------------------------------------
+ * Return list of supported statuses. (see status.h)
+ *
+ *  @param account	The MXit account object
+ *  @return			List of PurpleStatusType
+ */
+GList* mxit_status_types( PurpleAccount* account )
+{
+	GList*				statuslist	= NULL;
+	PurpleStatusType*	type;
+	unsigned int		i;
+
+	for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) {
+		const struct status* status = &mxit_statuses[i];
+
+		/* add mxit status (reference: "libpurple/status.h") */
+		type = purple_status_type_new_with_attrs( status->primative, status->id, status->name, TRUE, TRUE, FALSE,
+					"message", _( "Message" ), purple_value_new( PURPLE_TYPE_STRING ),
+					NULL );
+
+		statuslist = g_list_append( statuslist, type );
+	}
+
+	return statuslist;
+}
+
+
+/*------------------------------------------------------------------------
+ * Returns the MXit presence code, given the unique status ID.
+ *
+ *  @param id		The status ID
+ *  @return			The MXit presence code
+ */
+int mxit_convert_presence( const char* id )
+{
+	unsigned int	i;
+
+	for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) {
+		if ( strcmp( mxit_statuses[i].id, id ) == 0 )	/* status found! */
+			return mxit_statuses[i].mxit;
+	}
+
+	return -1;
+}
+
+
+/*------------------------------------------------------------------------
+ * Returns the MXit presence as a string, given the MXit presence ID.
+ *
+ *  @param no		The MXit presence I (see above)
+ *  @return			The presence as a text string
+ */
+const char* mxit_convert_presence_to_name( short no )
+{
+	unsigned int	i;
+
+	for ( i = 0; i < ARRAY_SIZE( mxit_statuses ); i++ ) {
+		if ( mxit_statuses[i].mxit == no )				/* status found! */
+			return _( mxit_statuses[i].name );
+	}
+
+	return "";
+}
+
+
+/*========================================================================================================================
+ * Moods
+ */
+
+/*------------------------------------------------------------------------
+ * Returns the MXit mood as a string, given the MXit mood's ID.
+ *
+ *  @param id		The MXit mood ID (see roster.h)
+ *  @return			The mood as a text string
+ */
+const char* mxit_convert_mood_to_name( short id )
+{
+	switch ( id ) {
+		case MXIT_MOOD_ANGRY :
+				return _( "Angry" );
+		case MXIT_MOOD_EXCITED :
+				return _( "Excited" );
+		case MXIT_MOOD_GRUMPY :
+				return _( "Grumpy" );
+		case MXIT_MOOD_HAPPY :
+				return _( "Happy" );
+		case MXIT_MOOD_INLOVE :
+				return _( "In Love" );
+		case MXIT_MOOD_INVINCIBLE :
+				return _( "Invincible" );
+		case MXIT_MOOD_SAD :
+				return _( "Sad" );
+		case MXIT_MOOD_HOT :
+				return _( "Hot" );
+		case MXIT_MOOD_SICK :
+				return _( "Sick" );
+		case MXIT_MOOD_SLEEPY :
+				return _( "Sleepy" );
+		case MXIT_MOOD_NONE :
+		default :
+				return "";
+	}
+}
+
+
+/*========================================================================================================================
+ * Subscription Types
+ */
+ 
+/*------------------------------------------------------------------------
+ * Returns a Contact subscription type as a string.
+ *
+ *  @param subtype	The subscription type
+ *  @return			The subscription type as a text string
+ */
+const char* mxit_convert_subtype_to_name( short subtype )
+{
+	switch ( subtype ) {
+		case MXIT_SUBTYPE_BOTH :
+				return _( "Both" );
+		case MXIT_SUBTYPE_PENDING :
+				return _( "Pending" );
+		case MXIT_SUBTYPE_ASK :
+				return _( "Invited" );
+		case MXIT_SUBTYPE_REJECTED :
+				return _( "Rejected" );
+		case MXIT_SUBTYPE_DELETED :
+				return _( "Deleted" );
+		case MXIT_SUBTYPE_NONE :
+				return _( "None" );
+		default :
+				return "";
+	}
+}
+
+
+/*========================================================================================================================
+ * Calls from the MXit Protocol layer
+ */
+
+#if	0
+/*------------------------------------------------------------------------
+ * Dump a contact's info the the debug console.
+ *
+ *  @param contact		The contact
+ */
+static void dump_contact( struct contact* contact )
+{
+	purple_debug_info( MXIT_PLUGIN_ID, "CONTACT: name='%s', alias='%s', group='%s', type='%i', presence='%i', mood='%i'\n",
+						contact->username, contact->alias, contact->groupname, contact->type, contact->presence, contact->mood );
+}
+#endif
+
+
+#if	0
+/*------------------------------------------------------------------------
+ * Move a buddy from one group to another
+ *
+ * @param buddy		the buddy to move between groups
+ * @param group		the new group to move the buddy to
+ */
+static PurpleBuddy* mxit_update_buddy_group( struct MXitSession* session, PurpleBuddy* buddy, PurpleGroup* group )
+{
+	struct contact*		contact			= NULL;
+	PurpleGroup*		current_group	= purple_buddy_get_group( buddy );
+	PurpleBuddy*		newbuddy		= NULL;
+
+	/* make sure the groups actually differs */
+	if ( strcmp( current_group->name, group->name ) != 0 ) {
+		/* groupnames does not match, so we need to make the update */
+
+		purple_debug_info( MXIT_PLUGIN_ID, "Moving '%s' from group '%s' to '%s'\n", buddy->alias, current_group->name, group->name );
+
+		/*
+		 * XXX: libPurple does not currently provide an API to change or rename the group name
+		 * for a specific buddy. One option is to remove the buddy from the list and re-adding
+		 * him in the new group, but by doing that makes the buddy go offline and then online
+		 * again. This is really not ideal and very iretating, but how else then?
+		 */
+
+		/* create new buddy */
+		newbuddy = purple_buddy_new( session->acc, buddy->name, buddy->alias );
+		newbuddy->proto_data = buddy->proto_data;
+		buddy->proto_data = NULL;
+
+		/* remove the buddy */
+		purple_blist_remove_buddy( buddy );
+
+		/* add buddy */
+		purple_blist_add_buddy( newbuddy, NULL, group, NULL );
+
+		/* now re-instate his presence again */
+		contact = newbuddy->proto_data;
+		if ( contact ) {
+
+			/* update the buddy's status (reference: "libpurple/prpl.h") */
+			if ( contact->statusMsg )
+				purple_prpl_got_user_status( session->acc, newbuddy->name, mxit_statuses[contact->presence].id, "message", contact->statusMsg, NULL );
+			else
+				purple_prpl_got_user_status( session->acc, newbuddy->name, mxit_statuses[contact->presence].id, NULL );
+
+			/* update avatar */
+			if ( contact->avatarId ) {
+				mxit_get_avatar( session, newbuddy->name, contact->avatarId );
+				g_free( contact->avatarId );
+				contact->avatarId = NULL;
+			}
+		}
+
+		return newbuddy;
+	}
+	else
+		return buddy;
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * A contact update packet was received from the MXit server, so update the buddy's
+ * information.
+ *
+ *  @param session		The MXit session object
+ *  @param contact		The contact
+ */
+void mxit_update_contact( struct MXitSession* session, struct contact* contact )
+{
+	PurpleBuddy*		buddy	= NULL;
+	PurpleGroup*		group	= NULL;
+	const char*			id		= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_contact: user='%s' alias='%s' group='%s'\n", contact->username, contact->alias, contact->groupname );
+
+	/*
+	 * libPurple requires all contacts to be in a group.
+	 * So if this MXit contact isn't in a group, pretend it is.
+	 */
+	if ( *contact->groupname == '\0' ) {
+		strcpy( contact->groupname, MXIT_DEFAULT_GROUP );
+	}
+
+	/* find or create a group for this contact */
+	group = purple_find_group( contact->groupname );
+	if ( !group )
+		group = purple_group_new( contact->groupname );
+
+	/* see if the buddy is not in the group already */
+	buddy = purple_find_buddy_in_group( session->acc, contact->username, group );
+	if ( !buddy ) {
+		/* buddy not found in the group */
+
+		/* lets try finding him in all groups */
+		buddy = purple_find_buddy( session->acc, contact->username );
+		if ( buddy ) {
+			/* ok, so we found him in another group. to switch him between groups we must delete him and add him again. */
+			purple_blist_remove_buddy( buddy );
+			buddy = NULL;
+		}
+
+		/* create new buddy */
+		buddy = purple_buddy_new( session->acc, contact->username, contact->alias );
+		buddy->proto_data = contact;
+
+		/* add new buddy to list */
+		purple_blist_add_buddy( buddy, NULL, group, NULL );
+	}
+	else {
+		/* buddy was found in the group */
+
+		/* now update the buddy's alias */
+		purple_blist_alias_buddy( buddy, contact->alias );
+
+		/* replace the buddy's contact struct */
+		if ( buddy->proto_data )
+			free( buddy->proto_data );
+		buddy->proto_data = contact;
+	}
+
+	/* load buddy's avatar id */
+	id = purple_buddy_icons_get_checksum_for_user( buddy );
+	if ( id )
+		contact->avatarId = g_strdup( id );
+	else
+		contact->avatarId = NULL;
+
+	/* update the buddy's status (reference: "libpurple/prpl.h") */
+	purple_prpl_got_user_status( session->acc, contact->username, mxit_statuses[contact->presence].id, NULL );
+}
+
+
+/*------------------------------------------------------------------------
+ * A presence update packet was received from the MXit server, so update the buddy's
+ * information.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The contact which presence to update
+ *  @param presence		The new presence state for the contact
+ *  @param mood			The new mood for the contact
+ *  @param customMood	The custom mood identifier
+ *  @param statusMsg	This is the contact's status message
+ *  @param avatarId		This is the contact's avatar id
+ */
+void mxit_update_buddy_presence( struct MXitSession* session, const char* username, short presence, short mood, const char* customMood, const char* statusMsg, const char* avatarId )
+{
+	PurpleBuddy*		buddy	= NULL;
+	struct contact*		contact	= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: user='%s' presence=%i mood=%i customMood='%s' statusMsg='%s' avatar='%s'\n",
+		username, presence, mood, customMood, statusMsg, avatarId );
+
+	if ( ( presence < MXIT_PRESENCE_OFFLINE ) || ( presence > MXIT_PRESENCE_DND ) ) {
+		purple_debug_info( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: invalid presence state %i\n", presence );
+		return;		/* ignore packet */
+	}
+
+	/* find the buddy information for this contact (reference: "libpurple/blist.h") */
+	buddy = purple_find_buddy( session->acc, username );
+	if ( !buddy ) {
+		purple_debug_warning( MXIT_PLUGIN_ID, "mxit_update_buddy_presence: unable to find the buddy '%s'\n", username );
+		return;
+	}
+
+	contact = buddy->proto_data;
+	if ( !contact )
+		return;
+
+	contact->presence = presence;	
+	contact->mood = mood;
+
+	g_strlcpy( contact->customMood, customMood, sizeof( contact->customMood ) );
+	// TODO: Download custom mood frame.
+
+	/* update status message */
+	if ( contact->statusMsg ) {
+		g_free( contact->statusMsg );
+		contact->statusMsg = NULL;
+	}
+	if ( statusMsg[0] != '\0' )
+		contact->statusMsg = g_strdup( statusMsg );	
+
+	/* update avatarId */
+	if ( ( contact->avatarId ) && ( g_ascii_strcasecmp( contact->avatarId, avatarId ) == 0 ) ) {
+		/*  avatar has not changed - do nothing */
+	}
+	else if ( avatarId[0] != '\0' ) {		/* avatar has changed */
+		if ( contact->avatarId )
+			g_free( contact->avatarId );
+		contact->avatarId = g_strdup( avatarId );
+
+		/* Send request to download new avatar image */
+		mxit_get_avatar( session, username, avatarId );
+	}
+	else		/* clear current avatar */
+		purple_buddy_icons_set_for_user( session->acc, username, NULL, 0, NULL );
+
+	/* update the buddy's status (reference: "libpurple/prpl.h") */
+	if ( contact->statusMsg )
+		purple_prpl_got_user_status( session->acc, username, mxit_statuses[contact->presence].id, "message", contact->statusMsg, NULL );
+	else
+		purple_prpl_got_user_status( session->acc, username, mxit_statuses[contact->presence].id, NULL );
+}
+
+
+/*------------------------------------------------------------------------
+ * update the blist cached by libPurple. We need to do this to keep
+ * libPurple and MXit's rosters in sync with each other.
+ *
+ * @param session		The MXit session object
+ */
+void mxit_update_blist( struct MXitSession* session )
+{
+	PurpleBuddy*	buddy	= NULL;
+	GSList*			list	= NULL;
+	unsigned int	i;
+
+	/* remove all buddies we did not receive a roster update for.
+	 * these contacts must have been removed from another client */
+	list = purple_find_buddies( session->acc, NULL );
+
+	for ( i = 0; i < g_slist_length( list ); i++ ) {
+		buddy = g_slist_nth_data( list, i );
+
+		if ( !buddy->proto_data ) {
+			/* this buddy should be removed, because we did not receive him in our roster update from MXit */
+			purple_debug_info( MXIT_PLUGIN_ID, "Removed 'old' buddy from the blist '%s' (%s)\n", buddy->alias, buddy->name );
+			purple_blist_remove_buddy( buddy );
+		}
+	}
+
+	/* tell the UI to update the blist */
+	purple_blist_add_account( session->acc );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user authorized an invite (subscription request).
+ *
+ *  @param user_data	Object associated with the invite
+ */
+static void mxit_cb_buddy_auth( gpointer user_data )
+{
+	struct contact_invite*	invite	= (struct contact_invite*) user_data;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_buddy_auth '%s'\n", invite->contact->username );
+
+	/* send a allow subscription packet to MXit */
+	mxit_send_allow_sub( invite->session, invite->contact->username, invite->contact->alias );
+
+	/* freeup invite object */
+	if ( invite->contact->msg )
+		g_free( invite->contact->msg );
+	g_free( invite->contact );
+	g_free( invite );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user rejected an invite (subscription request).
+ *
+ *  @param user_data	Object associated with the invite
+ */
+static void mxit_cb_buddy_deny( gpointer user_data )
+{
+	struct contact_invite*	invite	= (struct contact_invite*) user_data;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_cb_buddy_deny '%s'\n", invite->contact->username );
+
+	/* send a deny subscription packet to MXit */
+	mxit_send_deny_sub( invite->session, invite->contact->username );
+
+	/* freeup invite object */
+	if ( invite->contact->msg )
+		g_free( invite->contact->msg );
+	g_free( invite->contact );
+	g_free( invite );
+}
+
+
+/*------------------------------------------------------------------------
+ * A new subscription request packet was received from the MXit server.
+ * Prompt user to accept or reject it.
+ *
+ *  @param session		The MXit session object
+ *  @param contact		The contact performing the invite
+ */
+void mxit_new_subscription( struct MXitSession* session, struct contact* contact )
+{
+	struct contact_invite*	invite;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_new_subscription from '%s' (%s)\n", contact->username, contact->alias );
+
+	invite = g_new0( struct contact_invite, 1 );
+	invite->session = session;
+	invite->contact = contact;
+
+	/* (reference: "libpurple/account.h") */
+	purple_account_request_authorization( session->acc, contact->username, NULL, contact->alias, contact->msg, FALSE, mxit_cb_buddy_auth, mxit_cb_buddy_deny, invite );
+}
+
+
+/*------------------------------------------------------------------------
+ * Return TRUE if this is a MXit Chatroom contact.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the contact
+ */
+gboolean is_mxit_chatroom_contact( struct MXitSession* session, const char* username )
+{
+	PurpleBuddy*		buddy;
+	struct contact*		contact	= NULL;
+
+	/* find the buddy */
+	buddy = purple_find_buddy( session->acc, username );
+	if ( !buddy ) {
+		purple_debug_warning( MXIT_PLUGIN_ID, "is_mxit_chatroom_contact: unable to find the buddy '%s'\n", username );
+		return FALSE;
+	}
+
+	contact = buddy->proto_data;
+	if ( !contact )
+		return FALSE;
+
+	return ( contact->type == MXIT_TYPE_CHATROOM );
+}
+
+
+/*========================================================================================================================
+ * Callbacks from libpurple
+ */
+
+/*------------------------------------------------------------------------
+ * The user has added a buddy to the list, so send an invite request.
+ *
+ *  @param gc		The connection object
+ *  @param buddy	The new buddy
+ *  @param group	The group of the new buddy
+ */
+void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+	GSList*				list	= NULL;
+	PurpleBuddy*		mxbuddy	= NULL;
+	unsigned int		i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy '%s' (group='%s')\n", buddy->name, group->name );
+
+	list = purple_find_buddies( session->acc, buddy->name );
+	if ( g_slist_length( list ) == 1 ) {
+		purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy (scenario 1) (list:%i)\n", g_slist_length( list ) );
+		/*
+		 * we only send an invite to MXit when the user is not already inside our
+		 * blist.  this is done because purple does an add_buddy() call when
+		 * you accept an invite.  so in that case the user is already
+		 * in our blist and ready to be chatted to.
+		 */
+		mxit_send_invite( session, buddy->name, buddy->alias, group->name );
+	}
+	else {
+		purple_debug_info( MXIT_PLUGIN_ID, "mxit_add_buddy (scenario 2) (list:%i)\n", g_slist_length( list ) );
+		/*
+		 * we already have the buddy in our list, so we will only update
+		 * his information here and not send another invite message
+		 */
+
+		/* find the correct buddy */
+		for ( i = 0; i < g_slist_length( list ); i++ ) {
+			mxbuddy = g_slist_nth_data( list, i );
+
+			if ( mxbuddy->proto_data != NULL ) {
+				/* this is our REAL MXit buddy! */
+
+				/* now update the buddy's alias */
+				purple_blist_alias_buddy( mxbuddy, buddy->alias );
+
+				/* now update the buddy's group */
+//				mxbuddy = mxit_update_buddy_group( session, mxbuddy, group );
+
+				/* send the update to the MXit server */
+				mxit_send_update_contact( session, mxbuddy->name, mxbuddy->alias, group->name );
+			}
+		}
+	}
+
+	/*
+	 * we remove the buddy here from the buddy list because the MXit server
+	 * will send us a proper contact update packet if this succeeds.  now
+	 * we do not have to worry about error handling in case of adding an
+	 * invalid contact.  so the user will still see the contact as offline
+	 * until he eventually accepts the invite.
+	 */
+	purple_blist_remove_buddy( buddy );
+
+	g_slist_free( list );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has removed a buddy from the list.
+ *
+ *  @param gc		The connection object
+ *  @param buddy	The buddy being removed
+ *  @param group	The group the buddy was in
+ */
+void mxit_remove_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_remove_buddy '%s'\n", buddy->name );
+
+	mxit_send_remove( session, buddy->name );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user changed the buddy's alias.
+ *
+ *  @param gc		The connection object
+ *  @param who		The username of the buddy
+ *  @param alias	The new alias
+ */
+void mxit_buddy_alias( PurpleConnection* gc, const char* who, const char* alias )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+	PurpleBuddy*		buddy	= NULL;
+	PurpleGroup*		group	= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_buddy_alias '%s' to '%s\n", who, alias );
+
+	/* find the buddy */
+	buddy = purple_find_buddy( session->acc, who );
+	if ( !buddy ) {
+		purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_alias: unable to find the buddy '%s'\n", who );
+		return;
+	}
+
+	/* find buddy group */
+	group = purple_buddy_get_group( buddy );
+	if ( !group ) {
+		purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_alias: unable to find the group for buddy '%s'\n", who );
+		return;
+	}
+
+	mxit_send_update_contact( session, who, alias, group->name );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user changed the group for a single buddy.
+ *
+ *  @param gc			The connection object
+ *  @param who			The username of the buddy
+ *  @param old_group	The old group's name
+ *  @param new_group	The new group's name
+ */
+void mxit_buddy_group( PurpleConnection* gc, const char* who, const char* old_group, const char* new_group )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+	PurpleBuddy*		buddy	= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_buddy_group from '%s' to '%s'\n", old_group, new_group );
+
+	/* find the buddy */
+	buddy = purple_find_buddy( session->acc, who );
+	if ( !buddy ) {
+		purple_debug_warning( MXIT_PLUGIN_ID, "mxit_buddy_group: unable to find the buddy '%s'\n", who );
+		return;
+	}
+
+	mxit_send_update_contact( session, who, buddy->alias, new_group );
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has selected to rename a group, so update all contacts in that
+ * group.
+ *
+ *  @param gc				The connection object
+ *  @param old_name			The old group name
+ *  @param group			The updated group object
+ *  @param moved_buddies	The buddies affected by the rename
+ */
+void mxit_rename_group( PurpleConnection* gc, const char* old_name, PurpleGroup* group, GList* moved_buddies )
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+	PurpleBuddy*		buddy	= NULL;
+	GList*				item	= NULL;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_rename_group from '%s' to '%s\n", old_name, group->name );
+
+	//  TODO: Might be more efficient to use the "rename group" command (cmd=29).
+
+	/* loop through all the contacts in the group and send updates */
+	item = moved_buddies;
+	while ( item ) {
+		buddy = item->data;
+		mxit_send_update_contact( session, buddy->name, buddy->alias, group->name );
+		item = g_list_next( item );
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/roster.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,139 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- user roster management (mxit contacts) --
+ *
+ *				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
+ */
+
+#ifndef		_MXIT_ROSTER_H_
+#define		_MXIT_ROSTER_H_
+
+
+/* MXit contact presence states */
+#define		MXIT_PRESENCE_OFFLINE		0x00
+#define		MXIT_PRESENCE_ONLINE		0x01
+#define		MXIT_PRESENCE_AWAY			0x02
+#define		MXIT_PRESENCE_AVAILABLE		0x03
+#define		MXIT_PRESENCE_DND			0x04
+
+
+/* MXit contact types */
+#define		MXIT_TYPE_MXIT				0x00
+#define		MXIT_TYPE_JABBER			0x01
+#define		MXIT_TYPE_MSN				0x02
+#define		MXIT_TYPE_YAHOO				0x03
+#define		MXIT_TYPE_ICQ				0x04
+#define		MXIT_TYPE_AIM				0x05
+#define		MXIT_TYPE_QQ				0x06
+#define		MXIT_TYPE_WV				0x07
+#define		MXIT_TYPE_BOT				0x08
+#define		MXIT_TYPE_CHATROOM			0x09
+#define		MXIT_TYPE_SMS				0x0A
+#define		MXIT_TYPE_GROUP				0x0B
+#define		MXIT_TYPE_GALLERY			0x0C
+#define		MXIT_TYPE_INFO				0x0D
+#define		MXIT_TYPE_MULTIMX			0x0E
+#define		MXIT_TYPE_HYBRID			0x0F
+
+
+/* MXit contact moods */
+#define		MXIT_MOOD_NONE				0x00
+#define		MXIT_MOOD_ANGRY				0x01
+#define		MXIT_MOOD_EXCITED			0x02
+#define		MXIT_MOOD_GRUMPY			0x03
+#define		MXIT_MOOD_HAPPY				0x04
+#define		MXIT_MOOD_INLOVE			0x05
+#define		MXIT_MOOD_INVINCIBLE		0x06
+#define		MXIT_MOOD_SAD				0x07
+#define		MXIT_MOOD_HOT				0x08
+#define		MXIT_MOOD_SICK				0x09
+#define		MXIT_MOOD_SLEEPY			0x0A
+
+
+/* MXit contact flags */
+#define		MXIT_CFLAG_HIDDEN			0x02
+#define		MXIT_CFLAG_GATEWAY			0x04
+#define		MXIT_CFLAG_FOCUS_SEND_BLANK	0x20000
+
+
+/* Subscription types */
+#define		MXIT_SUBTYPE_BOTH			'B'
+#define		MXIT_SUBTYPE_PENDING		'P'
+#define		MXIT_SUBTYPE_ASK			'A'
+#define		MXIT_SUBTYPE_REJECTED		'R'
+#define		MXIT_SUBTYPE_DELETED		'D'
+#define		MXIT_SUBTYPE_NONE			'N'
+
+
+/* client protocol constants */
+#define		MXIT_CP_MAX_JID_LEN			64
+#define		MXIT_CP_MAX_GROUP_LEN		32
+#define		MXIT_CP_MAX_ALIAS_LEN		48
+
+#define		MXIT_DEFAULT_GROUP			"MXit"
+
+
+/*
+ * a MXit contact
+ */
+struct contact {
+	char		username[MXIT_CP_MAX_JID_LEN+1];	/* unique contact name (with domain) */
+	char		alias[MXIT_CP_MAX_GROUP_LEN+1];		/* contact alias (what will be seen) */
+	char		groupname[MXIT_CP_MAX_ALIAS_LEN+1];	/* contact group name */
+
+	short		type;								/* contact type */
+	short		mood;								/* contact current mood */
+	int			flags;								/* contact flags */
+	short		presence;							/* presence state */
+	short		subtype;							/* subscription type */
+
+	char*		msg;								/* invite message */
+
+	char		customMood[16];						/* custom mood */
+	char*		statusMsg;							/* status message */
+	char*		avatarId;							/* avatarId */
+};
+
+/* Presence / Status */
+GList* mxit_status_types( PurpleAccount* account );
+int mxit_convert_presence( const char* id );
+const char* mxit_convert_presence_to_name( short no );
+const char* mxit_convert_subtype_to_name( short subtype );
+
+/* Moods */
+const char* mxit_convert_mood_to_name( short id );
+
+/* MXit Protocol callbacks */
+void mxit_update_contact( struct MXitSession* session, struct contact* contact );
+void mxit_update_buddy_presence( struct MXitSession* session, const char* username, short presence, short mood, const char* customMood, const char* statusMsg, const char* avatarId );
+void mxit_new_subscription( struct MXitSession* session, struct contact* contact );
+void mxit_update_blist( struct MXitSession* session );
+gboolean is_mxit_chatroom_contact( struct MXitSession* session, const char* username );
+
+/* libPurple callbacks */
+void mxit_add_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group );
+void mxit_remove_buddy( PurpleConnection* gc, PurpleBuddy* buddy, PurpleGroup* group );
+void mxit_buddy_alias( PurpleConnection* gc, const char* who, const char* alias );
+void mxit_buddy_group( PurpleConnection* gc, const char* who, const char* old_group, const char* new_group );
+void mxit_rename_group( PurpleConnection* gc, const char* old_name, PurpleGroup* group, GList* moved_buddies );
+
+
+#endif		/* _MXIT_ROSTER_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/splashscreen.c	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,223 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *						-- splash screens --
+ *
+ *				Andrew Victor	<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 <libgen.h>
+#include <glib/gstdio.h>
+
+#include "purple.h"
+#include "imgstore.h"
+
+#include "protocol.h"
+#include "mxit.h"
+#include "splashscreen.h"
+
+
+/*------------------------------------------------------------------------
+ * Return the ID of the current splash-screen.
+ *
+ *  @param session		The MXit session object
+ *  @return				The ID of the splash-screen (or NULL if no splash-screen)
+ */
+const char* splash_current(struct MXitSession* session)
+{
+	const char* splashId = purple_account_get_string(session->acc, MXIT_CONFIG_SPLASHID, NULL);
+
+	purple_debug_info(MXIT_PLUGIN_ID, "Current splashId: '%s'\n", splashId);
+
+	if ((splashId != NULL) && (*splashId != '\0'))
+		return splashId;
+	else
+		return NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Indicate if splash-screen popups are enabled.
+ *
+ *  @param session		The MXit session object
+ *  @return				TRUE if the popup is enabled.
+ */
+gboolean splash_popup_enabled(struct MXitSession* session)
+{
+	return purple_account_get_bool(session->acc, MXIT_CONFIG_SPLASHPOPUP, DEFAULT_SPLASH_POPUP);
+}
+
+
+/*------------------------------------------------------------------------
+ * Return if the current splash-screen is clickable.
+ *
+ *  @param session		The MXit session object
+ *  @return				TRUE or FALSE
+ */
+static gboolean splash_clickable(struct MXitSession* session)
+{
+	return purple_account_get_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, FALSE);
+}
+
+
+/*------------------------------------------------------------------------
+ * Remove the stored splash-screen (if it exists).
+ *
+ *  @param session		The MXit session object
+ */
+void splash_remove(struct MXitSession* session)
+{
+	const char* splashId = NULL;
+	char* filename;
+
+	/* Get current splash ID */
+	splashId = splash_current(session);
+
+	if (splashId != NULL) {
+		purple_debug_info(MXIT_PLUGIN_ID, "Removing splashId: '%s'\n", splashId);
+
+		/* Delete stored splash image */
+		filename = g_strdup_printf("%s/mxit/%s.png", purple_user_dir(), splashId);
+		g_unlink(filename);
+		g_free(filename);
+
+		/* Clear current splash ID from settings */
+		purple_account_set_string(session->acc, MXIT_CONFIG_SPLASHID, "");
+		purple_account_set_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, FALSE);
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Save a new splash-screen for later display.
+ *
+ *  @param session		The MXit session object
+ *  @param splashID		The ID of the splash-screen
+ *  @param data			Splash-screen image data (PNG format)
+ *  @param datalen		Splash-screen image data size
+ */
+void splash_update(struct MXitSession* session, const char* splashId, const char* data, int datalen, gboolean clickable)
+{
+	char* dir;
+	char* filename;
+
+	/* Remove the current splash-screen */
+	splash_remove(session);
+
+	/* Save the new splash image */
+	dir = g_strdup_printf("%s/mxit",  purple_user_dir());
+	purple_build_dir(dir, S_IRUSR | S_IWUSR | S_IXUSR);		/* ensure directory exists */
+
+	filename = g_strdup_printf("%s/%s.png", dir, splashId);
+	if (purple_util_write_data_to_file_absolute(filename, data, datalen)) {
+		/* Store new splash-screen ID to settings */
+		purple_account_set_string(session->acc, MXIT_CONFIG_SPLASHID, splashId);
+		purple_account_set_bool(session->acc, MXIT_CONFIG_SPLASHCLICK, clickable );
+	}
+
+	g_free(dir);
+	g_free(filename);
+}
+
+
+/*------------------------------------------------------------------------
+ * The user has clicked OK on the Splash request form.
+ *
+ *  @param gc			The connection object
+ *  @param fields		The list of fields in the accepted form
+ */
+static void splash_click_ok(PurpleConnection* gc, PurpleRequestFields* fields)
+{
+	struct MXitSession*	session	= (struct MXitSession*) gc->proto_data;
+	const char* splashId;
+
+	/* Get current splash ID */
+	splashId = splash_current(session);
+	if (!splashId)
+		return;
+
+	/* if is clickable, then send click event */
+	if (splash_clickable(session))
+		mxit_send_splashclick(session, splashId);
+}
+
+
+/*------------------------------------------------------------------------
+ * Display the current splash-screen.
+ *
+ *  @param session		The MXit session object
+ */
+void splash_display(struct MXitSession* session)
+{
+	const char* splashId = NULL;
+	char* filename;
+	gchar* imgdata;
+	gsize imglen;
+	int imgid = -1;
+
+	/* Get current splash ID */
+	splashId = splash_current(session);
+	if (splashId == NULL)		/* no splash-screen */
+		return;
+
+	purple_debug_info(MXIT_PLUGIN_ID, "Display Splash: '%s'\n", splashId);
+
+	/* Load splash-screen image from file */
+	filename = g_strdup_printf("%s/mxit/%s.png", purple_user_dir(), splashId);
+	if (g_file_get_contents(filename, &imgdata, &imglen, NULL)) {
+		char buf[128];
+
+		/* Add splash-image to imagestore */
+		imgid = purple_imgstore_add_with_id(g_memdup(imgdata, imglen), imglen, NULL);
+
+		/* Generate and display message */
+		g_snprintf(buf, sizeof(buf), "<img id=\"%d\">", imgid);
+
+		/* Open a request-type popup to display the image */
+		{
+			PurpleRequestFields*		fields;
+			PurpleRequestFieldGroup*	group;
+			PurpleRequestField*			field;
+
+			fields = purple_request_fields_new();
+			group = purple_request_field_group_new(NULL);
+			purple_request_fields_add_group(fields, group);
+
+			field = purple_request_field_image_new("splash", "", imgdata, imglen);		/* add splash image */
+			purple_request_field_group_add_field(group, field);
+
+			if (splash_clickable(session)) {
+				purple_request_fields(session->con, _("MXit Advertising"), NULL, NULL, fields,
+					_("More Information"), G_CALLBACK(splash_click_ok), _("Close"), NULL, session->acc, NULL, NULL, session->con);
+			}
+			else {
+				purple_request_fields(session->con, _("MXit Advertising"), NULL, NULL, fields,
+					_("Continue"), G_CALLBACK(splash_click_ok), _("Close"), NULL, session->acc, NULL, NULL, session->con);
+			}
+		}
+
+		/* Release reference to image */
+		purple_imgstore_unref_by_id(imgid);
+
+		g_free(imgdata);
+	}
+
+	g_free(filename);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/splashscreen.h	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,59 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *						-- splash screens --
+ *
+ *				Andrew Victor	<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
+ */
+
+#ifndef		_MXIT_SPLASHSCREEN_H_
+#define		_MXIT_SPLASHSCREEN_H_
+
+#define		HANDLE_SPLASH1		"plas1.png"
+#define		HANDLE_SPLASH2		"plas2.png"
+
+#define		DEFAULT_SPLASH_POPUP	FALSE		/* disabled by default */
+
+/*
+ * Return the ID of the current splash-screen.
+ */
+const char* splash_current(struct MXitSession* session);
+
+/*
+ * Indicate if splash-screen popups are enabled.
+ */
+gboolean splash_popup_enabled();
+
+/*
+ * Save a new splash-screen.
+ */
+void splash_update(struct MXitSession* session, const char* splashId, const char* data, int datalen, gboolean clickable);
+
+/*
+ * Remove the stored splash-screen (if it exists).
+ */
+void splash_remove(struct MXitSession* session);
+
+/*
+ * Display the current splash-screen.
+ */
+void splash_display(struct MXitSession* session);
+
+#endif		/* _MXIT_SPLASHSCREEN_H_ */
--- a/libpurple/protocols/oscar/clientlogin.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/oscar/clientlogin.c	Thu Nov 12 23:25:57 2009 +0000
@@ -40,6 +40,7 @@
 #include "core.h"
 
 #include "oscar.h"
+#include "oscarcommon.h"
 
 #define URL_CLIENT_LOGIN "https://api.screenname.aol.com/auth/clientLogin"
 #define URL_START_OSCAR_SESSION "http://api.oscar.aol.com/aim/startOSCARSession"
@@ -102,11 +103,15 @@
 	return signature;
 }
 
-static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **host, unsigned short *port, char **cookie)
+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)
 {
 	xmlnode *response_node, *tmp_node, *data_node;
-	xmlnode *host_node = NULL, *port_node = NULL, *cookie_node = NULL;
+	xmlnode *host_node = NULL, *port_node = NULL, *cookie_node = NULL, *tls_node = NULL;
+	gboolean use_tls;
 	char *tmp;
+	guint code;
+
+	use_tls = purple_account_get_bool(purple_connection_get_account(gc), "use_ssl", OSCAR_DEFAULT_USE_SSL);
 
 	/* Parse the response as XML */
 	response_node = xmlnode_from_str(response, response_len);
@@ -131,6 +136,7 @@
 		host_node = xmlnode_get_child(data_node, "host");
 		port_node = xmlnode_get_child(data_node, "port");
 		cookie_node = xmlnode_get_child(data_node, "cookie");
+		tls_node = xmlnode_get_child(data_node, "tlsCertName");
 	}
 
 	/* Make sure we have a status code */
@@ -148,12 +154,13 @@
 	}
 
 	/* Make sure the status code was 200 */
-	if (strcmp(tmp, "200") != 0)
+	code = atoi(tmp);
+	if (code != 200)
 	{
 		purple_debug_error("oscar", "startOSCARSession response statusCode "
 				"was %s: %s\n", tmp, response);
 
-		if (strcmp(tmp, "401") == 0)
+		if (code == 401 || code == 607)
 			purple_connection_error_reason(gc,
 					PURPLE_CONNECTION_ERROR_OTHER_ERROR,
 					_("You have been connecting and disconnecting too "
@@ -177,7 +184,8 @@
 
 	/* Make sure we have everything else */
 	if (data_node == NULL || host_node == NULL ||
-		port_node == NULL || cookie_node == NULL)
+		port_node == NULL || cookie_node == NULL ||
+		(use_tls && tls_node == NULL))
 	{
 		char *msg;
 		purple_debug_error("oscar", "startOSCARSession response was missing "
@@ -195,7 +203,12 @@
 	*host = xmlnode_get_data_unescaped(host_node);
 	tmp = xmlnode_get_data_unescaped(port_node);
 	*cookie = xmlnode_get_data_unescaped(cookie_node);
-	if (*host == NULL || **host == '\0' || tmp == NULL || *tmp == '\0' || cookie == NULL || *cookie == '\0')
+
+	if (use_tls)
+		*tls_certname = xmlnode_get_data_unescaped(tls_node);
+
+	if (*host == NULL || **host == '\0' || tmp == NULL || *tmp == '\0' || *cookie == NULL || **cookie == '\0' ||
+			(use_tls && (*tls_certname == NULL || **tls_certname == '\0')))
 	{
 		char *msg;
 		purple_debug_error("oscar", "startOSCARSession response was missing "
@@ -223,6 +236,7 @@
 	OscarData *od;
 	PurpleConnection *gc;
 	char *host, *cookie;
+	char *tls_certname = NULL;
 	unsigned short port;
 	guint8 *cookiedata;
 	gsize cookiedata_len;
@@ -244,28 +258,30 @@
 		return;
 	}
 
-	if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie))
+	if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie, &tls_certname))
 		return;
 
 	cookiedata = purple_base64_decode(cookie, &cookiedata_len);
-	oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len);
+	oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len, tls_certname);
 	g_free(cookiedata);
 
 	g_free(host);
 	g_free(cookie);
+	g_free(tls_certname);
 }
 
 static void send_start_oscar_session(OscarData *od, const char *token, const char *session_key, time_t hosttime)
 {
 	char *query_string, *signature, *url;
+	gboolean use_tls = purple_account_get_bool(purple_connection_get_account(od->gc), "use_ssl", OSCAR_DEFAULT_USE_SSL);
 
 	/* Construct the GET parameters */
 	query_string = g_strdup_printf("a=%s"
 			"&f=xml"
 			"&k=%s"
 			"&ts=%" PURPLE_TIME_T_MODIFIER
-			"&useTLS=0",
-			purple_url_encode(token), get_client_key(od), hosttime);
+			"&useTLS=%d",
+			purple_url_encode(token), get_client_key(od), hosttime, use_tls);
 	signature = generate_signature("GET", URL_START_OSCAR_SESSION,
 			query_string, session_key);
 	url = g_strdup_printf(URL_START_OSCAR_SESSION "?%s&sig_sha256=%s",
--- a/libpurple/protocols/oscar/family_feedbag.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/oscar/family_feedbag.c	Thu Nov 12 23:25:57 2009 +0000
@@ -389,11 +389,10 @@
 
 /**
  * Locally find the presence flag item, and return the setting.  The returned setting is a
- * bitmask of the user flags that you are visible to.  See the AIM_FLAG_* #defines
- * in oscar.h
+ * bitmask of the preferences.  See the AIM_SSI_PRESENCE_FLAG_* #defines in oscar.h.
  *
  * @param list A pointer to the current list of items.
- * @return Return the current visibility mask.
+ * @return Return the current set of preferences.
  */
 guint32 aim_ssi_getpresence(struct aim_ssi_item *list)
 {
@@ -1130,9 +1129,11 @@
  * should show up as idle or not, etc.
  *
  * @param od The oscar odion.
- * @param presence I think it's a bitmask, but I only know what one of the bits is:
- *        0x00000002 - Hide wireless?
+ * @param presence A bitmask of the first 32 entries [0-31] from
+ *        http://dev.aol.com/aim/oscar/#FEEDBAG__BUDDY_PREFS
+ *        0x00000002 - Hide "eBuddy group" (whatever that is)
  *        0x00000400 - Allow others to see your idle time
+ *        0x00020000 - Don't show Recent Buddies
  * @return Return 0 if no errors, otherwise return the error number.
  */
 int aim_ssi_setpresence(OscarData *od, guint32 presence) {
--- a/libpurple/protocols/oscar/flap_connection.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Thu Nov 12 23:25:57 2009 +0000
@@ -73,7 +73,7 @@
 }
 
 void
-flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci)
+flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci, gboolean allow_multiple_logins)
 {
 	FlapFrame *frame;
 	GSList *tlvlist = NULL;
@@ -94,7 +94,7 @@
 	aim_tlvlist_add_16(&tlvlist, 0x0018, (guint16)ci->minor);
 	aim_tlvlist_add_16(&tlvlist, 0x0019, (guint16)ci->point);
 	aim_tlvlist_add_16(&tlvlist, 0x001a, (guint16)ci->build);
-	aim_tlvlist_add_8(&tlvlist, 0x004a, 0x01);
+	aim_tlvlist_add_8(&tlvlist, 0x004a, (allow_multiple_logins ? 0x01 : 0x03));
 
 	aim_tlvlist_write(&frame->data, &tlvlist);
 
--- a/libpurple/protocols/oscar/libaim.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/oscar/libaim.c	Thu Nov 12 23:25:57 2009 +0000
@@ -141,7 +141,7 @@
 static void
 init_plugin(PurplePlugin *plugin)
 {
-	oscar_init(PURPLE_PLUGIN_PROTOCOL_INFO(plugin));
+	oscar_init(plugin);
 }
 
 PURPLE_INIT_PLUGIN(aim, init_plugin, info);
--- a/libpurple/protocols/oscar/libicq.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/oscar/libicq.c	Thu Nov 12 23:25:57 2009 +0000
@@ -153,7 +153,7 @@
 {
 	PurpleAccountOption *option;
 
-	oscar_init(PURPLE_PLUGIN_PROTOCOL_INFO(plugin));
+	oscar_init(plugin);
 
 	option = purple_account_option_string_new(_("Encoding"), "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
--- a/libpurple/protocols/oscar/oscar.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Thu Nov 12 23:25:57 2009 +0000
@@ -1188,7 +1188,8 @@
 			ClientInfo icqinfo = CLIENTINFO_PURPLE_ICQ;
 			flap_connection_send_version_with_cookie_and_clientinfo(od,
 					conn, conn->cookielen, conn->cookie,
-					od->icq ? &icqinfo : &aiminfo);
+					od->icq ? &icqinfo : &aiminfo,
+					purple_account_get_bool(account, "allow_multiple_logins", OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS));
 		} else {
 			flap_connection_send_version_with_cookie(od, conn,
 					conn->cookielen, conn->cookie);
@@ -1414,9 +1415,9 @@
 	presence = aim_ssi_getpresence(od->ssi.local);
 
 	if (report_idle)
-		aim_ssi_setpresence(od, presence | 0x400);
+		aim_ssi_setpresence(od, presence | AIM_SSI_PRESENCE_FLAG_SHOWIDLE);
 	else
-		aim_ssi_setpresence(od, presence & ~0x400);
+		aim_ssi_setpresence(od, presence & ~AIM_SSI_PRESENCE_FLAG_SHOWIDLE);
 }
 
 /**
@@ -1826,17 +1827,35 @@
 	return 1;
 }
 
-int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen)
+int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen, const char *tls_certname)
 {
+	PurpleAccount *account;
 	FlapConnection *conn;
 
+	account = purple_connection_get_account(gc);
+
 	conn = flap_connection_new(od, SNAC_FAMILY_LOCATE);
 	conn->cookielen = cookielen;
 	conn->cookie = g_memdup(cookie, cookielen);
-	conn->connect_data = purple_proxy_connect(NULL,
-			purple_connection_get_account(gc), host, port,
-			connection_established_cb, conn);
-	if (conn->connect_data == NULL)
+
+	/*
+	 * tls_certname is only set (and must be set if we get this far) if
+	 * SSL is enabled.
+	 */
+	if (tls_certname)
+	{
+		conn->gsc = purple_ssl_connect_with_ssl_cn(account, host, port,
+				ssl_connection_established_cb, ssl_connection_error_cb,
+				tls_certname, conn);
+	}
+	else
+	{
+		conn->connect_data = purple_proxy_connect(NULL,
+				account, host, port,
+				connection_established_cb, conn);
+	}
+
+	if (conn->gsc == NULL && conn->connect_data == NULL)
 	{
 		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect"));
 		return 0;
@@ -3238,7 +3257,7 @@
 #endif
 	va_list ap;
 	guint16 reason, errcode;
-	char *data, *reason_str, *buf, *error;
+	char *data, *reason_str, *buf;
 
 	va_start(ap, fr);
 	reason = (guint16)va_arg(ap, unsigned int);
@@ -3263,21 +3282,27 @@
 #endif
 
 	/* Data is assumed to be the destination bn */
+
 	reason_str = g_strdup((reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason"));
-	if (errcode != 0 && errcode < errcodereasonlen) {
-		error = g_strdup_printf("%s: %s", reason_str, _(errcodereason[errcode]));
-		g_free(reason_str);
-	} else
-		error = reason_str;
-
-	buf = g_strdup_printf(_("Unable to send message: %s"), error);
+	if (errcode != 0 && errcode < errcodereasonlen)
+		buf = g_strdup_printf(_("Unable to send message: %s (%s)"), reason_str,
+		                      _(errcodereason[errcode]));
+	else
+		buf = g_strdup_printf(_("Unable to send message: %s"), reason_str);
+
 	if (!purple_conv_present_error(data, purple_connection_get_account(gc), buf)) {
 		g_free(buf);
-		buf = g_strdup_printf(_("Unable to send message to %s:"), data ? data : "(unknown)");
-		purple_notify_error(od->gc, NULL, buf, error);
+		if (errcode != 0 && errcode < errcodereasonlen)
+			buf = g_strdup_printf(_("Unable to send message to %s: %s (%s)"),
+			                      data ? data : "(unknown)", reason_str,
+			                      _(errcodereason[errcode]));
+		else
+			buf = g_strdup_printf(_("Unable to send message to %s: %s"),
+			                      data ? data : "(unknown)", reason_str);
+		purple_notify_error(od->gc, NULL, buf, reason_str);
 	}
 	g_free(buf);
-	g_free(error);
+	g_free(reason_str);
 
 	return 1;
 }
@@ -3992,9 +4017,6 @@
 	presence = purple_status_get_presence(status);
 	aim_srv_setidle(od, !purple_presence_is_idle(presence) ? 0 : time(NULL) - purple_presence_get_idle_time(presence));
 
-	/* Request offline messages for AIM and ICQ */
-	aim_im_reqofflinemsgs(od);
-
 	if (od->icq) {
 #ifdef OLDSTYLE_ICQ_OFFLINEMSGS
 		aim_icq_reqofflinemsgs(od);
@@ -4021,6 +4043,10 @@
 	 */
 	if (od->ssi.received_data) {
 		aim_srv_clientready(od, conn);
+
+		/* Request offline messages for AIM and ICQ */
+		aim_im_reqofflinemsgs(od);
+
 		purple_connection_set_state(gc, PURPLE_CONNECTED);
 	}
 
@@ -5218,7 +5244,7 @@
 	{ /* If not in server list then prune from local list */
 		GSList *cur, *next;
 		GSList *buddies = purple_find_buddies(account, NULL);
-		
+
 		/* Buddies */
 		cur = NULL;
 
@@ -5298,9 +5324,9 @@
 			report_idle = strcmp(idle_reporting_pref, "none") != 0;
 
 			if (report_idle)
-				aim_ssi_setpresence(od, tmp | 0x400);
+				aim_ssi_setpresence(od, tmp | AIM_SSI_PRESENCE_FLAG_SHOWIDLE);
 			else
-				aim_ssi_setpresence(od, tmp & ~0x400);
+				aim_ssi_setpresence(od, tmp & ~AIM_SSI_PRESENCE_FLAG_SHOWIDLE);
 		}
 
 
@@ -5308,45 +5334,28 @@
 
 	/* Add from server list to local list */
 	for (curitem=od->ssi.local; curitem; curitem=curitem->next) {
-	  if ((curitem->name == NULL) || (g_utf8_validate(curitem->name, -1, NULL)))
 		switch (curitem->type) {
 			case AIM_SSI_TYPE_BUDDY: { /* Buddy */
 				if (curitem->name) {
 					struct aim_ssi_item *groupitem;
-					char *gname, *gname_utf8, *alias, *alias_utf8;
+					const char *gname, *alias;
 
 					groupitem = aim_ssi_itemlist_find(od->ssi.local, curitem->gid, 0x0000);
 					gname = groupitem ? groupitem->name : NULL;
-					if (gname != NULL) {
-						if (g_utf8_validate(gname, -1, NULL))
-							gname_utf8 = g_strdup(gname);
-						else
-							gname_utf8 = oscar_utf8_try_convert(account, gname);
-					} else
-						gname_utf8 = NULL;
-
-					g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans"));
+
+					g = purple_find_group(gname ? gname : _("Orphans"));
 					if (g == NULL) {
-						g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans"));
+						g = purple_group_new(gname ? gname : _("Orphans"));
 						purple_blist_add_group(g, NULL);
 					}
 
 					alias = aim_ssi_getalias(od->ssi.local, gname, curitem->name);
-					if (alias != NULL) {
-						if (g_utf8_validate(alias, -1, NULL))
-							alias_utf8 = g_strdup(alias);
-						else
-							alias_utf8 = oscar_utf8_try_convert(account, alias);
-						g_free(alias);
-					} else
-						alias_utf8 = NULL;
-
 					b = purple_find_buddy_in_group(account, curitem->name, g);
 					if (b) {
 						/* Get server stored alias */
-						purple_blist_alias_buddy(b, alias_utf8);
+						purple_blist_alias_buddy(b, alias);
 					} else {
-						b = purple_buddy_new(account, curitem->name, alias_utf8);
+						b = purple_buddy_new(account, curitem->name, alias);
 
 						purple_debug_info("oscar",
 								   "ssi: adding buddy %s to group %s to local list\n", curitem->name, gname);
@@ -5370,30 +5379,15 @@
 								purple_buddy_get_name(b),
 								OSCAR_STATUS_ID_MOBILE, NULL);
 					}
-
-					g_free(gname_utf8);
-					g_free(alias_utf8);
 				}
 			} break;
 
 			case AIM_SSI_TYPE_GROUP: { /* Group */
-				char *gname;
-				char *gname_utf8;
-
-				gname = curitem->name;
-				if (gname != NULL) {
-					if (g_utf8_validate(gname, -1, NULL))
-						gname_utf8 = g_strdup(gname);
-					else
-						gname_utf8 = oscar_utf8_try_convert(account, gname);
-				} else
-					gname_utf8 = NULL;
-
-				if (gname_utf8 != NULL && purple_find_group(gname_utf8) == NULL) {
-					g = purple_group_new(gname_utf8);
+				const char *gname = curitem->name;
+				if (gname != NULL && purple_find_group(gname) == NULL) {
+					g = purple_group_new(gname);
 					purple_blist_add_group(g, NULL);
 				}
-				g_free(gname_utf8);
 			} break;
 
 			case AIM_SSI_TYPE_PERMIT: { /* Permit buddy */
@@ -5469,6 +5463,10 @@
 	 */
 	if (od->bos.have_rights) {
 		aim_srv_clientready(od, conn);
+
+		/* Request offline messages for AIM and ICQ */
+		aim_im_reqofflinemsgs(od);
+
 		purple_connection_set_state(gc, PURPLE_CONNECTED);
 	}
 
@@ -5528,7 +5526,8 @@
 {
 	PurpleConnection *gc;
 	PurpleAccount *account;
-	char *gname, *gname_utf8, *alias, *alias_utf8;
+	const char *gname;
+	char *alias;
 	PurpleBuddy *b;
 	PurpleGroup *g;
 	struct aim_ssi_item *ssi_item;
@@ -5549,19 +5548,7 @@
 		return 1;
 
 	gname = aim_ssi_itemlist_findparentname(od->ssi.local, name);
-	gname_utf8 = gname ? oscar_utf8_try_convert(account, gname) : NULL;
-
 	alias = aim_ssi_getalias(od->ssi.local, gname, name);
-	if (alias != NULL)
-	{
-		if (g_utf8_validate(alias, -1, NULL))
-			alias_utf8 = g_strdup(alias);
-		else
-			alias_utf8 = oscar_utf8_try_convert(account, alias);
-	}
-	else
-		alias_utf8 = NULL;
-	g_free(alias);
 
 	b = purple_find_buddy(account, name);
 	if (b) {
@@ -5570,21 +5557,21 @@
 		 * of your buddies, so update our local buddy list with
 		 * the person's new alias.
 		 */
-		purple_blist_alias_buddy(b, alias_utf8);
+		purple_blist_alias_buddy(b, alias);
 	} else if (snac_subtype == 0x0008) {
 		/*
 		 * You're logged in somewhere else and you added a buddy to
 		 * your server list, so add them to your local buddy list.
 		 */
-		b = purple_buddy_new(account, name, alias_utf8);
-
-		if (!(g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans")))) {
-			g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans"));
+		b = purple_buddy_new(account, name, alias);
+
+		if (!(g = purple_find_group(gname ? gname : _("Orphans")))) {
+			g = purple_group_new(gname ? gname : _("Orphans"));
 			purple_blist_add_group(g, NULL);
 		}
 
 		purple_debug_info("oscar",
-				   "ssi: adding buddy %s to group %s to local list\n", name, gname_utf8 ? gname_utf8 : _("Orphans"));
+				   "ssi: adding buddy %s to group %s to local list\n", name, gname ? gname : _("Orphans"));
 		purple_blist_add_buddy(b, NULL, g, NULL);
 
 		/* Mobile users should always be online */
@@ -5597,6 +5584,8 @@
 
 	}
 
+	g_free(alias);
+
 	ssi_item = aim_ssi_itemlist_finditem(od->ssi.local,
 			gname, name, AIM_SSI_TYPE_BUDDY);
 	if (ssi_item == NULL)
@@ -5606,9 +5595,6 @@
 				"group %s\n", name, gname);
 	}
 
-	g_free(gname_utf8);
-	g_free(alias_utf8);
-
 	return 1;
 }
 
@@ -6311,7 +6297,6 @@
 	struct name_data *data;
 	PurpleGroup *g;
 	char *comment;
-	gchar *comment_utf8;
 	gchar *title;
 	PurpleAccount *account;
 	const char *name;
@@ -6330,7 +6315,6 @@
 	data = g_new(struct name_data, 1);
 
 	comment = aim_ssi_getcomment(od->ssi.local, purple_group_get_name(g), name);
-	comment_utf8 = comment ? oscar_utf8_try_convert(account, comment) : NULL;
 
 	data->gc = gc;
 	data->name = g_strdup(name);
@@ -6338,7 +6322,7 @@
 
 	title = g_strdup_printf(_("Buddy Comment for %s"), data->name);
 	purple_request_input(gc, title, _("Buddy Comment:"), NULL,
-					   comment_utf8, TRUE, FALSE, NULL,
+					   comment, TRUE, FALSE, NULL,
 					   _("_OK"), G_CALLBACK(oscar_ssi_editcomment),
 					   _("_Cancel"), G_CALLBACK(oscar_free_name_data),
 					   account, data->name, NULL,
@@ -6346,7 +6330,6 @@
 	g_free(title);
 
 	g_free(comment);
-	g_free(comment_utf8);
 }
 
 static void
@@ -7152,8 +7135,9 @@
 	return FALSE;
 }
 
-void oscar_init(PurplePluginProtocolInfo *prpl_info)
+void oscar_init(PurplePlugin *plugin)
 {
+	PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
 	PurpleAccountOption *option;
 	static gboolean init = FALSE;
 
@@ -7176,9 +7160,11 @@
 		OSCAR_DEFAULT_ALWAYS_USE_RV_PROXY);
 	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
 
-	option = purple_account_option_bool_new(_("Allow multiple simultaneous logins"), "allow_multiple_logins",
-											OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS);
-	prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+	if (g_str_equal(purple_plugin_get_id(plugin), "prpl-aim")) {
+		option = purple_account_option_bool_new(_("Allow multiple simultaneous logins"), "allow_multiple_logins",
+												OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS);
+		prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
+	}
 
 	if (init)
 		return;
--- a/libpurple/protocols/oscar/oscar.h	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Thu Nov 12 23:25:57 2009 +0000
@@ -623,7 +623,7 @@
 	} chat;
 };
 
-int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen);
+int oscar_connect_to_bos(PurpleConnection *gc, OscarData *od, const char *host, guint16 port, guint8 *cookie, guint16 cookielen, const char *tls_certname);
 
 /* family_auth.c */
 
@@ -661,7 +661,7 @@
 void flap_connection_send(FlapConnection *conn, FlapFrame *frame);
 void flap_connection_send_version(OscarData *od, FlapConnection *conn);
 void flap_connection_send_version_with_cookie(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy);
-void flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci);
+void flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci, gboolean allow_multiple_login);
 void flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data);
 void flap_connection_send_snac_with_priority(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data, gboolean high_priority);
 void flap_connection_send_keepalive(OscarData *od, FlapConnection *conn);
@@ -1240,7 +1240,7 @@
 #define AIM_SSI_ACK_INVALIDNAME		0x000d
 #define AIM_SSI_ACK_AUTHREQUIRED	0x000e
 
-/* These flags are set in the 0x00c9 TLV of SSI teyp 0x0005 */
+/* These flags are set in the 0x00c9 TLV of SSI type 0x0005 */
 #define AIM_SSI_PRESENCE_FLAG_SHOWIDLE        0x00000400
 #define AIM_SSI_PRESENCE_FLAG_NORECENTBUDDIES 0x00020000
 
--- a/libpurple/protocols/oscar/oscarcommon.h	Tue Nov 03 18:27:22 2009 +0000
+++ b/libpurple/protocols/oscar/oscarcommon.h	Thu Nov 12 23:25:57 2009 +0000
@@ -94,4 +94,4 @@
 gboolean oscar_offline_message(const PurpleBuddy *buddy);
 void oscar_format_username(PurpleConnection *gc, const char *nick);
 GList *oscar_actions(PurplePlugin *plugin, gpointer context);
-void oscar_init(PurplePluginProtocolInfo *prpl_info);
+void oscar_init(PurplePlugin *plugin);
--- a/pidgin.desktop.in	Tue Nov 03 18:27:22 2009 +0000
+++ b/pidgin.desktop.in	Thu Nov 12 23:25:57 2009 +0000
@@ -1,7 +1,7 @@
 [Desktop Entry]
 _Name=Pidgin Internet Messenger
 _GenericName=Internet Messenger
-_Comment=Send instant messages over multiple protocols
+_Comment=Chat over IM.  Supports AIM, Google Talk, Jabber/XMPP, MSN, Yahoo and more
 Exec=pidgin
 Icon=pidgin
 StartupNotify=true
--- a/pidgin/gtkmain.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/pidgin/gtkmain.c	Thu Nov 12 23:25:57 2009 +0000
@@ -143,6 +143,10 @@
 }
 
 #ifdef HAVE_SIGNAL_H
+static char *segfault_message;
+
+static int signal_sockets[2];
+
 static void sighandler(int sig);
 
 /*
@@ -168,31 +172,60 @@
 	}
 }
 
-char *segfault_message;
+static void sighandler(int sig)
+{
+	ssize_t written;
+
+	/*
+	 * We won't do any of the heavy lifting for the signal handling here
+	 * because we have no idea what was interrupted.  Previously this signal
+	 * handler could result in some calls to malloc/free, which can cause
+	 * deadlock in libc when the signal handler was interrupting a previous
+	 * malloc or free.  So instead we'll do an ugly hack where we write the
+	 * signal number to one end of a socket pair.  The other half of the
+	 * socket pair is watched by our main loop.  When the main loop sees new
+	 * data on the socket it reads in the signal and performs the appropriate
+	 * action without fear of interrupting stuff.
+	 */
+	if (sig == SIGSEGV) {
+		fprintf(stderr, "%s", segfault_message);
+		abort();
+		return;
+	}
 
-/*
- * This signal handler shouldn't be touching this much stuff.
- * It should just set a flag and return, and something else in
- * Pidgin should monitor the flag to see if something needs to
- * be done.  Because the signal handler interrupts the program,
- * it could be called in the middle of adding a new connection
- * to the list of connections, and then if we try to disconnect
- * all connections it could lead to a crash because the linked
- * list of connections could be in a weird state.  But, well,
- * this signal handler probably isn't called very often, so it's
- * not a big deal.
- */
-static void
-sighandler(int sig)
+	written = write(signal_sockets[0], &sig, sizeof(int));
+	if (written < 0 || written != sizeof(int)) {
+		/* This should never happen */
+		purple_debug_error("sighandler", "Received signal %d but only "
+				"wrote %" G_GSSIZE_FORMAT " bytes out of %"
+				G_GSIZE_FORMAT ": %s\n",
+				sig, written, sizeof(int), g_strerror(errno));
+		exit(1);
+	}
+}
+
+static gboolean
+mainloop_sighandler(GIOChannel *source, GIOCondition cond, gpointer data)
 {
+	GIOStatus stat;
+	int sig;
+	gsize bytes_read;
+	GError *error = NULL;
+
+	/* read the signal number off of the io channel */
+	stat = g_io_channel_read_chars(source, (gchar *)&sig, sizeof(int),
+			&bytes_read, &error);
+	if (stat != G_IO_STATUS_NORMAL) {
+		purple_debug_error("sighandler", "Signal callback failed to read "
+				"from signal socket: %s", error->message);
+		purple_core_quit();
+		return FALSE;
+	}
+
 	switch (sig) {
 	case SIGHUP:
 		purple_debug_warning("sighandler", "Caught signal %d\n", sig);
 		break;
-	case SIGSEGV:
-		fprintf(stderr, "%s", segfault_message);
-		abort();
-		break;
 #if defined(USE_GSTREAMER) && !defined(GST_CAN_DISABLE_FORKING)
 /* By default, gstreamer forks when you initialize it, and waitpids for the
  * child.  But if libpurple reaps the child rather than leaving it to
@@ -219,6 +252,8 @@
 		purple_debug_warning("sighandler", "Caught signal %d\n", sig);
 		purple_core_quit();
 	}
+
+	return TRUE;
 }
 #endif
 
@@ -502,11 +537,13 @@
 	sigset_t sigset;
 	RETSIGTYPE (*prev_sig_disp)(int);
 	char errmsg[BUFSIZ];
+	GIOChannel *signal_channel;
+	GIOStatus signal_status;
 #ifndef DEBUG
 	char *segfault_message_tmp;
+#endif
 	GError *error = NULL;
 #endif
-#endif
 	int opt;
 	gboolean gui_check;
 	gboolean debug_enabled;
@@ -592,6 +629,29 @@
 		);
 #endif
 
+	/*
+	 * Create a socket pair for receiving unix signals from a signal
+	 * handler.
+	 */
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, signal_sockets) < 0) {
+		perror("Failed to create sockets for GLib signal handling");
+		exit(1);
+	}
+	signal_channel = g_io_channel_unix_new(signal_sockets[1]);
+
+	/*
+	 * Set the channel encoding to raw binary instead of the default of
+	 * UTF-8, because we'll be sending integers across instead of strings.
+	 */
+	error = NULL;
+	signal_status = g_io_channel_set_encoding(signal_channel, NULL, &error);
+	if (signal_status != G_IO_STATUS_NORMAL) {
+		fprintf(stderr, "Failed to set the signal channel to raw "
+				"binary: %s", error->message);
+		exit(1);
+	}
+	g_io_add_watch(signal_channel, G_IO_IN, mainloop_sighandler, NULL);
+
 	/* Let's not violate any PLA's!!!! */
 	/* jseymour: whatever the fsck that means */
 	/* Robot101: for some reason things like gdm like to block     *
--- a/pidgin/gtkprefs.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/pidgin/gtkprefs.c	Thu Nov 12 23:25:57 2009 +0000
@@ -1134,7 +1134,7 @@
 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
 	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
 
-	gtk_box_pack_start(GTK_BOX(ret), label, FALSE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(ret), label, TRUE, TRUE, 0);
 	gtk_widget_show(label);
 
 	sw = gtk_scrolled_window_new(NULL,NULL);
--- a/pidgin/pixmaps/Makefile.am	Tue Nov 03 18:27:22 2009 +0000
+++ b/pidgin/pixmaps/Makefile.am	Thu Nov 12 23:25:57 2009 +0000
@@ -236,6 +236,7 @@
 		protocols/16/jabber.png \
 		protocols/16/meanwhile.png \
 		protocols/16/msn.png \
+		protocols/16/mxit.png \
 		protocols/16/myspace.png \
 		protocols/16/qq.png \
 		protocols/16/silc.png \
@@ -308,6 +309,7 @@
 		protocols/48/jabber.png \
 		protocols/48/meanwhile.png \
 		protocols/48/msn.png \
+		protocols/48/mxit.png \
 		protocols/48/myspace.png \
 		protocols/48/qq.png \
 		protocols/48/silc.png \
@@ -326,6 +328,7 @@
 		protocols/scalable/jabber.svg \
 		protocols/scalable/meanwhile.svg \
 		protocols/scalable/msn.svg \
+		protocols/scalable/mxit.svg \
 		protocols/scalable/qq.svg \
 		protocols/scalable/silc.svg \
 		protocols/scalable/simple.svg \
Binary file pidgin/pixmaps/protocols/16/mxit.png has changed
Binary file pidgin/pixmaps/protocols/22/mxit.png has changed
Binary file pidgin/pixmaps/protocols/48/mxit.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pixmaps/protocols/scalable/mxit.svg	Thu Nov 12 23:25:57 2009 +0000
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
+<image overflow="visible" width="34" height="39" xlink:href="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/7AARRHVja3kAAQAEAAAAHgAA/+4AIUFkb2JlAGTAAAAAAQMA
+EAMCAwYAAAH0AAACSgAAA6n/2wCEABALCwsMCxAMDBAXDw0PFxsUEBAUGx8XFxcXFx8eFxoaGhoX
+Hh4jJSclIx4vLzMzLy9AQEBAQEBAQEBAQEBAQEABEQ8PERMRFRISFRQRFBEUGhQWFhQaJhoaHBoa
+JjAjHh4eHiMwKy4nJycuKzU1MDA1NUBAP0BAQEBAQEBAQEBAQP/CABEIACgAIwMBIgACEQEDEQH/
+xACyAAEAAwEBAAAAAAAAAAAAAAAAAQMFBAIBAAMBAQAAAAAAAAAAAAAAAAABAgMEEAABBAECBgMB
+AAAAAAAAAAABAAIDBAUREyESIhQVRRAjNQYRAAEDAgMDBwgLAAAAAAAAAAERAgMAEiETBDFRIkFh
+cUIUhcWhMlKDs9M01GKSIzNzwyRkhJQVEgABAwAGCQUAAAAAAAAAAAAAARECIUFRYYESEDFxodEy
+QoKicjNDNIT/2gAMAwEAAhEDEQAAANWbtDvwx51+eStam/NlFqUEyBo7eck8dAFoyr//2gAIAQIA
+AQUAe+RpEry7QJ4POA4FDeR3V9q//9oACAEDAAEFAAAUWjRDTTh8dC6V0r//2gAIAQEAAQUAiihn
+hNOsu1rBMr0HLks9hi7UdSE5ytyx5evM/J6dx6rCcInfp3ON2+CZfVY1z46RMm9IHyveZpW+q8Vc
+rtkr51qbF/QEx0s04eCg2//aAAgBAgIGPwBoxWSbRIyisXvNSfZsJrbFYkFblRlP1FOTHK+J8fid
+XuefE//aAAgBAwIGPwCmTDpJzsE9Tkrzs0VlXLuP/9oACAEBAQY/AIZ54GanU6mJk8sksTZnEyNa
+9Be11rW3IAMK+ChH8SP3dY6KH+pH7uvhNORy2wxscOhzGtc01n9rmvzP85bup2zs2b+JZ1q0csgJ
+adHC3hRVMcR5SN1XWSIqLw7fr0GNa8KQ242oC7Aea4moCAFc14cd4FqV3n4lWjP7KL2cNXfT/Kph
+3ZPtDUL0Nrbmk7i61K7z8SrQyNAd+khaQSm2OPmO6s5G3XXWrgllm1KLyjCjbUN2LSXLsFBha0BW
+kkOJ80g7Leau8vEqy9HPEdO0/ZRTxOe6NvoNeyWPhHIow31wt0rvVS/MVjHph6qX5iuKXTQr1mwv
+Lhzi+dwXpFZWdNZk5SX9fMzs/Z95fivkr//Z" transform="matrix(0.9999 0 0 0.9999 7.0146 6.0142)">
+</image>
+</svg>
--- a/pidgin/plugins/disco/gtkdisco.c	Tue Nov 03 18:27:22 2009 +0000
+++ b/pidgin/plugins/disco/gtkdisco.c	Thu Nov 12 23:25:57 2009 +0000
@@ -162,12 +162,15 @@
 
 static void discolist_cancel_cb(PidginDiscoList *pdl, const char *server)
 {
+	pdl->dialog->prompt_handle = NULL;
+
 	pidgin_disco_list_set_in_progress(pdl, FALSE);
 	pidgin_disco_list_unref(pdl);
 }
 
 static void discolist_ok_cb(PidginDiscoList *pdl, const char *server)
 {
+	pdl->dialog->prompt_handle = NULL;
 	gtk_widget_set_sensitive(pdl->dialog->browse_button, TRUE);
 
 	if (!server || !*server) {
@@ -236,7 +239,7 @@
 
 	/* Note to translators: The string "Enter an XMPP Server" is asking the
 	   user to type the name of an XMPP server which will then be queried */
-	purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"),
+	dialog->prompt_handle = purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"),
 			_("Select an XMPP server to query"),
 			server, FALSE, FALSE, NULL,
 			_("Find Services"), PURPLE_CALLBACK(discolist_ok_cb),
@@ -390,6 +393,9 @@
 	PidginDiscoDialog *dialog = d;
 	PidginDiscoList *list = dialog->discolist;
 
+	if (dialog->prompt_handle)
+		purple_request_close(PURPLE_REQUEST_INPUT, dialog->prompt_handle);
+
 	if (list) {
 		list->dialog = NULL;
 
--- a/pidgin/plugins/disco/gtkdisco.h	Tue Nov 03 18:27:22 2009 +0000
+++ b/pidgin/plugins/disco/gtkdisco.h	Thu Nov 12 23:25:57 2009 +0000
@@ -43,6 +43,8 @@
 
 	PurpleAccount *account;
 	PidginDiscoList *discolist;
+
+	gpointer *prompt_handle;
 };
 
 struct _PidginDiscoList {
--- a/po/POTFILES.in	Tue Nov 03 18:27:22 2009 +0000
+++ b/po/POTFILES.in	Thu Nov 12 23:25:57 2009 +0000
@@ -126,6 +126,15 @@
 libpurple/protocols/msnp9/state.c
 libpurple/protocols/msnp9/switchboard.c
 libpurple/protocols/msnp9/userlist.c
+libpurple/protocols/mxit/actions.c
+libpurple/protocols/mxit/filexfer.c
+libpurple/protocols/mxit/http.c
+libpurple/protocols/mxit/login.c
+libpurple/protocols/mxit/mxit.c
+libpurple/protocols/mxit/profile.c
+libpurple/protocols/mxit/protocol.c
+libpurple/protocols/mxit/roster.c
+libpurple/protocols/mxit/splashscreen.c
 libpurple/protocols/myspace/myspace.c
 libpurple/protocols/myspace/user.c
 libpurple/protocols/myspace/zap.c