changeset 32117:585d6f844f79

propagate from branch 'im.pidgin.pidgin' (head 37329f033a30f4f4f5048f9fdc86df4400e3ada5) to branch 'im.pidgin.soc.2009.webkitmessageview' (head 7be18cb8139c12d8080e3e0603264c2a623a15d3)
author Paul Aurich <paul@darkrain42.org>
date Mon, 09 Nov 2009 01:42:24 +0000
parents 231af7ce1b49 (diff) 994e8d214754 (current diff)
children 041d5144730f
files configure.ac libpurple/protocols/jabber/JEPS pidgin/gtkconv.c
diffstat 132 files changed, 12826 insertions(+), 938 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Sun Nov 08 01:12:44 2009 +0000
+++ b/COPYRIGHT	Mon Nov 09 01:42:24 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
@@ -406,11 +409,10 @@
 Carsten Schaar
 Toby Schaffer
 Jonathan Schleifer <js-pidgin@webkeks.org>
-Matteo Settenvini
-Colin Seymour
 Luke Schierer
 Ralph Schmieder
 David Schmitt
+Heiko Schmitt
 Mark Schneider
 Evan Schoenberg
 Gabriel Schulhof
@@ -420,6 +422,8 @@
 Peter Seebach
 Don Seiler
 Leonardo Serra
+Matteo Settenvini
+Colin Seymour
 Jim Seymour
 Javeed Shaikh
 Joe Shaw
@@ -495,6 +499,7 @@
 James Vega
 David Vermeille
 Sid Vicious
+Andrew Victor
 Jorge Villaseñor (Masca)
 Bjoern Voigt
 Wan Hing Wah
--- a/ChangeLog	Sun Nov 08 01:12:44 2009 +0000
+++ b/ChangeLog	Mon Nov 09 01:42:24 2009 +0000
@@ -1,13 +1,34 @@
+
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-version 2.6.3 (??/??/20??):
+version 2.6.4 (??/??/20??):
+	libpurple:
+	* Actually emit the hold signal for media calls.
+
 	General:
 	* New 'plugins' sub-command to 'debug' command (i.e. '/debug plugins')
 	  to announce the list of loaded plugins (in both Finch and Pidgin).
-	* Fix a crash when performing DNS queries on Unixes that use the
-	  blocking DNS lookups.  (Brian Lu)
 	* Fix building the GnuTLS plugin with older versions of GnuTLS.
 	* Fix DNS TXT query resolution.
+	* Always rejoin open chats after an account reconnects.
+
+	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.
+	* Fix a random crash that might occur when idle.
+	* 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
@@ -15,6 +36,9 @@
 	* 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 '@'.
 
 	Yahoo:
@@ -32,7 +56,18 @@
 
 	Pidgin:
 	* The userlist in a multiuser chat can be styled via gtkrc by using the
-	  widget name "pidgin_conv_userlist".
+	  widget name "pidgin_conv_userlist". (Heiko Schmitt)
+	* Add a hold button to the media window.
+
+version 2.6.3 (10/16/2009):
+	General:
+	* Fix a crash when performing DNS queries on Unixes that use the
+	  blocking DNS lookups.  (Brian Lu)
+
+	AIM and ICQ:
+	* Fix a crash when some clients send contacts in a format we don't
+	  understand.
+	* Fix blocking and other privacy lists.  (Thanks to AOL)
 
 version 2.6.2 (09/05/2009):
 	libpurple:
--- a/ChangeLog.API	Sun Nov 08 01:12:44 2009 +0000
+++ b/ChangeLog.API	Mon Nov 09 01:42:24 2009 +0000
@@ -1,5 +1,8 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.6.3 (10/16/2009):
+	No changes
+
 version 2.6.2 (09/05/2009):
 	Perl:
 		Added:
--- a/ChangeLog.win32	Sun Nov 08 01:12:44 2009 +0000
+++ b/ChangeLog.win32	Mon Nov 09 01:42:24 2009 +0000
@@ -1,3 +1,6 @@
+version 2.6.3 (10/16/2009):
+	* No changes
+
 version 2.6.2 (09/05/2009):
 	* No changes
 
--- a/NEWS	Sun Nov 08 01:12:44 2009 +0000
+++ b/NEWS	Mon Nov 09 01:42:24 2009 +0000
@@ -2,6 +2,11 @@
 
 Our development blog is available at: http://planet.pidgin.im
 
+2.6.3 (10/16/2009):
+	Mark: Someone reported a fairly serious bug in our AIM/ICQ code
+	so we're releasing a special "severe bug fix only" build.  See the
+	ChangeLog for details.  Enjoy!
+
 2.6.2 (09/05/2009):
 	Mark: Woo boy it's been a busy two weeks.  There was a lot of new code
 	in 2.6.0, and with new code comes new bugs.  The cadre of relentless
--- a/autogen.sh	Sun Nov 08 01:12:44 2009 +0000
+++ b/autogen.sh	Mon Nov 09 01:42:24 2009 +0000
@@ -83,7 +83,7 @@
 
 	OUTPUT=`mktemp autogen-XXXXXX`
 
-	printf "%s" "running ${CMD} ${@}... "
+	printf "running %s %s... " ${CMD} "$*"
 	${CMD} ${@} >${OUTPUT} 2>&1
 
 	if [ $? != 0 ] ; then
@@ -99,9 +99,17 @@
 	fi
 }
 
+cleanup () {
+	rm -f autogen-??????
+	echo
+	exit 2
+}
+
 ###############################################################################
 # We really start here, yes, very sneaky!
 ###############################################################################
+trap cleanup 2
+
 FIGLET=`which figlet 2> /dev/null`
 if [ x"${FIGLET}" != x"" ] ; then
 	${FIGLET} -f small ${PACKAGE}
@@ -143,7 +151,7 @@
 run_or_die ${INTLTOOLIZE} ${INTLTOOLIZE_FLAGS:-"-c -f --automake"}
 # This call to sed is needed to work around an annoying bug in intltool 0.40.6
 # See http://developer.pidgin.im/ticket/9520 for details
-run_or_die ${SED} "s:'\^\$\$lang\$\$':\^\$\$lang\$\$:g" -i po/Makefile.in.in
+run_or_die ${SED} -i.bak -e "s:'\^\$\$lang\$\$':\^\$\$lang\$\$:g" po/Makefile.in.in
 run_or_die ${ACLOCAL} ${ACLOCAL_FLAGS:-"-I m4macros"}
 run_or_die ${AUTOHEADER} ${AUTOHEADER_FLAGS}
 run_or_die ${AUTOMAKE} ${AUTOMAKE_FLAGS:-"-a -c --gnu"}
--- a/configure.ac	Sun Nov 08 01:12:44 2009 +0000
+++ b/configure.ac	Mon Nov 09 01:42:24 2009 +0000
@@ -46,7 +46,7 @@
 m4_define([purple_lt_current], [6])
 m4_define([purple_major_version], [2])
 m4_define([purple_minor_version], [6])
-m4_define([purple_micro_version], [3])
+m4_define([purple_micro_version], [4])
 m4_define([purple_version_suffix], [devel])
 m4_define([purple_version],
           [purple_major_version.purple_minor_version.purple_micro_version])
@@ -55,7 +55,7 @@
 m4_define([gnt_lt_current], [6])
 m4_define([gnt_major_version], [2])
 m4_define([gnt_minor_version], [6])
-m4_define([gnt_micro_version], [3])
+m4_define([gnt_micro_version], [4])
 m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
@@ -1080,7 +1080,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//'`
@@ -1137,6 +1137,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 ;;
@@ -1157,6 +1158,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")
@@ -1171,7 +1173,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//'`
@@ -1198,6 +1200,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 ;;
@@ -2532,6 +2535,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/gntconn.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/finch/gntconn.c	Mon Nov 09 01:42:24 2009 +0000
@@ -107,7 +107,6 @@
 {
 	FinchAutoRecon *info;
 	PurpleAccount *account = purple_connection_get_account(gc);
-	GList *list;
 
 	if (!purple_connection_error_is_fatal(reason)) {
 		info = g_hash_table_lookup(hash, account);
@@ -144,21 +143,6 @@
 		g_free(secondary);
 		purple_account_set_enabled(account, FINCH_UI, FALSE);
 	}
-
-	/* If we have any open chats, we probably want to rejoin when we get back online. */
-	list = purple_get_chats();
-	while (list) {
-		PurpleConversation *conv = list->data;
-		list = list->next;
-		if (purple_conversation_get_account(conv) != account ||
-				purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)))
-			continue;
-		purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE));
-		purple_conversation_write(conv, NULL, _("The account has disconnected and you are no "
-					"longer in this chat. You will be automatically rejoined in the chat when "
-					"the account reconnects."),
-				PURPLE_MESSAGE_SYSTEM, time(NULL));
-	}
 }
 
 static void
--- a/finch/gntconv.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/finch/gntconv.c	Mon Nov 09 01:42:24 2009 +0000
@@ -367,6 +367,28 @@
 	}
 }
 
+static void
+account_signing_off(PurpleConnection *gc)
+{
+	GList *list = purple_get_chats();
+	PurpleAccount *account = purple_connection_get_account(gc);
+
+	/* We are about to sign off. See which chats we are currently in, and mark
+	 * them for rejoin on reconnect. */
+	while (list) {
+		PurpleConversation *conv = list->data;
+		if (!purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)) &&
+				purple_conversation_get_account(conv) == account) {
+			purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE));
+			purple_conversation_write(conv, NULL, _("The account has disconnected and you are no "
+						"longer in this chat. You will be automatically rejoined in the chat when "
+						"the account reconnects."),
+					PURPLE_MESSAGE_SYSTEM, time(NULL));
+		}
+		list = list->next;
+	}
+}
+
 static gpointer
 finch_conv_get_handle(void)
 {
@@ -642,8 +664,25 @@
 create_conv_from_userlist(GntWidget *widget, FinchConv *fc)
 {
 	PurpleAccount *account = purple_conversation_get_account(fc->active_conv);
-	char *name = gnt_tree_get_selection_data(GNT_TREE(widget));
-	purple_conversation_new(PURPLE_CONV_TYPE_IM, account, name);
+	PurpleConnection *gc = purple_account_get_connection(account);
+	PurplePluginProtocolInfo *prpl_info = NULL;
+	char *name, *realname;
+
+	if (!gc) {
+		purple_conversation_write(fc->active_conv, NULL, _("You are not connected."),
+				PURPLE_MESSAGE_SYSTEM, time(NULL));
+		return;
+	}
+
+	name = gnt_tree_get_selection_data(GNT_TREE(widget));
+
+	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+	if (prpl_info && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, get_cb_real_name))
+		realname = prpl_info->get_cb_real_name(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(fc->active_conv)), name);
+	else
+		realname = NULL;
+	purple_conversation_new(PURPLE_CONV_TYPE_IM, account, realname ? realname : name);
+	g_free(realname);
 }
 
 static void
@@ -1433,6 +1472,8 @@
 					PURPLE_CALLBACK(account_signed_on_off), NULL);
 	purple_signal_connect(purple_connections_get_handle(), "signed-off", finch_conv_get_handle(),
 					PURPLE_CALLBACK(account_signed_on_off), NULL);
+	purple_signal_connect(purple_connections_get_handle(), "signing-off", finch_conv_get_handle(),
+					PURPLE_CALLBACK(account_signing_off), NULL);
 }
 
 void finch_conversation_uninit()
--- a/finch/libgnt/gntentry.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/finch/libgnt/gntentry.c	Mon Nov 09 01:42:24 2009 +0000
@@ -1044,8 +1044,11 @@
 		snprintf(entry->start, len + 1, "%s", text);
 	entry->end = entry->start + len;
 
-	entry->scroll = entry->start + scroll;
-	entry->cursor = entry->end - cursor;
+	if ((entry->scroll = entry->start + scroll) > entry->end)
+		entry->scroll = entry->end;
+
+	if ((entry->cursor = entry->end - cursor) > entry->end)
+		entry->cursor = entry->end;
 
 	if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(entry), GNT_WIDGET_MAPPED))
 		entry_redraw(GNT_WIDGET(entry));
--- a/finch/libgnt/gnttextview.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/finch/libgnt/gnttextview.c	Mon Nov 09 01:42:24 2009 +0000
@@ -67,6 +67,12 @@
 
 static void reset_text_view(GntTextView *view);
 
+static gboolean
+text_view_contains(GntTextView *view, const char *str)
+{
+	return (str >= view->string->str && str < view->string->str + view->string->len);
+}
+
 static void
 gnt_text_view_draw(GntWidget *widget)
 {
@@ -109,7 +115,7 @@
 			char back = *end;
 			chtype fl = seg->flags;
 			*end = '\0';
-			if (select_start < view->string->str + seg->start && select_end > view->string->str + seg->end) {
+			if (select_start && select_start < view->string->str + seg->start && select_end > view->string->str + seg->end) {
 				fl |= A_REVERSE;
 				wattrset(widget->window, fl);
 				wprintw(widget->window, "%s", (view->string->str + seg->start));
@@ -326,9 +332,10 @@
 		select_start = gnt_text_view_get_p(GNT_TEXT_VIEW(widget), x - widget->priv.x, y - widget->priv.y);
 		g_timeout_add(500, too_slow, NULL);
 	} else if (event == GNT_MOUSE_UP) {
-		if (select_start) {
+		GntTextView *view = GNT_TEXT_VIEW(widget);
+		if (text_view_contains(view, select_start)) {
 			GString *clip;
-			select_end = gnt_text_view_get_p(GNT_TEXT_VIEW(widget), x - widget->priv.x, y - widget->priv.y);
+			select_end = gnt_text_view_get_p(view, x - widget->priv.x, y - widget->priv.y);
 			if (select_end < select_start) {
 				gchar *t = select_start;
 				select_start = select_end;
@@ -336,7 +343,7 @@
 			}
 			if (select_start == select_end) {
 				if (double_click) {
-					clip = select_word_text(GNT_TEXT_VIEW(widget), select_start);
+					clip = select_word_text(view, select_start);
 					double_click = FALSE;
 				} else {
 					double_click = TRUE;
--- a/finch/libgnt/gnttree.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/finch/libgnt/gnttree.c	Mon Nov 09 01:42:24 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/Makefile.am	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/Makefile.am	Mon Nov 09 01:42:24 2009 +0000
@@ -32,7 +32,7 @@
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = purple.pc
 
-SUBDIRS = $(GCONF_DIR) plugins protocols tests . example
+SUBDIRS = $(GCONF_DIR) plugins protocols . tests example
 
 purple_coresources = \
 	account.c \
--- a/libpurple/account.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/account.c	Mon Nov 09 01:42:24 2009 +0000
@@ -1050,6 +1050,16 @@
 	if(account->system_log)
 		purple_log_free(account->system_log);
 
+	while (account->deny) {
+		g_free(account->deny->data);
+		account->deny = g_slist_delete_link(account->deny, account->deny);
+	}
+
+	while (account->permit) {
+		g_free(account->permit->data);
+		account->permit = g_slist_delete_link(account->permit, account->permit);
+	}
+
 	priv = PURPLE_ACCOUNT_GET_PRIVATE(account);
 	PURPLE_DBUS_UNREGISTER_POINTER(priv->current_error);
 	if (priv->current_error) {
--- a/libpurple/certificate.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/certificate.c	Mon Nov 09 01:42:24 2009 +0000
@@ -1402,13 +1402,15 @@
 		if (flags & PURPLE_CERTIFICATE_NAME_MISMATCH) {
 			gchar *sn = purple_certificate_get_subject_name(peer_crt);
 
-			g_string_append_printf(errors, _("The certificate claims to be "
-						"from \"%s\" instead. This could mean that you are "
-						"not connecting to the service you believe you are."),
-						sn);
-			g_free(sn);
+			if (sn) {
+				g_string_append_printf(errors, _("The certificate claims to be "
+							"from \"%s\" instead. This could mean that you are "
+							"not connecting to the service you believe you are."),
+							sn);
+				g_free(sn);
 
-			flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH;
+				flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH;
+			}
 		}
 
 		while (i != PURPLE_CERTIFICATE_LAST) {
--- a/libpurple/cipher.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/cipher.c	Mon Nov 09 01:42:24 2009 +0000
@@ -50,10 +50,6 @@
  * 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 <string.h>
-#include <stdio.h>
-
 #include "internal.h"
 #include "cipher.h"
 #include "dbus-maybe.h"
--- a/libpurple/dnsquery.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/dnsquery.c	Mon Nov 09 01:42:24 2009 +0000
@@ -172,6 +172,7 @@
 	return FALSE;
 }
 
+#ifdef USE_IDN
 static gboolean
 dns_str_is_ascii(const char *name)
 {
@@ -183,6 +184,7 @@
 
 	return TRUE;
 }
+#endif
 
 #if defined(PURPLE_DNSQUERY_USE_FORK)
 
@@ -293,12 +295,11 @@
 			rc = purple_network_convert_idn_to_ascii(dns_params.hostname, &hostname);
 			if (rc != 0) {
 				write_to_parent(child_out, &rc, sizeof(rc));
-				close(child_out);
 				if (show_debug)
 					fprintf(stderr, "dns[%d] Error: IDN conversion returned "
 							"%d\n", getpid(), rc);
 				dns_params.hostname[0] = '\0';
-				continue;
+				break;
 			}
 		} else /* intentional to execute the g_strdup */
 #endif
@@ -323,14 +324,13 @@
 		rc = getaddrinfo(hostname, servname, &hints, &res);
 		write_to_parent(child_out, &rc, sizeof(rc));
 		if (rc != 0) {
-			close(child_out);
 			if (show_debug)
 				printf("dns[%d] Error: getaddrinfo returned %d\n",
 					getpid(), rc);
 			dns_params.hostname[0] = '\0';
 			g_free(hostname);
 			hostname = NULL;
-			continue;
+			break;
 		}
 		tmp = res;
 		while (res) {
--- a/libpurple/dnssrv.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/dnssrv.c	Mon Nov 09 01:42:24 2009 +0000
@@ -248,6 +248,7 @@
 	return list;
 }
 
+#ifdef USE_IDN
 static gboolean
 dns_str_is_ascii(const char *name)
 {
@@ -259,8 +260,60 @@
 
 	return TRUE;
 }
+#endif
 
 #ifndef _WIN32
+static void
+write_to_parent(int in, int out, gconstpointer data, gsize size)
+{
+	const guchar *buf = data;
+	gssize w;
+
+	do {
+		w = write(out, buf, size);
+		if (w > 0) {
+			buf += w;
+			size -= w;
+		} else if (w < 0 && errno == EINTR) {
+			/* Let's try some more; */
+			w = 1;
+		}
+	} while (size > 0 && w > 0);
+
+	if (size != 0) {
+		/* An error occurred */
+		close(out);
+		close(in);
+		_exit(0);
+	}
+}
+
+/* Read size bytes to data. Dies if an error occurs. */
+static void
+read_from_parent(int in, int out, gpointer data, gsize size)
+{
+	guchar *buf = data;
+	gssize r;
+
+	do {
+		r = read(in, data, size);
+		if (r > 0) {
+			buf += r;
+			size -= r;
+		} else if (r < 0 && errno == EINTR) {
+			/* Let's try some more; */
+			r = 1;
+		}
+	} while (size > 0 && r > 0);
+
+	if (size != 0) {
+		/* An error occurred */
+		close(out);
+		close(in);
+		_exit(0);
+	}
+}
+
 
 G_GNUC_NORETURN static void
 resolve(int in, int out)
@@ -279,16 +332,12 @@
 	purple_restore_default_signal_handlers();
 #endif
 
-	if (read(in, &query, sizeof(query)) <= 0) {
-		close(out);
-		close(in);
-		_exit(0);
-	}
+	read_from_parent(in, out, &query, sizeof(query));
 
 	size = res_query( query.query, C_IN, query.type, (u_char*)&answer, sizeof( answer));
 	if (size == -1) {
-		write(out, &(query.type), sizeof(query.type));
-		write(out, &size, sizeof(int));
+		write_to_parent(in, out, &(query.type), sizeof(query.type));
+		write_to_parent(in, out, &size, sizeof(size));
 		close(out);
 		close(in);
 		_exit(0);
@@ -353,19 +402,17 @@
 	if (query.type == T_SRV)
 		ret = purple_srv_sort(ret);
 
-	/* TODO: Check return value */
-	write(out, &(query.type), sizeof(query.type));
-	write(out, &size, sizeof(size));
+	write_to_parent(in, out, &(query.type), sizeof(query.type));
+	write_to_parent(in, out, &size, sizeof(size));
 	while (ret != NULL)
 	{
-		/* TODO: Check return value */
 		if (query.type == T_SRV)
-			write(out, ret->data, sizeof(PurpleSrvResponse));
+			write_to_parent(in, out, ret->data, sizeof(PurpleSrvResponse));
 		if (query.type == T_TXT) {
 			PurpleTxtResponse *response = ret->data;
 			gsize l = strlen(response->content) + 1 /* null byte */;
-			write(out, &l, sizeof(l));
-			write(out, response->content, l);
+			write_to_parent(in, out, &l, sizeof(l));
+			write_to_parent(in, out, response->content, l);
 		}
 
 		g_free(ret->data);
--- a/libpurple/ft.h	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/ft.h	Mon Nov 09 01:42:24 2009 +0000
@@ -674,7 +674,7 @@
 void purple_xfer_ui_ready(PurpleXfer *xfer);
 
 /**
- * Allows the prpl to signal it's readh to send/receive data (depending on
+ * Allows the prpl to signal it's ready to send/receive data (depending on
  * the direction of the file transfer. Used when the prpl provides read/write
  * ops and cannot/does not provide a raw fd to the core.
  *
--- a/libpurple/media.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/media.c	Mon Nov 09 01:42:24 2009 +0000
@@ -103,6 +103,8 @@
 	gboolean initiator;
 	gboolean accepted;
 	gboolean candidates_prepared;
+	gboolean held;
+	gboolean paused;
 
 	GList *active_local_candidates;
 	GList *active_remote_candidates;
@@ -281,7 +283,7 @@
 			{ PURPLE_MEDIA_INFO_HOLD,
 					"PURPLE_MEDIA_INFO_HOLD", "hold" },
 			{ PURPLE_MEDIA_INFO_UNHOLD,
-					"PURPLE_MEDIA_INFO_HOLD", "unhold" },
+					"PURPLE_MEDIA_INFO_UNHOLD", "unhold" },
 			{ 0, NULL, NULL }
 		};
 		type = g_enum_register_static("PurpleMediaInfoType", values);
@@ -2330,11 +2332,46 @@
 		for (; streams; streams = g_list_delete_link(streams, streams)) {
 			PurpleMediaStream *stream = streams->data;
 			if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) {
+				stream->paused = active;
+
+				if (!stream->held)
+					g_object_set(stream->stream, "direction",
+							purple_media_to_fs_stream_direction(
+							stream->session->type & ((active) ?
+							~PURPLE_MEDIA_SEND_VIDEO :
+							PURPLE_MEDIA_VIDEO)), NULL);
+			}
+		}
+	} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD ||
+			type == PURPLE_MEDIA_INFO_UNHOLD)) {
+		GList *streams;
+		gboolean active = (type == PURPLE_MEDIA_INFO_HOLD);
+
+		g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+		streams = purple_media_get_streams(media,
+				session_id, participant);
+		for (; streams; streams = g_list_delete_link(streams, streams)) {
+			PurpleMediaStream *stream = streams->data;
+			stream->held = active;
+			if (stream->session->type & PURPLE_MEDIA_VIDEO) {
+				FsStreamDirection direction;
+
+				direction = ((active) ?
+						~PURPLE_MEDIA_VIDEO :
+						PURPLE_MEDIA_VIDEO);
+				if (!active && stream->paused)
+					direction &= ~PURPLE_MEDIA_SEND_VIDEO;
+
+				g_object_set(stream->stream, "direction",
+						purple_media_to_fs_stream_direction(
+						stream->session->type & direction), NULL);
+			} else if (stream->session->type & PURPLE_MEDIA_AUDIO) {
 				g_object_set(stream->stream, "direction",
 						purple_media_to_fs_stream_direction(
 						stream->session->type & ((active) ?
-						~PURPLE_MEDIA_SEND_VIDEO :
-						PURPLE_MEDIA_VIDEO)), NULL);
+						~PURPLE_MEDIA_AUDIO :
+						PURPLE_MEDIA_AUDIO)), NULL);
 			}
 		}
 	}
--- a/libpurple/plugins/perl/Makefile.mingw	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/plugins/perl/Makefile.mingw	Mon Nov 09 01:42:24 2009 +0000
@@ -7,10 +7,12 @@
 PIDGIN_TREE_TOP := ../../..
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
+DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES))
+
 TARGET = perl
 
 # Perl headers with /* /* */ type comments.. Turn off warnings.
-CFLAGS += -Wno-comment
+GCCWARNINGS += -Wno-comment
 
 ##
 ## INCLUDE PATHS
--- a/libpurple/plugins/perl/common/Makefile.mingw	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/plugins/perl/common/Makefile.mingw	Mon Nov 09 01:42:24 2009 +0000
@@ -5,9 +5,12 @@
 #
 
 PIDGIN_TREE_TOP := ../../../..
-GCCWARNINGS := -Wno-comment -Waggregate-return -Wcast-align -Wdeclaration-after-statement -Werror-implicit-function-declaration -Wextra -Wno-sign-compare -Wno-unused-parameter -Winit-self -Wmissing-declarations -Wmissing-prototypes -Wpointer-arith -Wundef -Wno-unused
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
+GCCWARNINGS += -Wno-comment -Wno-unused -Wno-nested-externs
+
+DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES))
+
 TARGET = Purple
 AUTOSPLIT = lib/auto/Purple/autosplit.ix
 EXTUTILS ?= C:/perl/lib/ExtUtils
--- a/libpurple/plugins/perl/perl-handlers.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/plugins/perl/perl-handlers.c	Mon Nov 09 01:42:24 2009 +0000
@@ -649,6 +649,7 @@
 static void
 destroy_cmd_handler(PurplePerlCmdHandler *handler)
 {
+	purple_cmd_unregister(handler->id);
 	cmd_handlers = g_slist_remove(cmd_handlers, handler);
 
 	if (handler->callback != NULL)
@@ -705,7 +706,6 @@
 		return;
 	}
 
-	purple_cmd_unregister(id);
 	destroy_cmd_handler(handler);
 }
 
--- a/libpurple/protocols/Makefile.am	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/Makefile.am	Mon Nov 09 01:42:24 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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Mon Nov 09 01:42:24 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/JEPS	Sun Nov 08 01:12:44 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-0045: IN PROGRESS
-	Multi-User Chat
-0047: IN PROGRESS
-	In-Band Bytestreams
-0060: NEED
-	Pub-Sub
-0071: AWAITING FINAL SPEC
-	XHTML-IM
-0073: NEED
-	Basic IM Protocol Suite
-0080: NEED (Do we?)
-	Geographic Location Information
-0084: NEED
-	User Avatars in Jabber
-0085: NEED
-	Chat State Notifications
-0089: WATCH
-	Generic Alerts
-0093: NEED
-	Roster Item Exchange
-0100: NEED
-	Gateway Interaction (Transports)
-0115: WATCH
-	Client Capabilities
-0117: NEED
-	Intermediate IM Protocol Suite
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/XEPS	Mon Nov 09 01:42:24 2009 +0000
@@ -0,0 +1,4 @@
+0060: NEED
+	Pub-Sub
+0080: NEED (Do we?)
+	Geographic Location Information
--- a/libpurple/protocols/jabber/auth.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/auth.c	Mon Nov 09 01:42:24 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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Mon Nov 09 01:42:24 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) {
--- a/libpurple/protocols/jabber/caps.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/caps.c	Mon Nov 09 01:42:24 2009 +0000
@@ -975,7 +975,7 @@
 	g_free(js->caps_hash);
 	js->caps_hash = jabber_caps_calculate_hash(&info, "sha1");
 	g_list_free(info.identities);
-	g_list_free(features);
+	g_list_free(info.features);
 }
 
 const gchar* jabber_caps_get_own_hash(JabberStream *js)
--- a/libpurple/protocols/jabber/chat.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/chat.c	Mon Nov 09 01:42:24 2009 +0000
@@ -106,7 +106,7 @@
 	{
 		char *room_jid = g_strdup_printf("%s@%s", room, server);
 
-		chat = g_hash_table_lookup(js->chats, jabber_normalize(NULL, room_jid));
+		chat = g_hash_table_lookup(js->chats, room_jid);
 		g_free(room_jid);
 	}
 
@@ -177,10 +177,21 @@
 		xmlnode_insert_data(body, msg, -1);
 	} else {
 		xmlnode_set_attrib(message, "to", name);
+		/*
+		 * Putting the reason into the body was an 'undocumented protocol,
+		 * ...not part of "groupchat 1.0"'.
+		 * http://xmpp.org/extensions/attic/jep-0045-1.16.html#invite
+		 *
+		 * Left here for compatibility.
+		 */
 		body = xmlnode_new_child(message, "body");
 		xmlnode_insert_data(body, msg, -1);
+
 		x = xmlnode_new_child(message, "x");
 		xmlnode_set_attrib(x, "jid", room_jid);
+
+		/* The better place for it! XEP-0249 style. */
+		xmlnode_set_attrib(x, "reason", msg);
 		xmlnode_set_namespace(x, "jabber:x:conference");
 	}
 
@@ -216,7 +227,8 @@
 	JabberChat *chat;
 	char *jid;
 
-	g_return_val_if_fail(jabber_chat_find(js, room, server) == NULL, NULL);
+	if (jabber_chat_find(js, room, server) != NULL)
+		return NULL;
 
 	chat = g_new0(JabberChat, 1);
 	chat->js = js;
@@ -264,7 +276,8 @@
 	char *jid;
 
 	chat = jabber_chat_new(js, room, server, handle, password, data);
-	g_return_val_if_fail(chat != NULL, NULL);
+	if (chat == NULL)
+		return NULL;
 
 	gc = js->gc;
 	account = purple_connection_get_account(gc);
@@ -371,7 +384,7 @@
 	JabberStream *js = chat->js;
 	char *room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
 
-	g_hash_table_remove(js->chats, jabber_normalize(NULL, room_jid));
+	g_hash_table_remove(js->chats, room_jid);
 	g_free(room_jid);
 }
 
@@ -679,11 +692,11 @@
 }
 
 
-void jabber_chat_change_nick(JabberChat *chat, const char *nick)
+gboolean jabber_chat_change_nick(JabberChat *chat, const char *nick)
 {
 	xmlnode *presence;
 	char *full_jid;
-	PurplePresence *gpresence;
+	PurpleAccount *account;
 	PurpleStatus *status;
 	JabberBuddyState state;
 	char *msg;
@@ -693,11 +706,11 @@
 		purple_conv_chat_write(PURPLE_CONV_CHAT(chat->conv), "",
 				_("Nick changing not supported in non-MUC chatrooms"),
 				PURPLE_MESSAGE_SYSTEM, time(NULL));
-		return;
+		return FALSE;
 	}
 
-	gpresence = purple_account_get_presence(chat->js->gc->account);
-	status = purple_presence_get_active_status(gpresence);
+	account = purple_connection_get_account(chat->js->gc);
+	status = purple_account_get_active_status(account);
 
 	purple_status_to_jabber(status, &state, &msg, &priority);
 
@@ -709,6 +722,8 @@
 
 	jabber_send(chat->js, presence);
 	xmlnode_free(presence);
+
+	return TRUE;
 }
 
 void jabber_chat_part(JabberChat *chat, const char *msg)
--- a/libpurple/protocols/jabber/chat.h	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/chat.h	Mon Nov 09 01:42:24 2009 +0000
@@ -62,6 +62,8 @@
  * in-prpl function for joining a chat room. Doesn't require sticking goop
  * into a hash table.
  *
+ * @param room     The room to join. This MUST be normalized already.
+ * @param server   The server the room is on. This MUST be normalized already.
  * @param password The password (if required) to join the room. May be NULL.
  * @param data     The chat hash table.  May be NULL (it will be generated
  *                 for current core<>prpl API interface.)
@@ -87,7 +89,7 @@
 void jabber_chat_register(JabberChat *chat);
 void jabber_chat_change_topic(JabberChat *chat, const char *topic);
 void jabber_chat_set_topic(PurpleConnection *gc, int id, const char *topic);
-void jabber_chat_change_nick(JabberChat *chat, const char *nick);
+gboolean jabber_chat_change_nick(JabberChat *chat, const char *nick);
 void jabber_chat_part(JabberChat *chat, const char *msg);
 void jabber_chat_track_handle(JabberChat *chat, const char *handle,
 		const char *jid, const char *affiliation, const char *role);
--- a/libpurple/protocols/jabber/disco.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/disco.c	Mon Nov 09 01:42:24 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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/google.c	Mon Nov 09 01:42:24 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");
@@ -994,8 +994,9 @@
 
 	const char *grt = xmlnode_get_attrib_with_namespace(item, "t", "google:roster");
 	const char *subscription = xmlnode_get_attrib(item, "subscription");
+	const char *ask = xmlnode_get_attrib(item, "ask");
 
-	if (!subscription || !strcmp(subscription, "none")) {
+	if ((!subscription || !strcmp(subscription, "none")) && !ask) {
 		/* The Google Talk servers will automatically add people from your Gmail address book
 		 * with subscription=none. If we see someone with subscription=none, ignore them.
 		 */
@@ -1093,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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/iq.c	Mon Nov 09 01:42:24 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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Mon Nov 09 01:42:24 2009 +0000
@@ -68,10 +68,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 +199,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 +254,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 +293,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 +375,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 +402,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 +418,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 +488,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 +529,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 +569,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 +578,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);
@@ -1825,7 +1828,7 @@
 		JabberFeature *feature = jabber_features->data;
 		g_free(feature->namespace);
 		g_free(feature);
-		jabber_features = g_list_remove_link(jabber_features, jabber_features);
+		jabber_features = g_list_delete_link(jabber_features, jabber_features);
 	}
 }
 
@@ -1862,7 +1865,7 @@
 		g_free(id->lang);
 		g_free(id->name);
 		g_free(id);
-		jabber_identities = g_list_remove_link(jabber_identities, jabber_identities);
+		jabber_identities = g_list_delete_link(jabber_identities, jabber_identities);
 	}
 }
 
@@ -2606,8 +2609,15 @@
 	if(!chat || !args || !args[0])
 		return PURPLE_CMD_RET_FAILED;
 
-	jabber_chat_change_nick(chat, args[0]);
-	return PURPLE_CMD_RET_OK;
+	if (!jabber_resourceprep_validate(args[0])) {
+		*error = g_strdup(_("Invalid nickname"));
+		return PURPLE_CMD_RET_FAILED;
+	}
+
+	if (jabber_chat_change_nick(chat, args[0]))
+		return PURPLE_CMD_RET_OK;
+	else
+		return PURPLE_CMD_RET_FAILED;
 }
 
 static PurpleCmdRet jabber_cmd_chat_part(PurpleConversation *conv,
@@ -3239,7 +3249,7 @@
 	id = purple_cmd_register("part", "s", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
 	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
-	                  jabber_cmd_chat_part, _("part [room]:  Leave the room."),
+	                  jabber_cmd_chat_part, _("part [message]:  Leave the room."),
 	                  NULL);
 	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
 
@@ -3394,8 +3404,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 ||
@@ -3485,9 +3493,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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Mon Nov 09 01:42:24 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;
@@ -382,6 +373,6 @@
 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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.c	Mon Nov 09 01:42:24 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/libxmpp.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Mon Nov 09 01:42:24 2009 +0000
@@ -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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/message.c	Mon Nov 09 01:42:24 2009 +0000
@@ -545,7 +545,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;
@@ -758,9 +758,22 @@
 					jm->type != JABBER_MESSAGE_ERROR) {
 				const char *jid = xmlnode_get_attrib(child, "jid");
 				if(jid) {
+					const char *reason = xmlnode_get_attrib(child, "reason");
+					const char *password = xmlnode_get_attrib(child, "password");
+
 					jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE;
 					g_free(jm->to);
 					jm->to = g_strdup(jid);
+
+					if (reason) {
+						g_free(jm->body);
+						jm->body = g_strdup(reason);
+					}
+
+					if (password) {
+						g_free(jm->password);
+						jm->password = g_strdup(password);
+					}
 				}
 			} else if(!strcmp(xmlns, "http://jabber.org/protocol/muc#user") &&
 					jm->type != JABBER_MESSAGE_ERROR) {
@@ -775,8 +788,10 @@
 						g_free(jm->body);
 						jm->body = xmlnode_get_data(reason);
 					}
-					if((password = xmlnode_get_child(child, "password")))
+					if((password = xmlnode_get_child(child, "password"))) {
+						g_free(jm->password);
 						jm->password = xmlnode_get_data(password);
+					}
 
 					jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE;
 				}
@@ -797,7 +812,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:
@@ -1088,7 +1103,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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/oob.c	Mon Nov 09 01:42:24 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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Mon Nov 09 01:42:24 2009 +0000
@@ -476,7 +476,7 @@
 	/*
 	 * Versions of libpurple before 2.6.0 didn't advertise this capability, so
 	 * we can't yet use Entity Capabilities to determine whether or not the
-	 * other client supports Entity Capabilities.
+	 * other client supports Chat States.
 	 */
 	if (jabber_resource_has_capability(jbr, "http://jabber.org/protocol/chatstates"))
 		jbr->chat_states = JABBER_CHAT_STATES_SUPPORTED;
@@ -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/roster.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/roster.c	Mon Nov 09 01:42:24 2009 +0000
@@ -72,7 +72,7 @@
 		const char *alias, GSList *groups)
 {
 	GSList *buddies, *l;
-	GSList *pool = NULL;
+	PurpleAccount *account = purple_connection_get_account(js->gc);
 
 	buddies = purple_find_buddies(js->gc->account, jid);
 
@@ -117,25 +117,14 @@
 			groups = g_slist_delete_link(groups, l);
 		} else {
 			/* This buddy isn't in the group on the server anymore */
-			pool = g_slist_prepend(pool, b);
+			purple_debug_info("jabber", "jabber_roster_parse(): Removing %s "
+			                  "from group '%s' on the local list\n",
+			                  purple_buddy_get_name(b),
+			                  purple_group_get_name(g));
+			purple_blist_remove_buddy(b);
 		}
 	}
 
-	if (pool) {
-		GString *tmp = g_string_new(NULL);
-		GSList *list = pool;
-		for ( ; list; list = list->next) {
-			tmp = g_string_append(tmp,
-					purple_group_get_name(purple_buddy_get_group(list->data)));
-			if (list->next)
-				tmp = g_string_append(tmp, ", ");
-		}
-
-		purple_debug_info("jabber", "jabber_roster_parse(): Removing %s from "
-		                  "groups: %s\n", jid, tmp->str);
-		g_string_free(tmp, TRUE);
-	}
-
 	if (groups) {
 		char *tmp = roster_groups_join(groups);
 		purple_debug_info("jabber", "jabber_roster_parse(): Adding %s to "
@@ -145,17 +134,7 @@
 
 	while(groups) {
 		PurpleGroup *g = purple_find_group(groups->data);
-		PurpleBuddy *b = NULL;
-
-		/* If there are buddies we would otherwise delete, move them to
-		 * the new group (instead of deleting them below)
-		 */
-		if (pool) {
-			b = pool->data;
-			pool = g_slist_delete_link(pool, pool);
-		} else {
-			b = purple_buddy_new(js->gc->account, jid, alias);
-		}
+		PurpleBuddy *b = purple_buddy_new(account, jid, alias);
 
 		if(!g) {
 			g = purple_group_new(groups->data);
@@ -169,14 +148,6 @@
 		groups = g_slist_delete_link(groups, groups);
 	}
 
-	/* Remove this person from all the groups they're no longer in on the
-	 * server */
-	while (pool) {
-		PurpleBuddy *b = pool->data;
-		purple_blist_remove_buddy(b);
-		pool = g_slist_delete_link(pool, pool);
-	}
-
 	g_slist_free(buddies);
 }
 
--- a/libpurple/protocols/jabber/si.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/si.c	Mon Nov 09 01:42:24 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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/jabber/useravatar.c	Mon Nov 09 01:42:24 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/contact.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/msn/contact.c	Mon Nov 09 01:42:24 2009 +0000
@@ -362,7 +362,7 @@
 	char *display_text;
 
 	passport = xmlnode_get_data(xmlnode_get_child(member, node));
-	if (!purple_email_is_valid(passport)) {
+	if (!msn_email_is_valid(passport)) {
 		g_free(passport);
 		return;
 	}
@@ -765,7 +765,7 @@
 		if (passport == NULL)
 			continue;
 
-		if (!purple_email_is_valid(passport))
+		if (!msn_email_is_valid(passport))
 			continue;
 
 		if ((displayName = xmlnode_get_child(contactInfo, "displayName")))
@@ -1232,8 +1232,13 @@
 	if (user->invite_message) {
 		char *tmp;
 		body = g_markup_escape_text(user->invite_message, -1);
-		tmp = g_markup_escape_text(purple_connection_get_display_name(session->account->gc), -1);
+
+		/* Ignore the cast, we treat it as const anyway. */
+		tmp = (char *)purple_connection_get_display_name(session->account->gc);
+		tmp = tmp ? g_markup_escape_text(tmp, -1) : g_strdup("");
+
 		invite = g_strdup_printf(MSN_CONTACT_INVITE_MESSAGE_XML, body, tmp);
+
 		g_free(body);
 		g_free(tmp);
 
--- a/libpurple/protocols/msn/msn.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/msn/msn.c	Mon Nov 09 01:42:24 2009 +0000
@@ -118,6 +118,29 @@
 	return buf;
 }
 
+gboolean
+msn_email_is_valid(const char *passport)
+{
+	if (purple_email_is_valid(passport)) {
+		/* Special characters aren't allowed in domains, so only go to '@' */
+		while (*passport != '@') {
+			if (*passport == '/')
+				return FALSE;
+			else if (*passport == '?')
+				return FALSE;
+			else if (*passport == '=')
+				return FALSE;
+			/* MSN also doesn't like colons, but that's checked already */
+
+			passport++;
+		}
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
 static gboolean
 msn_send_attention(PurpleConnection *gc, const char *username, guint type)
 {
@@ -611,9 +634,14 @@
 		MsnSession *session = gc->proto_data;
 		if (session) {
 			MsnUser *user = msn_userlist_find_user(session->userlist, who);
-			if (user)
+			if (user) {
 				/* Include these too: MSN_CLIENT_CAP_MSNMOBILE|MSN_CLIENT_CAP_MSNDIRECT ? */
-				ret = (user->clientid & MSN_CLIENT_CAP_WEBMSGR) == 0;
+				if ((user->clientid & MSN_CLIENT_CAP_WEBMSGR) ||
+						user->networkid == MSN_NETWORK_YAHOO)
+					ret = FALSE;
+				else
+					ret = TRUE;
+			}
 		} else
 			ret = FALSE;
 	}
@@ -1511,7 +1539,7 @@
 
 	bname = purple_buddy_get_name(buddy);
 
-	if (!purple_email_is_valid(bname)) {
+	if (!msn_email_is_valid(bname)) {
 		gchar *buf;
 		buf = g_strdup_printf(_("Unable to add the buddy %s because the username is invalid.  Usernames must be valid email addresses."), bname);
 		if (!purple_conv_present_error(bname, purple_connection_get_account(gc), buf))
--- a/libpurple/protocols/msn/msn.h	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/msn/msn.h	Mon Nov 09 01:42:24 2009 +0000
@@ -133,6 +133,7 @@
 	((MSN_CLIENT_ID_VERSION    << 24) | \
 	 (MSN_CLIENT_ID_CAPABILITIES))
 
+gboolean msn_email_is_valid(const char *passport);
 void msn_act_id(PurpleConnection *gc, const char *entry);
 void msn_send_privacy(PurpleConnection *gc);
 void msn_send_im_message(MsnSession *session, MsnMessage *msg);
--- a/libpurple/protocols/msn/nexus.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/msn/nexus.c	Mon Nov 09 01:42:24 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);
--- a/libpurple/protocols/msn/notification.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/msn/notification.c	Mon Nov 09 01:42:24 2009 +0000
@@ -670,7 +670,7 @@
 			                     "User %s is on both Allow and Block list; "
 			                     "removing from Allow list.\n",
 			                     user->passport);
-			msn_userlist_rem_buddy_from_list(session->userlist, user->passport, MSN_LIST_AL);
+			msn_user_unset_op(user, MSN_LIST_AL_OP);
 		}
 
 		if (user->networkid != MSN_NETWORK_UNKNOWN) {
@@ -840,17 +840,48 @@
 	MsnSession *session;
 	PurpleAccount *account;
 	PurpleConnection *gc;
-	char *adl = g_strndup(payload, len);
-	char *reason = g_strdup_printf(_("Unknown error (%d): %s"),
-		GPOINTER_TO_INT(cmd->payload_cbdata), adl);
-	g_free(adl);
+	int error = GPOINTER_TO_INT(cmd->payload_cbdata);
 
 	session = cmdproc->session;
 	account = session->account;
 	gc = purple_account_get_connection(account);
 
-	purple_notify_error(gc, NULL, _("Unable to add user"), reason);
-	g_free(reason);
+	if (error == 241) {
+		/* khc: some googling suggests that error 241 means the buddy is somehow
+		   in the local list, but not the server list, and that we should add
+		   those buddies to the addressbook. For now I will just notify the user
+		   about the raw payload, because I am lazy */
+		xmlnode *adl = xmlnode_from_str(payload, len);
+		GString *emails = g_string_new(NULL);
+
+		xmlnode *domain = xmlnode_get_child(adl, "d");
+		while (domain) {
+			const char *domain_str = xmlnode_get_attrib(domain, "n");
+			xmlnode *contact = xmlnode_get_child(domain, "c");
+			while (contact) {
+				g_string_append_printf(emails, "%s@%s\n",
+					xmlnode_get_attrib(contact, "n"), domain_str);
+				contact = xmlnode_get_next_twin(contact);
+			}
+			domain = xmlnode_get_next_twin(domain);
+		}
+
+		purple_notify_error(gc, NULL,
+			_("The following users are missing from your addressbook"),
+			emails->str);
+		g_string_free(emails, TRUE);
+		xmlnode_free(adl);
+	}
+	else
+	{
+		char *adl = g_strndup(payload, len);
+		char *reason = g_strdup_printf(_("Unknown error (%d): %s"),
+			error, adl);
+		g_free(adl);
+
+		purple_notify_error(gc, NULL, _("Unable to add user"), reason);
+		g_free(reason);
+	}
 }
 
 static void
@@ -878,50 +909,49 @@
 }
 
 static void
-adl_241_error_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload,
-	size_t len)
+rml_error_parse(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len)
 {
-	/* khc: some googling suggests that error 241 means the buddy is somehow
-	   in the local list, but not the server list, and that we should add
-	   those buddies to the addressbook. For now I will just notify the user
-	   about the raw payload, because I am lazy */
 	MsnSession *session;
 	PurpleAccount *account;
 	PurpleConnection *gc;
-	xmlnode *adl;
-	xmlnode *domain;
-	GString *emails;
+	char *adl, *reason;
+	int error = GPOINTER_TO_INT(cmd->payload_cbdata);
 
 	session = cmdproc->session;
 	account = session->account;
 	gc = purple_account_get_connection(account);
 
-	adl = xmlnode_from_str(payload, len);
-	emails = g_string_new(NULL);
+	adl = g_strndup(payload, len);
+	reason = g_strdup_printf(_("Unknown error (%d): %s"),
+		error, adl);
+	g_free(adl);
 
-	domain = xmlnode_get_child(adl, "d");
-	while (domain) {
-		const char *domain_str = xmlnode_get_attrib(domain, "n");
-		xmlnode *contact = xmlnode_get_child(domain, "c");
-		while (contact) {
-			g_string_append_printf(emails, "%s@%s\n",
-				xmlnode_get_attrib(contact, "n"), domain_str);
-			contact = xmlnode_get_next_twin(contact);
-		}
-		domain = xmlnode_get_next_twin(domain);
-	}
-
-	purple_notify_error(gc, NULL,
-		_("The following users are missing from your addressbook"), emails->str);
-	g_string_free(emails, TRUE);
-	xmlnode_free(adl);
+	purple_notify_error(gc, NULL, _("Unable to remove user"), reason);
+	g_free(reason);
 }
 
 static void
-adl_241_error_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+rml_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error)
 {
-	cmdproc->last_cmd->payload_cb = adl_241_error_cmd_post;
-	cmd->payload_len = atoi(cmd->params[1]);
+	MsnSession *session;
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	MsnCommand *cmd = cmdproc->last_cmd;
+
+	session = cmdproc->session;
+	account = session->account;
+	gc = purple_account_get_connection(account);
+
+	purple_debug_error("msn", "RML error\n");
+	if (cmd->param_count > 1) {
+		cmd->payload_cb = rml_error_parse;
+		cmd->payload_len = atoi(cmd->params[1]);
+		cmd->payload_cbdata = GINT_TO_POINTER(error);
+	} else {
+		char *reason = g_strdup_printf(_("Unknown error (%d)"), error);
+		purple_notify_error(gc, NULL, _("Unable to remove user"), reason);
+		g_free(reason);
+	}
 }
 
 static void
@@ -1068,7 +1098,17 @@
 		/* Where'd this come from? */
 		return;
 
-	if (cmd->param_count == 7) {
+	if (cmd->param_count == 8) {
+		/* Yahoo! Buddy, looks like */
+		networkid = atoi(cmd->params[3]);
+		friendly = g_strdup(purple_url_decode(cmd->params[4]));
+		clientid = strtoul(cmd->params[5], NULL, 10);
+
+		/* cmd->params[7] seems to be a URL to a Yahoo! icon:
+				https://sec.yimg.com/i/us/nt/b/purpley.1.0.png
+		   ... and it's purple, HAH!
+		*/
+	} else if (cmd->param_count == 7) {
 		/* MSNP14+ with Display Picture object */
 		networkid = atoi(cmd->params[3]);
 		friendly = g_strdup(purple_url_decode(cmd->params[4]));
@@ -2095,9 +2135,8 @@
 
 	msn_table_add_cmd(cbs_table, "fallback", "XFR", xfr_cmd);
 
-	msn_table_add_cmd(cbs_table, NULL, "241", adl_241_error_cmd);
-
 	msn_table_add_error(cbs_table, "ADL", adl_error);
+	msn_table_add_error(cbs_table, "RML", rml_error);
 	msn_table_add_error(cbs_table, "FQY", fqy_error);
 	msn_table_add_error(cbs_table, "USR", usr_error);
 
--- a/libpurple/protocols/msn/oim.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/msn/oim.c	Mon Nov 09 01:42:24 2009 +0000
@@ -153,7 +153,7 @@
 	gpointer cb_data;
 } MsnOimRequestData;
 
-static void msn_oim_request_helper(MsnOimRequestData *data);
+static gboolean msn_oim_request_helper(MsnOimRequestData *data);
 
 static void
 msn_oim_request_cb(MsnSoapMessage *request, MsnSoapMessage *response,
@@ -202,7 +202,7 @@
 	g_free(data);
 }
 
-static void
+static gboolean
 msn_oim_request_helper(MsnOimRequestData *data)
 {
 	MsnSession *session = data->oim->session;
@@ -224,13 +224,13 @@
 		const char *msn_p;
 
 		token = msn_nexus_get_token(session->nexus, MSN_AUTH_MESSENGER_WEB);
-		g_return_if_fail(token != NULL);
+		g_return_val_if_fail(token != NULL, FALSE);
 
 		msn_t = g_hash_table_lookup(token, "t");
 		msn_p = g_hash_table_lookup(token, "p");
 
-		g_return_if_fail(msn_t != NULL);
-		g_return_if_fail(msn_p != NULL);
+		g_return_val_if_fail(msn_t != NULL, FALSE);
+		g_return_val_if_fail(msn_p != NULL, FALSE);
 
 		passport = xmlnode_get_child(data->body, "Header/PassportCookie");
 		xml_t = xmlnode_get_child(passport, "t");
@@ -248,6 +248,8 @@
 		msn_soap_message_new(data->action, xmlnode_copy(data->body)),
 		data->host, data->url, FALSE,
 		msn_oim_request_cb, data);
+
+	return FALSE;
 }
 
 
--- a/libpurple/protocols/msn/userlist.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/msn/userlist.c	Mon Nov 09 01:42:24 2009 +0000
@@ -539,7 +539,7 @@
 
 	purple_debug_info("msn", "Add user: %s to group: %s\n", who, new_group_name);
 
-	if (!purple_email_is_valid(who))
+	if (!msn_email_is_valid(who))
 	{
 		/* only notify the user about problems adding to the friends list
 		 * maybe we should do something else for other lists, but it probably
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/Makefile.am	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/oscar/clientlogin.c	Mon Nov 09 01:42:24 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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/oscar/family_feedbag.c	Mon Nov 09 01:42:24 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/family_icbm.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/oscar/family_icbm.c	Mon Nov 09 01:42:24 2009 +0000
@@ -151,6 +151,55 @@
 	return AIM_CLIENTTYPE_UNKNOWN;
 }
 
+/*
+ * Subtype 0x0001 - Error
+ */
+static int
+error(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs)
+{
+	int ret = 0;
+	aim_rxcallback_t userfunc;
+	aim_snac_t *snac2;
+	guint16 reason, errcode = 0;
+	char *bn;
+	GSList *tlvlist;
+
+	if (!(snac2 = aim_remsnac(od, snac->id))) {
+		purple_debug_misc("oscar", "icbm error: received response from unknown request!\n");
+		return 0;
+	}
+
+	if (snac2->family != SNAC_FAMILY_ICBM) {
+		purple_debug_misc("oscar", "icbm error: received response from invalid request! %d\n", snac2->family);
+		g_free(snac2->data);
+		g_free(snac2);
+		return 0;
+	}
+
+	if (!(bn = snac2->data)) {
+		purple_debug_misc("oscar", "icbm error: received response from request without a buddy name!\n");
+		g_free(snac2);
+		return 0;
+	}
+
+	reason = byte_stream_get16(bs);
+
+	tlvlist = aim_tlvlist_read(bs);
+	if (aim_tlv_gettlv(tlvlist, 0x0008, 1))
+		errcode = aim_tlv_get16(tlvlist, 0x0008, 1);
+	aim_tlvlist_free(tlvlist);
+
+	/* Notify the user that the message wasn't delivered */
+	if ((userfunc = aim_callhandler(od, snac->family, snac->subtype)))
+		ret = userfunc(od, conn, frame, reason, errcode, bn);
+
+	if (snac2)
+		g_free(snac2->data);
+	g_free(snac2);
+
+	return ret;
+}
+
 /**
  * Subtype 0x0002 - Set ICBM parameters.
  *
@@ -2789,7 +2838,9 @@
 static int
 snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs)
 {
-	if (snac->subtype == 0x0005)
+	if (snac->subtype == 0x0001)
+		return error(od, conn, mod, frame, snac, bs);
+	else if (snac->subtype == 0x0005)
 		return aim_im_paraminfo(od, conn, mod, frame, snac, bs);
 	else if (snac->subtype == 0x0006)
 		return outgoingim(od, conn, mod, frame, snac, bs);
--- a/libpurple/protocols/oscar/family_oservice.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/oscar/family_oservice.c	Mon Nov 09 01:42:24 2009 +0000
@@ -319,7 +319,10 @@
 	for (i = 0; i < numclasses; i++)
 	{
 		struct rateclass *rateclass;
+		guint32 delta;
+		struct timeval now;
 
+		gettimeofday(&now, NULL);
 		rateclass = g_new0(struct rateclass, 1);
 
 		rateclass->classid = byte_stream_get16(bs);
@@ -339,11 +342,24 @@
 		 * the new version hardcoded here.
 		 */
 		if (mod->version >= 3)
-			byte_stream_getrawbuf(bs, rateclass->unknown, sizeof(rateclass->unknown));
+		{
+			rateclass->delta = byte_stream_get32(bs);
+			rateclass->dropping_snacs = byte_stream_get8(bs);
+
+			delta = rateclass->delta;
+
+			rateclass->last.tv_sec = now.tv_sec - delta / 1000;
+			delta %= 1000;
+			rateclass->last.tv_usec = now.tv_usec - delta * 1000;
+		}
+		else
+		{
+			rateclass->delta = rateclass->dropping_snacs = 0;
+			rateclass->last.tv_sec = now.tv_sec;
+			rateclass->last.tv_usec = now.tv_usec;
+		}
 
 		rateclass->members = g_hash_table_new(g_direct_hash, g_direct_equal);
-		rateclass->last.tv_sec = 0;
-		rateclass->last.tv_usec = 0;
 		conn->rateclasses = g_slist_prepend(conn->rateclasses, rateclass);
 	}
 	conn->rateclasses = g_slist_reverse(conn->rateclasses);
@@ -383,8 +399,7 @@
 	 */
 
 	/*
-	 * Last step in the conn init procedure is to acknowledge that we
-	 * agree to these draconian limitations.
+	 * Subscribe to rate change information for all rate classes.
 	 */
 	aim_srv_rates_addparam(od, conn);
 
@@ -451,7 +466,10 @@
 	aim_rxcallback_t userfunc;
 	guint16 code, classid;
 	struct rateclass *rateclass;
+	guint32 delta;
+	struct timeval now;
 
+	gettimeofday(&now, NULL);
 	code = byte_stream_get16(bs);
 	classid = byte_stream_get16(bs);
 
@@ -468,8 +486,29 @@
 	rateclass->current = byte_stream_get32(bs);
 	rateclass->max = byte_stream_get32(bs);
 
-	if ((userfunc = aim_callhandler(od, snac->family, snac->subtype)))
-		ret = userfunc(od, conn, frame, code, classid, rateclass->windowsize, rateclass->clear, rateclass->alert, rateclass->limit, rateclass->disconnect, rateclass->current, rateclass->max);
+	if (mod->version >= 3)
+	{
+		rateclass->delta = byte_stream_get32(bs);
+		rateclass->dropping_snacs = byte_stream_get8(bs);
+
+		delta = rateclass->delta;
+
+		rateclass->last.tv_sec = now.tv_sec - delta / 1000;
+		delta %= 1000;
+		rateclass->last.tv_usec = now.tv_usec - delta * 1000;
+	}
+	else
+	{
+		rateclass->delta = rateclass->dropping_snacs = 0;
+		rateclass->last.tv_sec = now.tv_sec;
+		rateclass->last.tv_usec = now.tv_usec;
+	}
+
+	if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) {
+		/* Can't pass in guint8 via ... varargs, so we use an unsigned int */
+		unsigned int dropping_snacs = rateclass->dropping_snacs;
+		ret = userfunc(od, conn, frame, code, classid, rateclass->windowsize, rateclass->clear, rateclass->alert, rateclass->limit, rateclass->disconnect, rateclass->current, rateclass->max, rateclass->delta, dropping_snacs);
+	}
 
 	return ret;
 }
--- a/libpurple/protocols/oscar/flap_connection.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Mon Nov 09 01:42:24 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);
 
@@ -131,11 +131,13 @@
 rateclass_get_new_current(FlapConnection *conn, struct rateclass *rateclass, struct timeval *now)
 {
 	unsigned long timediff; /* In milliseconds */
+	guint32 current;
 
 	timediff = (now->tv_sec - rateclass->last.tv_sec) * 1000 + (now->tv_usec - rateclass->last.tv_usec) / 1000;
+	current = ((rateclass->current * (rateclass->windowsize - 1)) + timediff) / rateclass->windowsize;
 
-	/* This formula is taken from the joscar API docs. Preesh. */
-	return MIN(((rateclass->current * (rateclass->windowsize - 1)) + timediff) / rateclass->windowsize, rateclass->max);
+	/* This formula is taken from http://dev.aol.com/aim/oscar/#RATELIMIT */
+	return MIN(current, rateclass->max);
 }
 
 /*
@@ -161,8 +163,7 @@
 
 			new_current = rateclass_get_new_current(conn, rateclass, &now);
 
-			/* (Add 100ms padding to account for inaccuracies in the calculation) */
-			if (new_current < rateclass->alert + 100)
+			if (rateclass->dropping_snacs || new_current <= rateclass->alert)
 				/* Not ready to send this SNAC yet--keep waiting. */
 				return FALSE;
 
@@ -245,10 +246,9 @@
 		gettimeofday(&now, NULL);
 		new_current = rateclass_get_new_current(conn, rateclass, &now);
 
-		/* (Add 100ms padding to account for inaccuracies in the calculation) */
-		if (new_current < rateclass->alert + 100)
+		if (rateclass->dropping_snacs || new_current <= rateclass->alert)
 		{
-			purple_debug_info("oscar", "Current rate for conn %p would be %u, but we alert at %u; enqueueing\n", conn, new_current, (rateclass->alert + 100));
+			purple_debug_info("oscar", "Current rate for conn %p would be %u, but we alert at %u; enqueueing\n", conn, new_current, rateclass->alert);
 
 			enqueue = TRUE;
 		}
--- a/libpurple/protocols/oscar/libaim.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/oscar/libaim.c	Mon Nov 09 01:42:24 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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/oscar/libicq.c	Mon Nov 09 01:42:24 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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Mon Nov 09 01:42:24 2009 +0000
@@ -144,6 +144,26 @@
 };
 static const int msgerrreasonlen = G_N_ELEMENTS(msgerrreason);
 
+static const char * const errcodereason[] = {
+	N_("Invalid error"),
+	N_("Not logged in"),
+	N_("Cannot receive IM due to parental controls"),
+	N_("Cannot send SMS without accepting terms"),
+	N_("Cannot send SMS"), /* SMS_WITHOUT_DISCLAIMER is weird */
+	N_("Cannot send SMS to this country"),
+	N_("Unknown error"), /* Undocumented */
+	N_("Unknown error"), /* Undocumented */
+	N_("Cannot send SMS to unknown country"),
+	N_("Bot accounts cannot initiate IMs"),
+	N_("Bot account cannot IM this user"),
+	N_("Bot account reached IM limit"),
+	N_("Bot account reached daily IM limit"),
+	N_("Bot account reached monthly IM limit"),
+	N_("Unable to receive offline messages"),
+	N_("Offline message store full")
+};
+static const int errcodereasonlen = G_N_ELEMENTS(errcodereason);
+
 /* All the libfaim->purple callback functions */
 
 /* Only used when connecting with the old-style BUCP login */
@@ -1168,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);
@@ -1394,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);
 }
 
 /**
@@ -1806,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;
@@ -1877,7 +1916,7 @@
 			break;
 		case 0x18:
 			/* username connecting too frequently */
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer."));
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("Your username has been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer."));
 			break;
 		case 0x1c:
 		{
@@ -1889,7 +1928,7 @@
 		}
 		case 0x1d:
 			/* IP address connecting too frequently */
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too frequently. Wait a minute and try again. If you continue to try, you will need to wait even longer."));
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("Your IP address has been connecting and disconnecting too frequently. Wait a minute and try again. If you continue to try, you will need to wait even longer."));
 			break;
 		default:
 			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Unknown reason"));
@@ -2874,25 +2913,46 @@
 			gchar **text;
 			text = g_strsplit(args->msg, "\376", 0);
 			if (text) {
-				num = 0;
-				for (i=0; i<strlen(text[0]); i++)
-					num = num*10 + text[0][i]-48;
-				for (i=0; i<num; i++) {
-					struct name_data *data = g_new(struct name_data, 1);
-					gchar *message = g_strdup_printf(_("ICQ user %u has sent you a buddy: %s (%s)"), args->uin, text[i*2+2], text[i*2+1]);
-					data->gc = gc;
-					data->name = g_strdup(text[i*2+1]);
-					data->nick = g_strdup(text[i*2+2]);
-
-					purple_request_action(gc, NULL, message,
-										_("Do you want to add this buddy "
-										  "to your buddy list?"),
-										PURPLE_DEFAULT_ACTION_NONE,
-										purple_connection_get_account(gc), data->name, NULL,
-										data, 2,
-										_("_Add"), G_CALLBACK(purple_icq_buddyadd),
-										_("_Decline"), G_CALLBACK(oscar_free_name_data));
-					g_free(message);
+				/* Read the number of contacts that we were sent */
+				errno = 0;
+				num = text[0] ? strtoul(text[0], NULL, 10) : 0;
+
+				if (num > 0 && errno == 0) {
+					for (i=0; i<num; i++) {
+						struct name_data *data;
+						gchar *message;
+
+						if (!text[i*2 + 1] || !text[i*2 + 2]) {
+							/* We're missing the contact name or nickname.  Bail out. */
+							gchar *tmp = g_strescape(args->msg, NULL);
+							purple_debug_error("oscar", "Unknown syntax parsing "
+									"ICQ buddies.  args->msg=%s\n", tmp);
+							g_free(tmp);
+							break;
+						}
+
+						message = g_strdup_printf(_("ICQ user %u has sent you a buddy: %s (%s)"), args->uin, text[i*2+2], text[i*2+1]);
+
+						data = g_new(struct name_data, 1);
+						data->gc = gc;
+						data->name = g_strdup(text[i*2+1]);
+						data->nick = g_strdup(text[i*2+2]);
+
+						purple_request_action(gc, NULL, message,
+								_("Do you want to add this buddy "
+								  "to your buddy list?"),
+								PURPLE_DEFAULT_ACTION_NONE,
+								purple_connection_get_account(gc), data->name, NULL,
+								data, 2,
+								_("_Add"), G_CALLBACK(purple_icq_buddyadd),
+								_("_Decline"), G_CALLBACK(oscar_free_name_data));
+						g_free(message);
+					}
+				} else {
+					gchar *tmp = g_strescape(args->msg, NULL);
+					purple_debug_error("oscar", "Unknown syntax parsing "
+							"ICQ buddies.  args->msg=%s\n", tmp);
+					g_free(tmp);
 				}
 				g_strfreev(text);
 			}
@@ -3196,17 +3256,18 @@
 	PurpleXfer *xfer;
 #endif
 	va_list ap;
-	guint16 reason;
-	char *data, *buf;
+	guint16 reason, errcode;
+	char *data, *reason_str, *buf;
 
 	va_start(ap, fr);
 	reason = (guint16)va_arg(ap, unsigned int);
+	errcode = (guint16)va_arg(ap, unsigned int);
 	data = va_arg(ap, char *);
 	va_end(ap);
 
 	purple_debug_error("oscar",
-			   "Message error with data %s and reason %hu\n",
-				(data != NULL ? data : ""), reason);
+			   "Message error with data %s and reason %hu and errcode %hu\n",
+				(data != NULL ? data : ""), reason, errcode);
 
 	if ((data == NULL) || (*data == '\0'))
 		/* We can't do anything if data is empty */
@@ -3221,14 +3282,27 @@
 #endif
 
 	/* Data is assumed to be the destination bn */
-	buf = g_strdup_printf(_("Unable to send message: %s"), (reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason."));
+
+	reason_str = g_strdup((reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason"));
+	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,
-				  (reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason."));
+		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(reason_str);
 
 	return 1;
 }
@@ -3719,7 +3793,8 @@
 	};
 	va_list ap;
 	guint16 code, rateclass;
-	guint32 windowsize, clear, alert, limit, disconnect, currentavg, maxavg;
+	guint32 windowsize, clear, alert, limit, disconnect, currentavg, maxavg, delta;
+	guint8 dropping_snacs;
 
 	va_start(ap, fr);
 	code = (guint16)va_arg(ap, unsigned int);
@@ -3731,23 +3806,28 @@
 	disconnect = va_arg(ap, guint32);
 	currentavg = va_arg(ap, guint32);
 	maxavg = va_arg(ap, guint32);
+	delta = va_arg(ap, guint32);
+	dropping_snacs = (guint8)va_arg(ap, unsigned int);
 	va_end(ap);
 
 	purple_debug_misc("oscar",
 			   "rate %s (param ID 0x%04hx): curavg = %u, maxavg = %u, alert at %u, "
-		     "clear warning at %u, limit at %u, disconnect at %u (window size = %u)\n",
+		     "clear warning at %u, limit at %u, disconnect at %u, delta is %u, dropping is %u (window size = %u)\n",
 		     (code < 5) ? codes[code] : codes[0],
 		     rateclass,
 		     currentavg, maxavg,
 		     alert, clear,
 		     limit, disconnect,
-		     windowsize);
+		     delta,
+		     dropping_snacs,
+		     windowsize
+		     );
 
 	if (code == AIM_RATE_CODE_LIMIT)
 	{
 		purple_debug_warning("oscar",  _("The last action you attempted could not be "
 				"performed because you are over the rate limit. "
-				"Please wait 10 seconds and try again."));
+				"Please wait 10 seconds and try again.\n"));
 	}
 
 	return 1;
@@ -3909,12 +3989,8 @@
 	od->rights.maxpermits = (guint)maxpermits;
 	od->rights.maxdenies = (guint)maxdenies;
 
-	purple_connection_set_state(gc, PURPLE_CONNECTED);
-
 	purple_debug_info("oscar", "buddy list loaded\n");
 
-	aim_srv_clientready(od, conn);
-
 	if (purple_account_get_user_info(account) != NULL)
 		serv_set_info(gc, purple_account_get_user_info(account));
 
@@ -3941,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);
@@ -3957,6 +4030,26 @@
 	aim_srv_requestnew(od, SNAC_FAMILY_ALERT);
 	aim_srv_requestnew(od, SNAC_FAMILY_CHATNAV);
 
+	od->bos.have_rights = TRUE;
+
+	/*
+	 * If we've already received our feedbag data then we're not waiting on
+	 * anything else, so send the server clientready.
+	 *
+	 * Normally we get bos rights before we get our feedbag data, so this
+	 * rarely (never?) happens.  And I'm not sure it actually matters if we
+	 * wait for bos rights before calling clientready.  But it seems safer
+	 * to do it this way.
+	 */
+	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);
+	}
+
 	return 1;
 }
 
@@ -5151,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;
 
@@ -5231,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);
 		}
 
 
@@ -5241,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 0x0000: { /* Buddy */
+			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);
@@ -5303,33 +5379,18 @@
 								purple_buddy_get_name(b),
 								OSCAR_STATUS_ID_MOBILE, NULL);
 					}
-
-					g_free(gname_utf8);
-					g_free(alias_utf8);
 				}
 			} break;
 
-			case 0x0001: { /* 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);
+			case AIM_SSI_TYPE_GROUP: { /* Group */
+				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 0x0002: { /* Permit buddy */
+			case AIM_SSI_TYPE_PERMIT: { /* Permit buddy */
 				if (curitem->name) {
 					/* if (!find_permdeny_by_name(gc->permit, curitem->name)) { AAA */
 					GSList *list;
@@ -5342,7 +5403,7 @@
 				}
 			} break;
 
-			case 0x0003: { /* Deny buddy */
+			case AIM_SSI_TYPE_DENY: { /* Deny buddy */
 				if (curitem->name) {
 					GSList *list;
 					for (list=account->deny; (list && oscar_util_name_compare(curitem->name, list->data)); list=list->next);
@@ -5354,7 +5415,7 @@
 				}
 			} break;
 
-			case 0x0004: { /* Permit/deny setting */
+			case AIM_SSI_TYPE_PDINFO: { /* Permit/deny setting */
 				/*
 				 * We don't inherit the permit/deny setting from the server
 				 * for ICQ because, for ICQ, this setting controls who can
@@ -5372,7 +5433,7 @@
 				}
 			} break;
 
-			case 0x0005: { /* Presence setting */
+			case AIM_SSI_TYPE_PRESENCEPREFS: { /* Presence setting */
 				/* We don't want to change Purple's setting because it applies to all accounts */
 			} break;
 		} /* End of switch on curitem->type */
@@ -5396,6 +5457,19 @@
 	oscar_set_icon(gc, img);
 	purple_imgstore_unref(img);
 
+	/*
+	 * If we've already received our bos rights then we're not waiting on
+	 * anything else, so send the server clientready.
+	 */
+	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);
+	}
+
 	return 1;
 }
 
@@ -5452,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;
@@ -5473,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) {
@@ -5494,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 */
@@ -5521,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)
@@ -5530,9 +5595,6 @@
 				"group %s\n", name, gname);
 	}
 
-	g_free(gname_utf8);
-	g_free(alias_utf8);
-
 	return 1;
 }
 
@@ -6235,7 +6297,6 @@
 	struct name_data *data;
 	PurpleGroup *g;
 	char *comment;
-	gchar *comment_utf8;
 	gchar *title;
 	PurpleAccount *account;
 	const char *name;
@@ -6254,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);
@@ -6262,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,
@@ -6270,7 +6330,6 @@
 	g_free(title);
 
 	g_free(comment);
-	g_free(comment_utf8);
 }
 
 static void
@@ -7076,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;
 
@@ -7100,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	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Mon Nov 09 01:42:24 2009 +0000
@@ -535,6 +535,10 @@
 		struct aim_userinfo_s *userinfo;
 	} locate;
 
+	struct {
+		gboolean have_rights;
+	} bos;
+
 	/* Server-stored information (ssi) */
 	struct {
 		gboolean received_data;
@@ -619,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 */
 
@@ -657,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);
@@ -1236,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
 
@@ -1681,7 +1685,8 @@
 	guint32 disconnect;
 	guint32 current;
 	guint32 max;
-	guint8 unknown[5]; /* only present in versions >= 3 */
+	guint32 delta;
+	guint8 dropping_snacs;
 	GHashTable *members; /* Key is family and subtype, value is TRUE. */
 
 	struct timeval last; /**< The time when we last sent a SNAC of this rate class. */
--- a/libpurple/protocols/oscar/oscarcommon.h	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/oscar/oscarcommon.h	Mon Nov 09 01:42:24 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/libpurple/protocols/silc/Makefile.mingw	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/silc/Makefile.mingw	Mon Nov 09 01:42:24 2009 +0000
@@ -7,6 +7,8 @@
 PIDGIN_TREE_TOP := ../../..
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
+DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES))
+
 TARGET = libsilc
 NEEDED_DLLS =		$(SILC_TOOLKIT)/bin/libsilc-1-1-2.dll \
 			$(SILC_TOOLKIT)/bin/libsilcclient-1-1-2.dll
@@ -79,7 +81,7 @@
 $(OBJECTS): $(PURPLE_CONFIG_H)
 
 $(TARGET).dll: $(PURPLE_DLL).a $(OBJECTS)
-	$(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--image-base,0x64000000 -o $(TARGET).dll
+	$(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--image-base,0x74000000 -o $(TARGET).dll
 
 ##
 ## CLEAN RULES
--- a/libpurple/protocols/silc10/Makefile.mingw	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/silc10/Makefile.mingw	Mon Nov 09 01:42:24 2009 +0000
@@ -7,6 +7,8 @@
 PIDGIN_TREE_TOP := ../../..
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
+DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES))
+
 TARGET = libsilc
 NEEDED_DLLS =		$(SILC_TOOLKIT)/lib/silc.dll \
 			$(SILC_TOOLKIT)/lib/silcclient.dll
--- a/libpurple/protocols/yahoo/libyahoo.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/yahoo/libyahoo.c	Mon Nov 09 01:42:24 2009 +0000
@@ -249,7 +249,7 @@
 	yahoo_roomlist_get_list,
 	yahoo_roomlist_cancel,
 	yahoo_roomlist_expand_category,
-	NULL, /* can_receive_file */
+	yahoo_can_receive_file, /* can_receive_file */
 	yahoo_send_file,
 	yahoo_new_xfer,
 	yahoo_offline_message, /* offline_message */
--- a/libpurple/protocols/yahoo/libymsg.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/yahoo/libymsg.c	Mon Nov 09 01:42:24 2009 +0000
@@ -154,6 +154,7 @@
 	gboolean unicode = FALSE;
 	char *message = NULL;
 	YahooFederation fed = YAHOO_FEDERATION_NONE;
+	char *fedname = NULL;
 
 	if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) {
 		if (!purple_account_get_remember_password(account))
@@ -194,18 +195,20 @@
 						break;
 					if (p->key == 241) {
 						fed = strtol(p->value, NULL, 10);
+						g_free(fedname);
 						switch (fed) {
 							case YAHOO_FEDERATION_MSN:
-								name = g_strconcat("msn/", name, NULL);
+								name = fedname = g_strconcat("msn/", name, NULL);
 								break;
 							case YAHOO_FEDERATION_OCS:
-								name = g_strconcat("ocs/", name, NULL);
+								name = fedname = g_strconcat("ocs/", name, NULL);
 								break;
 							case YAHOO_FEDERATION_IBM:
-								name = g_strconcat("ibm/", name, NULL);
+								name = fedname = g_strconcat("ibm/", name, NULL);
 								break;
 							case YAHOO_FEDERATION_NONE:
 							default:
+								fedname = NULL;
 								break;
 						}
 						break;
@@ -390,6 +393,7 @@
 			yahoo_update_status(gc, name, f);
 	}
 
+	g_free(fedname);
 }
 
 static void yahoo_do_group_check(PurpleAccount *account, GHashTable *ht, const char *name, const char *group)
@@ -879,6 +883,7 @@
 	char *id;
 	char *msg;
 	YahooFederation fed;
+	char *fed_from;
 };
 
 static void yahoo_process_sms_message(PurpleConnection *gc, struct yahoo_packet *pkt)
@@ -950,9 +955,6 @@
 	GSList *l = pkt->hash;
 	GSList *list = NULL;
 	struct _yahoo_im *im = NULL;
-	const char *imv = NULL;
-	gint val_11 = 0;
-	char *fed_from = NULL;
 
 	account = purple_connection_get_account(gc);
 
@@ -963,10 +965,11 @@
 			if (pair->key == 4 || pair->key == 1) {
 				im = g_new0(struct _yahoo_im, 1);
 				list = g_slist_append(list, im);
-				im->from = fed_from = pair->value;
+				im->from = pair->value;
 				im->time = time(NULL);
 				im->utf8 = TRUE;
 				im->fed = YAHOO_FEDERATION_NONE;
+				im->fed_from = g_strdup(im->from);
 			}
 			if (im && pair->key == 5)
 				im->active_id = pair->value;
@@ -985,32 +988,75 @@
 			}
 			if (im && pair->key == 241) {
 				im->fed = strtol(pair->value, NULL, 10);
+				g_free(im->fed_from);
 				switch (im->fed) {
 					case YAHOO_FEDERATION_MSN:
-						fed_from = g_strconcat("msn/",im->from, NULL);
+						im->fed_from = g_strconcat("msn/",im->from, NULL);
 						break;
 					case YAHOO_FEDERATION_OCS:
-						fed_from = g_strconcat("ocs/",im->from, NULL);
+						im->fed_from = g_strconcat("ocs/",im->from, NULL);
 						break;
 					case YAHOO_FEDERATION_IBM:
-						fed_from = g_strconcat("ibm/",im->from, NULL);
+						im->fed_from = g_strconcat("ibm/",im->from, NULL);
 						break;
 					case YAHOO_FEDERATION_NONE:
 					default:
+						im->fed_from = g_strdup(im->from);
 						break;
 				}
-				purple_debug_info("yahoo", "Message from federated (%d) buddy %s.\n", im->fed, fed_from);
+				purple_debug_info("yahoo", "Message from federated (%d) buddy %s.\n", im->fed, im->fed_from);
 					
 			}
 			/* peer session id */
-			if (pair->key == 11) {
-				if (im)
-					val_11 = strtol(pair->value, NULL, 10);
+			if (im && (pair->key == 11)) {
+				/* disconnect the peer if connected through p2p and sends wrong value for session id */
+				if( (im->fed == YAHOO_FEDERATION_NONE) && (pkt_type == YAHOO_PKT_TYPE_P2P) 
+						&& (yd->session_id != strtol(pair->value, NULL, 10)) )
+				{
+					purple_debug_warning("yahoo","p2p: %s sent us message with wrong session id. Disconnecting p2p connection to peer\n", im->fed_from);
+					/* remove from p2p connection lists, also calls yahoo_p2p_disconnect_destroy_data */
+					g_hash_table_remove(yd->peers, im->fed_from);
+					g_free(im->fed_from);
+					g_free(im);
+					return; /* Not sure whether we should process remaining IMs in this packet */
+				}
 			}
 			/* IMV key */
-			if (pair->key == 63)
+			if (im && pair->key == 63)
 			{
-				imv = pair->value;
+				/* Check for the Doodle IMV, no IMvironment for federated buddies */
+				if (im->from != NULL && im->fed == YAHOO_FEDERATION_NONE)
+				{
+					g_hash_table_replace(yd->imvironments, g_strdup(im->from), g_strdup(pair->value));
+
+					if (strstr(pair->value, "doodle;") != NULL)
+					{
+						PurpleWhiteboard *wb;
+
+						if (!purple_privacy_check(account, im->from)) {
+							purple_debug_info("yahoo", "Doodle request from %s dropped.\n",
+												im->from);
+							g_free(im->fed_from);
+							g_free(im);
+							return;
+						}
+						/* I'm not sure the following ever happens -DAA */
+						wb = purple_whiteboard_get_session(account, im->from);
+
+						/* If a Doodle session doesn't exist between this user */
+						if(wb == NULL)
+						{
+							doodle_session *ds;
+							wb = purple_whiteboard_create(account, im->from,
+											DOODLE_STATE_REQUESTED);
+							ds = wb->proto_data;
+							ds->imv_key = g_strdup(pair->value);
+
+							yahoo_doodle_command_send_request(gc, im->from, pair->value);
+							yahoo_doodle_command_send_ready(gc, im->from, pair->value);
+						}
+					}
+				}
 			}
 			if (pair->key == 429)
 				if (im)
@@ -1022,63 +1068,19 @@
 		                  _("Your Yahoo! message did not get sent."), NULL);
 	}
 
-	/* disconnect the peer if connected through p2p and sends wrong value for session id */
-	if( (pkt_type == YAHOO_PKT_TYPE_P2P) && (val_11 != yd->session_id) ) {
-		purple_debug_warning("yahoo","p2p: %s sent us message with wrong session id. Disconnecting p2p connection to peer\n", im ? fed_from : "(im was null)");
-		/* remove from p2p connection lists, also calls yahoo_p2p_disconnect_destroy_data */
-		if (im) {
-			g_hash_table_remove(yd->peers, fed_from);
-			g_free(im);
-		}
-		return;
-	}
-
-	/* TODO: It seems that this check should be per IM, not global */
-	/* Check for the Doodle IMV */
-	/* no doodle with federated buddies -- assumption???  */
-	if (im != NULL && imv!= NULL && im->from != NULL)
-	{
-		g_hash_table_replace(yd->imvironments, g_strdup(im->from), g_strdup(imv));
-
-		if (strstr(imv, "doodle;") != NULL)
-		{
-			PurpleWhiteboard *wb;
-
-			if (!purple_privacy_check(account, im->from)) {
-				purple_debug_info("yahoo", "Doodle request from %s dropped.\n", im->from);
-				return;
-			}
-
-			/* I'm not sure the following ever happens -DAA */
-
-			wb = purple_whiteboard_get_session(account, im->from);
-
-			/* If a Doodle session doesn't exist between this user */
-			if(wb == NULL)
-			{
-				doodle_session *ds;
-				wb = purple_whiteboard_create(account, im->from, DOODLE_STATE_REQUESTED);
-				ds = wb->proto_data;
-				ds->imv_key = g_strdup(imv);
-
-				yahoo_doodle_command_send_request(gc, im->from, imv);
-				yahoo_doodle_command_send_ready(gc, im->from, imv);
-			}
-		}
-	}
-
 	for (l = list; l; l = l->next) {
 		YahooFriend *f;
 		char *m, *m2;
 		im = l->data;
 
-		if (!fed_from || !im->msg) {
+		if (!im->fed_from || !im->msg) {
+			g_free(im->fed_from);
 			g_free(im);
 			continue;
 		}
 
-		if (!purple_privacy_check(account, fed_from)) {
-			purple_debug_info("yahoo", "Message from %s dropped.\n", fed_from);
+		if (!purple_privacy_check(account, im->fed_from)) {
+			purple_debug_info("yahoo", "Message from %s dropped.\n", im->fed_from);
 			return;
 		}
 
@@ -1116,10 +1118,11 @@
 		if (!strcmp(m, "<ding>")) {
 			char *username;
 
-			username = g_markup_escape_text(fed_from, -1);
+			username = g_markup_escape_text(im->fed_from, -1);
 			purple_prpl_got_attention(gc, username, YAHOO_BUZZ);
 			g_free(username);
 			g_free(m);
+			g_free(im->fed_from);
 			g_free(im);
 			continue;
 		}
@@ -1127,7 +1130,7 @@
 		m2 = yahoo_codes_to_html(m);
 		g_free(m);
 
-		serv_got_im(gc, fed_from, m2, 0, im->time);
+		serv_got_im(gc, im->fed_from, m2, 0, im->time);
 		g_free(m2);
 
 		/* Official clients don't share buddy images with federated buddies */
@@ -1140,10 +1143,10 @@
 			}
 		}
 
+		g_free(im->fed_from);
 		g_free(im);
 	}
-	if (fed_from != im->from)
-		g_free(fed_from);
+
 	g_slist_free(list);
 }
 
@@ -3996,7 +3999,7 @@
 
 	}
 
-	if (f && f->status != YAHOO_STATUS_OFFLINE) {
+	if (f && f->status != YAHOO_STATUS_OFFLINE && f->fed == YAHOO_FEDERATION_NONE) {
 		if (!yd->wm) {
 			act = purple_menu_action_new(_("Join in Chat"),
 			                           PURPLE_CALLBACK(yahoo_chat_goto_menu),
@@ -4036,10 +4039,12 @@
 		                           build_presence_submenu(f, gc));
 		m = g_list_append(m, act);
 
-		act = purple_menu_action_new(_("Start Doodling"),
-		                           PURPLE_CALLBACK(yahoo_doodle_blist_node),
-		                           NULL, NULL);
-		m = g_list_append(m, act);
+		if (f->fed == YAHOO_FEDERATION_NONE) {
+			act = purple_menu_action_new(_("Start Doodling"),
+					PURPLE_CALLBACK(yahoo_doodle_blist_node),
+					NULL, NULL);
+			m = g_list_append(m, act);
+		}
 
 		act = purple_menu_action_new(_("Set User Info..."),
 		                           PURPLE_CALLBACK(yahoo_userinfo_blist_node),
@@ -4364,17 +4369,7 @@
 		}
 	}
 
-	if (who[3] == '/') {
-		if (!g_ascii_strncasecmp(who, "msn/", 4)) {
-			fed = YAHOO_FEDERATION_MSN;
-		}
-		else if (!g_ascii_strncasecmp(who, "ocs/", 4)) {
-			fed = YAHOO_FEDERATION_OCS;
-		}
-		else if (!g_ascii_strncasecmp(who, "ibm/", 4)) {
-			fed = YAHOO_FEDERATION_IBM;
-		}
-	}
+	fed = yahoo_get_federation_from_name(who);
 
 	if (who[0] == '+') {
 		/* we have an sms to be sent */
@@ -4506,17 +4501,7 @@
 	YahooFederation fed = YAHOO_FEDERATION_NONE;
 	struct yahoo_packet *pkt = NULL;
 
-	if (who[3] == '/') {
-		if (!g_ascii_strncasecmp(who, "msn/", 4)) {
-			fed = YAHOO_FEDERATION_MSN;
-		}
-		else if (!g_ascii_strncasecmp(who, "ocs/", 4)) {
-			fed = YAHOO_FEDERATION_OCS;
-		}
-		else if (!g_ascii_strncasecmp(who, "ibm/", 4)) {
-			fed = YAHOO_FEDERATION_IBM;
-		}
-	}
+	fed = yahoo_get_federation_from_name(who);
 
 	/* Don't do anything if sms is being typed */
 	if( strncmp(who, "+", 1) == 0 )
@@ -4808,18 +4793,9 @@
 		return;
 
 	f = yahoo_friend_find(gc, bname);
-	if (bname[3] == '/') {
+	fed = yahoo_get_federation_from_name(bname);
+	if (fed != YAHOO_FEDERATION_NONE)
 		fed_bname += 4;
-		if (!g_ascii_strncasecmp(bname, "msn/", 4)) {
-			fed = YAHOO_FEDERATION_MSN;
-		}
-		else if (!g_ascii_strncasecmp(bname, "ocs/", 4)) {
-			fed = YAHOO_FEDERATION_OCS;
-		}
-		else if (!g_ascii_strncasecmp(bname, "ibm/", 4)) {
-			fed = YAHOO_FEDERATION_IBM;
-		}
-	}
 
 	g = purple_buddy_get_group(buddy);
 	if (g)
@@ -4931,18 +4907,8 @@
 	if (!who || who[0] == '\0')
 		return;
 
-	if (who[3] == '/') {
-		if (!g_ascii_strncasecmp(who, "msn/", 4)) {
-			fed = YAHOO_FEDERATION_MSN;
-		}
-		else if (!g_ascii_strncasecmp(who, "ocs/", 4)) {
-			fed = YAHOO_FEDERATION_OCS;
-		}
-		else if (!g_ascii_strncasecmp(who, "ibm/", 4)) {
-			fed = YAHOO_FEDERATION_IBM;
-		}
-	}
-	
+	fed = yahoo_get_federation_from_name(who);
+
 	pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id);
 
 	if(fed)
@@ -4963,17 +4929,8 @@
 
 	if (!who || who[0] == '\0')
 		return;
-	if (who[3] == '/') {
-		if (!g_ascii_strncasecmp(who, "msn/", 4)) {
-			fed = YAHOO_FEDERATION_MSN;
-		}
-		else if (!g_ascii_strncasecmp(who, "ocs/", 4)) {
-			fed = YAHOO_FEDERATION_OCS;
-		}
-		else if (!g_ascii_strncasecmp(who, "ibm/", 4)) {
-			fed = YAHOO_FEDERATION_IBM;
-		}
-	}
+	fed = yahoo_get_federation_from_name(who);
+
 	pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id);
 
 	if(fed)
--- a/libpurple/protocols/yahoo/libymsg.h	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/yahoo/libymsg.h	Mon Nov 09 01:42:24 2009 +0000
@@ -346,6 +346,7 @@
 
 char *yahoo_convert_to_numeric(const char *str);
 
+YahooFederation yahoo_get_federation_from_name(const char *who);
 
 /* yahoo_profile.c */
 void yahoo_get_info(PurpleConnection *gc, const char *name);
--- a/libpurple/protocols/yahoo/util.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/yahoo/util.c	Mon Nov 09 01:42:24 2009 +0000
@@ -916,3 +916,18 @@
 
 	return g_string_free(dest, FALSE);
 }
+
+YahooFederation yahoo_get_federation_from_name(const char *who)
+{
+	YahooFederation fed = YAHOO_FEDERATION_NONE;
+	if (who[3] == '/') {
+		if (!g_ascii_strncasecmp(who, "msn", 3))
+			fed = YAHOO_FEDERATION_MSN;
+		else if (!g_ascii_strncasecmp(who, "ocs", 3))
+			fed = YAHOO_FEDERATION_OCS;
+		else if (!g_ascii_strncasecmp(who, "ibm", 3))
+			fed = YAHOO_FEDERATION_IBM;
+	}
+	return fed;
+}
+
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.c	Mon Nov 09 01:42:24 2009 +0000
@@ -1070,6 +1070,13 @@
 	yahoo_packet_send_and_free(pkt, yd);
 }
 
+gboolean yahoo_can_receive_file(PurpleConnection *gc, const char *who)
+{
+	if (!who || yahoo_get_federation_from_name(who) != YAHOO_FEDERATION_NONE)
+		return FALSE;
+	return TRUE;
+}
+
 void yahoo_send_file(PurpleConnection *gc, const char *who, const char *file)
 {
 	struct yahoo_xfer_data *xfer_data;
--- a/libpurple/protocols/yahoo/yahoo_filexfer.h	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.h	Mon Nov 09 01:42:24 2009 +0000
@@ -43,6 +43,18 @@
 PurpleXfer *yahoo_new_xfer(PurpleConnection *gc, const char *who);
 
 /**
+ * Returns TRUE if the buddy can receive file, FALSE otherwise.
+ * Federated users cannot receive files. So this will return FALSE only
+ * for them.
+ *
+ * @param gc The connection
+ * @param who The name of the remote user
+ *
+ * @return TRUE or FALSE
+ */
+gboolean yahoo_can_receive_file(PurpleConnection *gc, const char *who);
+
+/**
  * Send a file.
  *
  * @param gc The PurpleConnection handle.
--- a/libpurple/server.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/server.c	Mon Nov 09 01:42:24 2009 +0000
@@ -786,14 +786,14 @@
 	struct chat_invite_data *cid;
 	int plugin_return;
 
+	g_return_if_fail(name != NULL);
+	g_return_if_fail(who != NULL);
+
 	account = purple_connection_get_account(gc);
-	if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->set_permit_deny == NULL) {
-		/* protocol does not support privacy, handle it ourselves */
-		if (!purple_privacy_check(account, who)) {
-			purple_signal_emit(purple_conversations_get_handle(), "chat-invite-blocked",
-					account, who, name, message, data);
-			return;
-		}
+	if (!purple_privacy_check(account, who)) {
+		purple_signal_emit(purple_conversations_get_handle(), "chat-invite-blocked",
+				account, who, name, message, data);
+		return;
 	}
 
 	cid = g_new0(struct chat_invite_data, 1);
--- a/libpurple/tests/test_jabber_jutil.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/tests/test_jabber_jutil.c	Mon Nov 09 01:42:24 2009 +0000
@@ -134,6 +134,14 @@
 	assert_invalid_jid("paul@2[::1]124/as");
 	assert_invalid_jid("paul@まつ.おおかみ/\x01");
 
+	/*
+	 * RFC 3454 Section 6 reads, in part,
+	 * "If a string contains any RandALCat character, the
+	 *  string MUST NOT contain any LCat character."
+	 * The character is U+066D (ARABIC FIVE POINTED STAR).
+	 */
+	assert_invalid_jid("foo@example.com/٭simplexe٭");
+
 	/* Ensure that jabber_id_new is properly lowercasing node and domains */
 	assert_jid_parts("paul", "darkrain42.org", "PaUL@darkrain42.org");
 	assert_jid_parts("paul", "darkrain42.org", "paul@DaRkRaIn42.org");
--- a/libpurple/theme-loader.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/theme-loader.c	Mon Nov 09 01:42:24 2009 +0000
@@ -100,6 +100,7 @@
 	PurpleThemeLoaderPrivate *priv = PURPLE_THEME_LOADER_GET_PRIVATE(loader);
 
 	g_free(priv->type);
+	g_free(priv);
 
 	parent_class->finalize(obj);
 }
--- a/libpurple/win32/global.mak	Sun Nov 08 01:12:44 2009 +0000
+++ b/libpurple/win32/global.mak	Mon Nov 09 01:42:24 2009 +0000
@@ -85,7 +85,7 @@
 DEFINES += -DHAVE_CYRUS_SASL
 endif
 
-DEFINES += -DHAVE_CONFIG_H
+DEFINES += -DHAVE_CONFIG_H -DWIN32_LEAN_AND_MEAN
 
 # Use -g flag when building debug version of Pidgin (including plugins).
 # Use -fnative-struct instead of -mms-bitfields when using mingw 1.1
--- a/pidgin.desktop.in	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin.desktop.in	Mon Nov 09 01:42:24 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/Makefile.mingw	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/Makefile.mingw	Mon Nov 09 01:42:24 2009 +0000
@@ -7,6 +7,8 @@
 PIDGIN_TREE_TOP := ..
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
+DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES))
+
 NEEDED_DLLS = $(GTKSPELL_TOP)/gtkspell/libgtkspell.dll
 
 ##
@@ -55,12 +57,12 @@
 ##
 PIDGIN_C_SRC =	\
 			gtkaccount.c \
-			gtkblist.c \
+			gtkblist-theme-loader.c \
 			gtkblist-theme.c \
-			gtkblist-theme-loader.c \
-			gtkcertmgr.c \
+			gtkblist.c \
 			gtkcellrendererexpander.c \
 			gtkcellrendererprogress.c \
+			gtkcertmgr.c \
 			gtkconn.c \
 			gtkconv.c \
 			gtkdebug.c \
@@ -70,8 +72,8 @@
 			gtkeventloop.c \
 			gtkexpander.c \
 			gtkft.c \
+			gtkicon-theme-loader.c \
 			gtkicon-theme.c \
-			gtkicon-theme-loader.c \
 			gtkidle.c \
 			gtkimhtml.c \
 			gtkimhtmltoolbar.c \
--- a/pidgin/gtkblist.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/gtkblist.c	Mon Nov 09 01:42:24 2009 +0000
@@ -4184,6 +4184,12 @@
 		}
 	}
 
+	if (hidden_conv) {
+		char *tmp = nametext;
+		nametext = g_strdup_printf("<b>%s</b>", tmp);
+		g_free(tmp);
+	}
+
 	/* Put it all together */
 	if ((!aliased || biglist) && (statustext || idletime)) {
 		/* using <span size='smaller'> breaks the status, so it must be seperated into <small><span>*/
--- a/pidgin/gtkconn.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/gtkconn.c	Mon Nov 09 01:42:24 2009 +0000
@@ -142,7 +142,6 @@
 {
 	PurpleAccount *account = NULL;
 	PidginAutoRecon *info;
-	GList *list;
 
 	account = purple_connection_get_account(gc);
 	info = g_hash_table_lookup(auto_reconns, account);
@@ -164,17 +163,6 @@
 
 		purple_account_set_enabled(account, PIDGIN_UI, FALSE);
 	}
-
-	/* If we have any open chats, we probably want to rejoin when we get back online. */
-	list = purple_get_chats();
-	while (list) {
-		PurpleConversation *conv = list->data;
-		list = list->next;
-		if (conv->account != account ||
-				purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)))
-			continue;
-		purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE));
-	}
 }
 
 static void pidgin_connection_network_connected (void)
--- a/pidgin/gtkconv.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/gtkconv.c	Mon Nov 09 01:42:24 2009 +0000
@@ -4185,7 +4185,7 @@
 		/* Users */
 		for (; l != NULL; l = l->next) {
 			tab_complete_process_item(&most_matched, entered, entered_bytes, &partial, nick_partial,
-									  &matches, TRUE, ((PurpleConvChatBuddy *)l->data)->name);
+									  &matches, FALSE, ((PurpleConvChatBuddy *)l->data)->name);
 		}
 
 
@@ -7234,6 +7234,28 @@
 	}
 }
 
+static void
+account_signing_off(PurpleConnection *gc)
+{
+	GList *list = purple_get_chats();
+	PurpleAccount *account = purple_connection_get_account(gc);
+
+	/* We are about to sign off. See which chats we are currently in, and mark
+	 * them for rejoin on reconnect. */
+	while (list) {
+		PurpleConversation *conv = list->data;
+		if (!purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)) &&
+				purple_conversation_get_account(conv) == account) {
+			purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE));
+			purple_conversation_write(conv, NULL, _("The account has disconnected and you are no "
+						"longer in this chat. You will be automatically rejoined in the chat when "
+						"the account reconnects."),
+					PURPLE_MESSAGE_SYSTEM, time(NULL));
+		}
+		list = list->next;
+	}
+}
+
 static gboolean
 update_buddy_status_timeout(PurpleBuddy *buddy)
 {
@@ -7728,6 +7750,8 @@
 	purple_signal_connect(purple_connections_get_handle(), "signed-off", handle,
 						G_CALLBACK(account_signed_off_cb),
 						GINT_TO_POINTER(PURPLE_CONV_ACCOUNT_OFFLINE));
+	purple_signal_connect(purple_connections_get_handle(), "signing-off", handle,
+						G_CALLBACK(account_signing_off), NULL);
 
 	purple_signal_connect(purple_conversations_get_handle(), "received-im-msg",
 						handle, G_CALLBACK(received_im_msg_cb), NULL);
--- a/pidgin/gtkimhtml.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/gtkimhtml.c	Mon Nov 09 01:42:24 2009 +0000
@@ -5053,7 +5053,7 @@
 		                                     It will be destroyed when 'anchor' is destroyed. */
 		anchor = gtk_text_buffer_create_child_anchor(imhtml->text_buffer, iter);
 		g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", text, g_free);
-		g_object_set_data(G_OBJECT(anchor), "gtkimhtml_tiptext", text);
+		g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext", g_strdup(text), g_free);
 		g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free);
 
 		/* This catches the expose events generated by animated
@@ -5075,7 +5075,8 @@
 			gtk_container_add(GTK_CONTAINER(ebox), img);
 			gtk_widget_show(img);
 			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", text, g_free);
-			g_object_set_data(G_OBJECT(anchor), "gtkimhtml_tiptext", text);
+			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_tiptext",
+				g_strdup(text), g_free);
 			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley), g_free);
 			gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml), ebox, anchor);
 		}
--- a/pidgin/gtkmain.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/gtkmain.c	Mon Nov 09 01:42:24 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     *
@@ -744,7 +804,7 @@
 	}
 
 #if GLIB_CHECK_VERSION(2,2,0)
-	g_set_application_name(_("Pidgin"));
+	g_set_application_name(PIDGIN_NAME);
 #endif /* glib-2.0 >= 2.2.0 */
 
 #ifdef _WIN32
--- a/pidgin/gtkmedia.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/gtkmedia.c	Mon Nov 09 01:42:24 2009 +0000
@@ -89,6 +89,7 @@
 	GtkWidget *menubar;
 	GtkWidget *statusbar;
 
+	GtkWidget *hold;
 	GtkWidget *mute;
 	GtkWidget *pause;
 
@@ -187,6 +188,15 @@
 }
 
 static void
+pidgin_media_hold_toggled(GtkToggleButton *toggle, PidginMedia *media)
+{
+	purple_media_stream_info(media->priv->media,
+			gtk_toggle_button_get_active(toggle) ?
+			PURPLE_MEDIA_INFO_HOLD : PURPLE_MEDIA_INFO_UNHOLD,
+			NULL, NULL, TRUE);
+}
+
+static void
 pidgin_media_mute_toggled(GtkToggleButton *toggle, PidginMedia *media)
 {
 	purple_media_stream_info(media->priv->media,
@@ -633,6 +643,16 @@
 				FALSE, FALSE, 0);
 		gtk_widget_show(GTK_WIDGET(button_widget));
 		gtk_widget_show(send_widget);
+
+		/* Hold button */
+		gtkmedia->priv->hold =
+				gtk_toggle_button_new_with_mnemonic("_Hold");
+		g_signal_connect(gtkmedia->priv->hold, "toggled",
+				G_CALLBACK(pidgin_media_hold_toggled),
+				gtkmedia);
+		gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->hold,
+				FALSE, FALSE, 0);
+		gtk_widget_show(gtkmedia->priv->hold);
 	} else {
 		send_widget = gtkmedia->priv->send_widget;
 		button_widget = gtkmedia->priv->button_widget;
--- a/pidgin/gtkpounce.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/gtkpounce.c	Mon Nov 09 01:42:24 2009 +0000
@@ -1455,7 +1455,7 @@
 		 * Here we place the protocol name in the pounce dialog to lessen
 		 * confusion about what protocol a pounce is for.
 		 */
-		tmp = g_strdup_printf(
+		tmp = g_strdup(
 				   (events & PURPLE_POUNCE_TYPING) ?
 				   _("Started typing") :
 				   (events & PURPLE_POUNCE_TYPED) ?
--- a/pidgin/gtkprefs.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/gtkprefs.c	Mon Nov 09 01:42:24 2009 +0000
@@ -77,6 +77,7 @@
 static GtkListStore *smiley_theme_store = NULL;
 static GtkTreeSelection *smiley_theme_sel = NULL;
 static GtkWidget *prefs_proxy_frame = NULL;
+static GtkWidget *prefs_proxy_subframe = NULL;
 
 static GtkWidget *prefs = NULL;
 static GtkWidget *debugbutton = NULL;
@@ -624,7 +625,8 @@
 		_("The default Pidgin status icon theme"));
 	gtk_list_store_set(prefs_status_icon_themes, &iter, 0, pixbuf, 1, tmp, 2, "", -1);
 	g_free(tmp);
-	g_object_unref(G_OBJECT(pixbuf));
+	if (pixbuf)
+		g_object_unref(G_OBJECT(pixbuf));
 
 	purple_theme_manager_for_each_theme(prefs_themes_sort);
 	pref_sound_generate_markup();
@@ -1132,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);
@@ -1886,23 +1888,27 @@
 	pidgin_prefs_checkbox(_("_Enable automatic router port forwarding"),
 			"/purple/network/map_ports", vbox);
 
-	ports_checkbox = pidgin_prefs_checkbox(_("_Manually specify range of ports to listen on"),
-			"/purple/network/ports_range_use", vbox);
-
-	spin_button = pidgin_prefs_labeled_spin_button(vbox, _("_Start port:"),
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+
+	ports_checkbox = pidgin_prefs_checkbox(_("_Manually specify range of ports to listen on:"),
+			"/purple/network/ports_range_use", hbox);
+
+	spin_button = pidgin_prefs_labeled_spin_button(hbox, _("_Start:"),
 			"/purple/network/ports_range_start", 0, 65535, sg);
 	if (!purple_prefs_get_bool("/purple/network/ports_range_use"))
 		gtk_widget_set_sensitive(GTK_WIDGET(spin_button), FALSE);
 	g_signal_connect(G_OBJECT(ports_checkbox), "clicked",
 					 G_CALLBACK(pidgin_toggle_sensitive), spin_button);
 
-	spin_button = pidgin_prefs_labeled_spin_button(vbox, _("_End port:"),
+	spin_button = pidgin_prefs_labeled_spin_button(hbox, _("_End:"),
 			"/purple/network/ports_range_end", 0, 65535, sg);
 	if (!purple_prefs_get_bool("/purple/network/ports_range_use"))
 		gtk_widget_set_sensitive(GTK_WIDGET(spin_button), FALSE);
 	g_signal_connect(G_OBJECT(ports_checkbox), "clicked",
 					 G_CALLBACK(pidgin_toggle_sensitive), spin_button);
 
+	pidgin_add_widget_to_vbox(GTK_BOX(vbox), NULL, NULL, hbox, TRUE, NULL);
+
 	g_object_unref(sg);
 
 	/* TURN server */
@@ -1921,9 +1927,9 @@
 
 	pidgin_prefs_labeled_spin_button(hbox, _("_Port:"),
 		"/purple/network/turn_port", 0, 65535, NULL);
-	hbox = pidgin_prefs_labeled_entry(vbox, _("_Username:"),
+	hbox = pidgin_prefs_labeled_entry(vbox, _("Use_rname:"),
 		"/purple/network/turn_username", sg);
-	pidgin_prefs_labeled_password(hbox, _("_Password:"),
+	pidgin_prefs_labeled_password(hbox, _("Pass_word:"),
 		"/purple/network/turn_password", NULL);
 
 	if (purple_running_gnome()) {
@@ -1967,9 +1973,15 @@
 		gtk_widget_show(browser_button);
 	} else {
 		vbox = pidgin_make_frame(ret, _("Proxy Server"));
-		prefs_proxy_frame = gtk_vbox_new(FALSE, 0);
-
-		pidgin_prefs_dropdown(vbox, _("Proxy _type:"), PURPLE_PREF_STRING,
+		prefs_proxy_frame = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+		prefs_proxy_subframe = gtk_vbox_new(FALSE, 0);
+
+		/* This is a global option that affects SOCKS4 usage even with account-specific proxy settings */
+		pidgin_prefs_checkbox(_("Use remote _DNS with SOCKS4 proxies"),
+							  "/purple/proxy/socks4_remotedns", prefs_proxy_frame);
+		gtk_box_pack_start(GTK_BOX(vbox), prefs_proxy_frame, 0, 0, 0);
+
+		pidgin_prefs_dropdown(prefs_proxy_frame, _("Proxy t_ype:"), PURPLE_PREF_STRING,
 					"/purple/proxy/type",
 					_("No proxy"), "none",
 					"SOCKS 4", "socks4",
@@ -1977,21 +1989,17 @@
 					"HTTP", "http",
 					_("Use Environmental Settings"), "envvar",
 					NULL);
-		gtk_box_pack_start(GTK_BOX(vbox), prefs_proxy_frame, 0, 0, 0);
+		gtk_box_pack_start(GTK_BOX(prefs_proxy_frame), prefs_proxy_subframe, 0, 0, 0);
 		proxy_info = purple_global_proxy_get_info();
 
 		purple_prefs_connect_callback(prefs, "/purple/proxy/type",
-					    proxy_changed_cb, prefs_proxy_frame);
-
-		/* This is a global option that affects SOCKS4 usage even with account-specific proxy settings */
-		pidgin_prefs_checkbox(_("Use remote DNS with SOCKS4 proxies"),
-							  "/purple/proxy/socks4_remotedns", prefs_proxy_frame);
+					    proxy_changed_cb, prefs_proxy_subframe);
 
 		table = gtk_table_new(4, 2, FALSE);
 		gtk_container_set_border_width(GTK_CONTAINER(table), 0);
 		gtk_table_set_col_spacings(GTK_TABLE(table), 5);
 		gtk_table_set_row_spacings(GTK_TABLE(table), 10);
-		gtk_container_add(GTK_CONTAINER(prefs_proxy_frame), table);
+		gtk_container_add(GTK_CONTAINER(prefs_proxy_subframe), table);
 
 
 		label = gtk_label_new_with_mnemonic(_("_Host:"));
@@ -2012,11 +2020,11 @@
 		gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
 		pidgin_set_accessible_label (entry, label);
 
-		label = gtk_label_new_with_mnemonic(_("_Port:"));
+		label = gtk_label_new_with_mnemonic(_("P_ort:"));
 		gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
 		gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
 
-		entry = gtk_entry_new();
+		entry = gtk_spin_button_new_with_range(0, 65535, 1);
 		gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
 		gtk_table_attach(GTK_TABLE(table), entry, 3, 4, 0, 1, GTK_FILL, 0, 0, 0);
 		g_signal_connect(G_OBJECT(entry), "changed",
@@ -2031,7 +2039,7 @@
 		}
 		pidgin_set_accessible_label (entry, label);
 
-		label = gtk_label_new_with_mnemonic(_("_User:"));
+		label = gtk_label_new_with_mnemonic(_("User_name:"));
 		gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
 		gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
 
--- a/pidgin/gtkrequest.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/gtkrequest.c	Mon Nov 09 01:42:24 2009 +0000
@@ -81,6 +81,33 @@
 } PidginRequestData;
 
 static void
+pidgin_widget_decorate_account(GtkWidget *cont, PurpleAccount *account)
+{
+	GtkWidget *image;
+	GdkPixbuf *pixbuf;
+	GtkTooltips *tips;
+
+	if (!account)
+		return;
+
+	pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
+	image = gtk_image_new_from_pixbuf(pixbuf);
+	g_object_unref(G_OBJECT(pixbuf));
+
+	tips = gtk_tooltips_new();
+	gtk_tooltips_set_tip(tips, image, purple_account_get_username(account), NULL);
+
+	if (GTK_IS_DIALOG(cont)) {
+		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(cont)->action_area), image, FALSE, TRUE, 0);
+		gtk_box_reorder_child(GTK_BOX(GTK_DIALOG(cont)->action_area), image, 0);
+	} else if (GTK_IS_HBOX(cont)) {
+		gtk_misc_set_alignment(GTK_MISC(image), 0, 0);
+		gtk_box_pack_end(GTK_BOX(cont), image, FALSE, TRUE, 0);
+	}
+	gtk_widget_show(image);
+}
+
+static void
 generic_response_start(PidginRequestData *data)
 {
 	g_return_if_fail(data != NULL);
@@ -347,6 +374,8 @@
 
 	gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
 
+	pidgin_widget_decorate_account(hbox, account);
+
 	/* Descriptive label */
 	primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
 	secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
@@ -515,6 +544,8 @@
 	gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
 	gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
 
+	pidgin_widget_decorate_account(hbox, account);
+
 	/* Vertical box */
 	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
 	gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
@@ -639,6 +670,8 @@
 	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
 	gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
 
+	pidgin_widget_decorate_account(hbox, account);
+
 	/* Descriptive label */
 	primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
 	secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
@@ -1144,6 +1177,8 @@
 	GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
 	gtk_window_set_default(GTK_WINDOW(win), button);
 
+	pidgin_widget_decorate_account(hbox, account);
+
 	/* Setup the vbox */
 	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
 	gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
--- a/pidgin/gtkroomlist.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/gtkroomlist.c	Mon Nov 09 01:42:24 2009 +0000
@@ -111,7 +111,18 @@
 static void dialog_select_account_cb(GObject *w, PurpleAccount *account,
 				     PidginRoomlistDialog *dialog)
 {
+	gboolean change = (account != dialog->account);
 	dialog->account = account;
+
+	if (change && dialog->roomlist) {
+		PidginRoomlist *rl = dialog->roomlist->ui_data;
+		if (rl->tree) {
+			gtk_widget_destroy(rl->tree);
+			rl->tree = NULL;
+		}
+		purple_roomlist_unref(dialog->roomlist);
+		dialog->roomlist = NULL;
+	}
 }
 
 static void list_button_cb(GtkButton *button, PidginRoomlistDialog *dialog)
--- a/pidgin/gtkstatusbox.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/gtkstatusbox.c	Mon Nov 09 01:42:24 2009 +0000
@@ -79,8 +79,8 @@
 
 static void pidgin_status_box_pulse_typing(PidginStatusBox *status_box);
 static void pidgin_status_box_refresh(PidginStatusBox *status_box);
-static void status_menu_refresh_iter(PidginStatusBox *status_box);
-static void pidgin_status_box_regenerate(PidginStatusBox *status_box);
+static void status_menu_refresh_iter(PidginStatusBox *status_box, gboolean status_changed);
+static void pidgin_status_box_regenerate(PidginStatusBox *status_box, gboolean status_changed);
 static void pidgin_status_box_changed(PidginStatusBox *box);
 static void pidgin_status_box_size_request (GtkWidget *widget, GtkRequisition *requisition);
 static void pidgin_status_box_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
@@ -304,7 +304,7 @@
 	if (status_box->account == account)
 		update_to_reflect_account_status(status_box, account, newstatus);
 	else if (status_box->token_status_account == account)
-		status_menu_refresh_iter(status_box);
+		status_menu_refresh_iter(status_box, TRUE);
 }
 
 static gboolean
@@ -312,6 +312,7 @@
 {
 	if (event->button == 3) {
 		GtkWidget *menu_item;
+		const char *path;
 
 		if (box->icon_box_menu)
 			gtk_widget_destroy(box->icon_box_menu);
@@ -325,7 +326,8 @@
 		menu_item = pidgin_new_item_from_stock(box->icon_box_menu, _("Remove"), GTK_STOCK_REMOVE,
 						     G_CALLBACK(remove_buddy_icon_cb),
 						     box, 0, 0, NULL);
-		if (purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon") == NULL)
+		if (!(path = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon"))
+				|| !*path)
 			gtk_widget_set_sensitive(menu_item, FALSE);
 
 		gtk_menu_popup(GTK_MENU(box->icon_box_menu), NULL, NULL, NULL, NULL,
@@ -559,7 +561,7 @@
 		else
 			statusbox->token_status_account = check_active_accounts_for_identical_statuses();
 
-		pidgin_status_box_regenerate(statusbox);
+		pidgin_status_box_regenerate(statusbox, TRUE);
 
 		break;
 	default:
@@ -821,7 +823,7 @@
  * keyboard signals instead of the changed signal?
  */
 static void
-status_menu_refresh_iter(PidginStatusBox *status_box)
+status_menu_refresh_iter(PidginStatusBox *status_box, gboolean status_changed)
 {
 	PurpleSavedStatus *saved_status;
 	PurpleStatusPrimitive primitive;
@@ -912,18 +914,15 @@
 	} else
 		status_box->active_row = NULL;
 
-	message = purple_savedstatus_get_message(saved_status);
-	if (!purple_savedstatus_is_transient(saved_status) || !message || !*message)
-	{
-		status_box->imhtml_visible = FALSE;
-		gtk_widget_hide_all(status_box->vbox);
-	}
-	else
-	{
-		status_box->imhtml_visible = TRUE;
-		gtk_widget_show_all(status_box->vbox);
+	if (status_changed) {
+		message = purple_savedstatus_get_message(saved_status);
 
 		/*
+		 * If we are going to hide the imhtml, don't retain the
+		 * message because showing the old message later is
+		 * confusing. If we are going to set the message to a pre-set,
+		 * then we need to do this anyway
+		 *
 		 * Suppress the "changed" signal because the status
 		 * was changed programmatically.
 		 */
@@ -931,12 +930,24 @@
 
 		gtk_imhtml_clear(GTK_IMHTML(status_box->imhtml));
 		gtk_imhtml_clear_formatting(GTK_IMHTML(status_box->imhtml));
-		gtk_imhtml_append_text(GTK_IMHTML(status_box->imhtml), message, 0);
+
+		if (!purple_savedstatus_is_transient(saved_status) || !message || !*message)
+		{
+			status_box->imhtml_visible = FALSE;
+			gtk_widget_hide_all(status_box->vbox);
+		}
+		else
+		{
+			status_box->imhtml_visible = TRUE;
+			gtk_widget_show_all(status_box->vbox);
+
+			gtk_imhtml_append_text(GTK_IMHTML(status_box->imhtml), message, 0);
+		}
+
 		gtk_widget_set_sensitive(GTK_WIDGET(status_box->imhtml), TRUE);
+		update_size(status_box);
 	}
 
-	update_size(status_box);
-
 	/* Stop suppressing the "changed" signal. */
 	gtk_widget_set_sensitive(GTK_WIDGET(status_box), TRUE);
 }
@@ -996,50 +1007,50 @@
  * statuses and a token account if they do */
 static PurpleAccount* check_active_accounts_for_identical_statuses(void)
 {
-	PurpleAccount *acct = NULL, *acct2;
-	GList *tmp, *tmp2, *active_accts = purple_accounts_get_all_active();
-	GList *s, *s1, *s2;
-
-	for (tmp = active_accts; tmp; tmp = tmp->next) {
-		acct = tmp->data;
-		s = purple_account_get_status_types(acct);
-		for (tmp2 = tmp->next; tmp2; tmp2 = tmp2->next) {
-			acct2 = tmp2->data;
-
-			/* Only actually look at the statuses if the accounts use the same prpl */
-			if (strcmp(purple_account_get_protocol_id(acct), purple_account_get_protocol_id(acct2))) {
-				acct = NULL;
-				break;
-			}
-
-			s2 = purple_account_get_status_types(acct2);
-
-			s1 = s;
-			while (s1 && s2) {
-				PurpleStatusType *st1 = s1->data, *st2 = s2->data;
-				/* TODO: Are these enough to consider the statuses identical? */
-				if (purple_status_type_get_primitive(st1) != purple_status_type_get_primitive(st2)
-						|| strcmp(purple_status_type_get_id(st1), purple_status_type_get_id(st2))
-						|| strcmp(purple_status_type_get_name(st1), purple_status_type_get_name(st2))) {
-					acct = NULL;
-					break;
-				}
-
-				s1 = s1->next;
-				s2 = s2->next;
-			}
-
-			if (s1 != s2) {/* Will both be NULL if matched */
-				acct = NULL;
+	GList *iter, *active_accts = purple_accounts_get_all_active();
+	PurpleAccount *acct1 = NULL;
+	const char *prpl1 = NULL;
+
+	if (active_accts) {
+		acct1 = active_accts->data;
+		prpl1 = purple_account_get_protocol_id(acct1);
+	} else {
+		/* there's no enabled account */
+		return NULL;
+	}
+
+	/* start at the second account */
+	for (iter = active_accts->next; iter; iter = iter->next) {
+		PurpleAccount *acct2 = iter->data;
+		GList *s1, *s2;
+
+		if (!g_str_equal(prpl1, purple_account_get_protocol_id(acct2))) {
+			acct1 = NULL;
+			break;
+		}
+
+		for (s1 = purple_account_get_status_types(acct1),
+				 s2 = purple_account_get_status_types(acct2); s1 && s2;
+			 s1 = s1->next, s2 = s2->next) {
+			PurpleStatusType *st1 = s1->data, *st2 = s2->data;
+			/* TODO: Are these enough to consider the statuses identical? */
+			if (purple_status_type_get_primitive(st1) != purple_status_type_get_primitive(st2)
+				|| strcmp(purple_status_type_get_id(st1), purple_status_type_get_id(st2))
+				|| strcmp(purple_status_type_get_name(st1), purple_status_type_get_name(st2))) {
+				acct1 = NULL;
 				break;
 			}
 		}
-		if (!acct)
+
+		if (s1 != s2) {/* Will both be NULL if matched */
+			acct1 = NULL;
 			break;
+		}
 	}
+
 	g_list_free(active_accts);
 
-	return acct;
+	return acct1;
 }
 
 static void
@@ -1068,7 +1079,7 @@
 }
 
 static void
-pidgin_status_box_regenerate(PidginStatusBox *status_box)
+pidgin_status_box_regenerate(PidginStatusBox *status_box, gboolean status_changed)
 {
 	GtkIconSize icon_size;
 
@@ -1104,7 +1115,7 @@
 		pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_CUSTOM, NULL, _("New status..."), NULL, NULL);
 		pidgin_status_box_add(PIDGIN_STATUS_BOX(status_box), PIDGIN_STATUS_BOX_TYPE_SAVED, NULL, _("Saved statuses..."), NULL, NULL);
 
-		status_menu_refresh_iter(status_box);
+		status_menu_refresh_iter(status_box, status_changed);
 		pidgin_status_box_refresh(status_box);
 
 	} else {
@@ -1156,7 +1167,7 @@
 			update_to_reflect_account_status(status_box, status_box->account,
 							purple_account_get_active_status(status_box->account));
 		else {
-			status_menu_refresh_iter(status_box);
+			status_menu_refresh_iter(status_box, TRUE);
 			pidgin_status_box_refresh(status_box);
 		}
 		return TRUE;
@@ -1229,7 +1240,7 @@
 
 	/* Regenerate the list if it has changed */
 	if (initial_token_acct != status_box->token_status_account) {
-		pidgin_status_box_regenerate(status_box);
+		pidgin_status_box_regenerate(status_box, TRUE);
 	}
 
 }
@@ -1238,13 +1249,14 @@
 current_savedstatus_changed_cb(PurpleSavedStatus *now, PurpleSavedStatus *old, PidginStatusBox *status_box)
 {
 	/* Make sure our current status is added to the list of popular statuses */
-	pidgin_status_box_regenerate(status_box);
+	pidgin_status_box_regenerate(status_box, TRUE);
 }
 
 static void
 saved_status_updated_cb(PurpleSavedStatus *status, PidginStatusBox *status_box)
 {
-	pidgin_status_box_regenerate(status_box);
+	pidgin_status_box_regenerate(status_box,
+		purple_savedstatus_get_current() == status);
 }
 
 static void
@@ -1919,7 +1931,7 @@
 	status_box->token_status_account = check_active_accounts_for_identical_statuses();
 
 	cache_pixbufs(status_box);
-	pidgin_status_box_regenerate(status_box);
+	pidgin_status_box_regenerate(status_box, TRUE);
 
 	purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed",
 						status_box,
@@ -2324,18 +2336,6 @@
 	pidgin_status_box_refresh(status_box);
 }
 
-static gboolean
-message_changed(const char *one, const char *two)
-{
-	if (one == NULL && two == NULL)
-		return FALSE;
-
-	if (one == NULL || two == NULL)
-		return TRUE;
-
-	return (g_utf8_collate(one, two) != 0);
-}
-
 static void
 activate_currently_selected_status(PidginStatusBox *status_box)
 {
@@ -2386,6 +2386,7 @@
 
 	if (status_box->account == NULL) {
 		PurpleStatusType *acct_status_type = NULL;
+		const char *id = NULL; /* id of acct_status_type */
 		PurpleStatusPrimitive primitive = GPOINTER_TO_INT(data);
 		/* Global */
 		/* Save the newly selected status to prefs.xml and status.xml */
@@ -2394,7 +2395,6 @@
 		if (status_box->token_status_account) {
 			gint active;
 			PurpleStatus *status;
-			const char *id = NULL;
 			GtkTreePath *path = gtk_tree_row_reference_get_path(status_box->active_row);
 			active = gtk_tree_path_get_indices(path)[0];
 
@@ -2402,37 +2402,35 @@
 
 			status = purple_account_get_active_status(status_box->token_status_account);
 
-			 acct_status_type = find_status_type_by_index(status_box->token_status_account, active);
+			acct_status_type = find_status_type_by_index(status_box->token_status_account, active);
 			id = purple_status_type_get_id(acct_status_type);
 
-			if (strncmp(id, purple_status_get_id(status), strlen(id)) == 0)
+			if (g_str_equal(id, purple_status_get_id(status)) &&
+				purple_strequal(message, purple_status_get_attr_string(status, "message")))
 			{
 				/* Selected status and previous status is the same */
-				if (!message_changed(message, purple_status_get_attr_string(status, "message")))
-				{
-					PurpleSavedStatus *ss = purple_savedstatus_get_current();
-					/* Make sure that statusbox displays the correct thing.
-					 * It can get messed up if the previous selection was a
-					 * saved status that wasn't supported by this account */
-					if ((purple_savedstatus_get_type(ss) == primitive)
-							&& purple_savedstatus_is_transient(ss)
-							&& purple_savedstatus_has_substatuses(ss))
-						changed = FALSE;
-				}
+				PurpleSavedStatus *ss = purple_savedstatus_get_current();
+				/* Make sure that statusbox displays the correct thing.
+				 * It can get messed up if the previous selection was a
+				 * saved status that wasn't supported by this account */
+				if ((purple_savedstatus_get_type(ss) == primitive)
+					&& purple_savedstatus_is_transient(ss)
+					&& purple_savedstatus_has_substatuses(ss))
+					changed = FALSE;
 			}
 		} else {
 			saved_status = purple_savedstatus_get_current();
 			if (purple_savedstatus_get_type(saved_status) == primitive &&
-			    !purple_savedstatus_has_substatuses(saved_status))
+			    !purple_savedstatus_has_substatuses(saved_status) &&
+				purple_strequal(purple_savedstatus_get_message(saved_status), message))
 			{
-				if (!message_changed(purple_savedstatus_get_message(saved_status), message))
-					changed = FALSE;
+				changed = FALSE;
 			}
 		}
 
 		if (changed)
 		{
-			/* Manually find the appropriate transient acct */
+			/* Manually find the appropriate transient status */
 			if (status_box->token_status_account) {
 				GList *iter = purple_savedstatuses_get_all();
 				GList *tmp, *active_accts = purple_accounts_get_all_active();
@@ -2440,27 +2438,31 @@
 				for (; iter != NULL; iter = iter->next) {
 					PurpleSavedStatus *ss = iter->data;
 					const char *ss_msg = purple_savedstatus_get_message(ss);
+					/* find a known transient status that is the same as the
+					 * new selected one */
 					if ((purple_savedstatus_get_type(ss) == primitive) && purple_savedstatus_is_transient(ss) &&
 						purple_savedstatus_has_substatuses(ss) && /* Must have substatuses */
-						!message_changed(ss_msg, message))
+						purple_strequal(ss_msg, message))
 					{
 						gboolean found = FALSE;
-						/* The currently enabled accounts must have substatuses for all the active accts */
+						/* this status must have substatuses for all the active accts */
 						for(tmp = active_accts; tmp != NULL; tmp = tmp->next) {
 							PurpleAccount *acct = tmp->data;
 							PurpleSavedStatusSub *sub = purple_savedstatus_get_substatus(ss, acct);
 							if (sub) {
 								const PurpleStatusType *sub_type = purple_savedstatus_substatus_get_type(sub);
 								const char *subtype_status_id = purple_status_type_get_id(sub_type);
-								if (subtype_status_id && !strcmp(subtype_status_id,
-										purple_status_type_get_id(acct_status_type)))
+								if (purple_strequal(subtype_status_id, id)) {
 									found = TRUE;
+									break;
+								}
 							}
 						}
-						if (!found)
-							continue;
-						saved_status = ss;
-						break;
+
+						if (found) {
+							saved_status = ss;
+							break;
+						}
 					}
 				}
 
@@ -2503,11 +2505,11 @@
 		status_type = find_status_type_by_index(status_box->account, active);
 		id = purple_status_type_get_id(status_type);
 
-		if (strncmp(id, purple_status_get_id(status), strlen(id)) == 0)
+		if (g_str_equal(id, purple_status_get_id(status)) &&
+			purple_strequal(message, purple_status_get_attr_string(status, "message")))
 		{
 			/* Selected status and previous status is the same */
-			if (!message_changed(message, purple_status_get_attr_string(status, "message")))
-				changed = FALSE;
+			changed = FALSE;
 		}
 
 		if (changed)
@@ -2597,7 +2599,7 @@
 	if (status_box->typing == 0)
 	{
 		/* Nothing has changed, so we don't need to do anything */
-		status_menu_refresh_iter(status_box);
+		status_menu_refresh_iter(status_box, FALSE);
 		return;
 	}
 
@@ -2655,14 +2657,14 @@
 			pidgin_status_editor_show(FALSE,
 				purple_savedstatus_is_transient(saved_status)
 					? saved_status : NULL);
-			status_menu_refresh_iter(status_box);
+			status_menu_refresh_iter(status_box, FALSE);
 			return;
 		}
 
 		if (type == PIDGIN_STATUS_BOX_TYPE_SAVED)
 		{
 			pidgin_status_window_show();
-			status_menu_refresh_iter(status_box);
+			status_menu_refresh_iter(status_box, FALSE);
 			return;
 		}
 	}
--- a/pidgin/gtkutils.c	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/gtkutils.c	Mon Nov 09 01:42:24 2009 +0000
@@ -75,7 +75,7 @@
 } AopMenu;
 
 static guint accels_save_timer = 0;
-static GList *gnome_url_handlers = NULL;
+static GSList *registered_url_handlers = NULL;
 
 static gboolean
 url_clicked_idle_cb(gpointer data)
@@ -3890,7 +3890,7 @@
 				start += sizeof("/desktop/gnome/url-handlers/") - 1;
 
 				protocol = g_strdup_printf("%s:", start);
-				gnome_url_handlers = g_list_prepend(gnome_url_handlers, protocol);
+				registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
 				gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
 			}
 			start = c + 1;
@@ -3898,9 +3898,45 @@
 	}
 	g_free(tmp);
 
-	return (gnome_url_handlers != NULL);
+	return (registered_url_handlers != NULL);
 }
 
+#ifdef _WIN32
+static void
+winpidgin_register_win32_url_handlers(void)
+{
+	int idx = 0;
+	LONG ret = ERROR_SUCCESS;
+
+	do {
+		DWORD nameSize = 256;
+		char start[256];
+		/* I don't think we need to worry about non-ASCII protocol names */
+		ret = RegEnumKeyExA(HKEY_CLASSES_ROOT, idx++, start, &nameSize,
+							NULL, NULL, NULL, NULL);
+		if (ret == ERROR_SUCCESS) {
+			HKEY reg_key = NULL;
+			ret = RegOpenKeyExA(HKEY_CLASSES_ROOT, start, 0, KEY_READ, &reg_key);
+			if (ret == ERROR_SUCCESS) {
+				ret = RegQueryValueExA(reg_key, "URL Protocol", NULL, NULL, NULL, NULL);
+				if (ret == ERROR_SUCCESS) {
+					gchar *protocol = g_strdup_printf("%s:", start);
+					registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
+					/* We still pass everything to the "http" "open" handler for security reasons */
+					gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
+				}
+				RegCloseKey(reg_key);
+			}
+			ret = ERROR_SUCCESS;
+		}
+	} while (ret == ERROR_SUCCESS);
+
+	if (ret != ERROR_NO_MORE_ITEMS)
+		purple_debug_error("winpidgin", "Error iterating HKEY_CLASSES_ROOT subkeys: %ld\n",
+						   ret);
+}
+#endif
+
 void pidgin_utils_init(void)
 {
 	gtk_imhtml_class_register_protocol("http://", url_clicked_cb, link_context_menu);
@@ -3918,6 +3954,11 @@
 	/* If we're under GNOME, try registering the system URL handlers. */
 	if (purple_running_gnome())
 		register_gnome_url_handlers();
+
+#ifdef _WIN32
+	winpidgin_register_win32_url_handlers();
+#endif
+
 }
 
 void pidgin_utils_uninit(void)
@@ -3925,16 +3966,16 @@
 	gtk_imhtml_class_register_protocol("open://", NULL, NULL);
 
 	/* If we have GNOME handlers registered, unregister them. */
-	if (gnome_url_handlers)
+	if (registered_url_handlers)
 	{
-		GList *l;
-		for (l = gnome_url_handlers ; l ; l = l->next)
+		GSList *l;
+		for (l = registered_url_handlers; l; l = l->next)
 		{
 			gtk_imhtml_class_register_protocol((char *)l->data, NULL, NULL);
 			g_free(l->data);
 		}
-		g_list_free(gnome_url_handlers);
-		gnome_url_handlers = NULL;
+		g_slist_free(registered_url_handlers);
+		registered_url_handlers = NULL;
 		return;
 	}
 
--- a/pidgin/pixmaps/Makefile.am	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/pixmaps/Makefile.am	Mon Nov 09 01:42:24 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	Mon Nov 09 01:42:24 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	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/plugins/disco/gtkdisco.c	Mon Nov 09 01:42:24 2009 +0000
@@ -141,8 +141,18 @@
 static void dialog_select_account_cb(GObject *w, PurpleAccount *account,
                                      PidginDiscoDialog *dialog)
 {
+	gboolean change = (account != dialog->account);
 	dialog->account = account;
 	gtk_widget_set_sensitive(dialog->browse_button, account != NULL);
+
+	if (change && dialog->discolist) {
+		if (dialog->discolist->tree) {
+			gtk_widget_destroy(dialog->discolist->tree);
+			dialog->discolist->tree = NULL;
+		}
+		pidgin_disco_list_unref(dialog->discolist);
+		dialog->discolist = NULL;
+	}
 }
 
 static void register_button_cb(GtkWidget *unused, PidginDiscoDialog *dialog)
@@ -152,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) {
@@ -226,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),
@@ -380,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	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/plugins/disco/gtkdisco.h	Mon Nov 09 01:42:24 2009 +0000
@@ -43,6 +43,8 @@
 
 	PurpleAccount *account;
 	PidginDiscoList *discolist;
+
+	gpointer *prompt_handle;
 };
 
 struct _PidginDiscoList {
--- a/pidgin/plugins/perl/common/Makefile.mingw	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/plugins/perl/common/Makefile.mingw	Mon Nov 09 01:42:24 2009 +0000
@@ -5,9 +5,12 @@
 #
 
 PIDGIN_TREE_TOP := ../../../..
-GCCWARNINGS := -Wno-comment -Waggregate-return -Wcast-align -Wdeclaration-after-statement -Werror-implicit-function-declaration -Wextra -Wno-sign-compare -Wno-unused-parameter -Winit-self -Wmissing-declarations -Wmissing-prototypes -Wpointer-arith -Wundef -Wno-unused
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
+GCCWARNINGS += -Wno-comment -Wno-unused -Wno-nested-externs
+
+DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES))
+
 TARGET = Pidgin
 EXTUTILS ?= C:/perl/lib/ExtUtils
 
--- a/pidgin/plugins/win32/winprefs/Makefile.mingw	Sun Nov 08 01:12:44 2009 +0000
+++ b/pidgin/plugins/win32/winprefs/Makefile.mingw	Mon Nov 09 01:42:24 2009 +0000
@@ -8,6 +8,7 @@
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
 TARGET = winprefs
+DEFINES := $(subst -DWIN32_LEAN_AND_MEAN,,$(DEFINES))
 DEFINES += -DWINVER=0x500
 
 ##
--- a/po/ChangeLog	Sun Nov 08 01:12:44 2009 +0000
+++ b/po/ChangeLog	Mon Nov 09 01:42:24 2009 +0000
@@ -1,7 +1,10 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.6.4
+	* Vietnamese translation updated (Clytie Siddall)
+
 version 2.6.3
-	* Vietnamese translation updated (Clytie Siddall)
+	* No changes
 
 version 2.6.2
 	* Afrikaans translation updated (Friedel Wolff)
--- a/po/POTFILES.in	Sun Nov 08 01:12:44 2009 +0000
+++ b/po/POTFILES.in	Mon Nov 09 01:42:24 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
--- a/po/ca.po	Sun Nov 08 01:12:44 2009 +0000
+++ b/po/ca.po	Mon Nov 09 01:42:24 2009 +0000
@@ -33,8 +33,8 @@
 msgstr ""
 "Project-Id-Version: Pidgin\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-09-07 18:26-0700\n"
-"PO-Revision-Date: 2009-07-26 17:23+0200\n"
+"POT-Creation-Date: 2009-10-25 17:57+0100\n"
+"PO-Revision-Date: 2009-10-26 09:20+0100\n"
 "Last-Translator: Josep Puigdemont i Casamajó <josep.puigdemont@gmail.com>\n"
 "Language-Team: Catalan <tradgnome@softcatala.net>\n"
 "MIME-Version: 1.0\n"
@@ -576,13 +576,6 @@
 msgid "Re-enable Account"
 msgstr "Rehabilita el compte"
 
-msgid ""
-"The account has disconnected and you are no longer in this chat. You will be "
-"automatically rejoined in the chat when the account reconnects."
-msgstr ""
-"El compte s'ha desconnectat i ja no sou al xat. Quan es torni a connectar el "
-"compte entrareu de nou automàticament al xat."
-
 msgid "No such command."
 msgstr "No existeix l'ordre."
 
@@ -625,6 +618,13 @@
 msgid "You have left this chat."
 msgstr "Heu sortit d'aquest xat"
 
+msgid ""
+"The account has disconnected and you are no longer in this chat. You will be "
+"automatically rejoined in the chat when the account reconnects."
+msgstr ""
+"El compte s'ha desconnectat i ja no sou al xat. Quan es torni a connectar el "
+"compte entrareu de nou automàticament al xat."
+
 msgid "Logging started. Future messages in this conversation will be logged."
 msgstr ""
 "S'ha iniciat el registre. Es registraran els propers missatges d'aquesta "
@@ -661,6 +661,9 @@
 msgid "Enable Sounds"
 msgstr "Habilita els sons"
 
+msgid "You are not connected."
+msgstr "No esteu connectat."
+
 msgid "<AUTO-REPLY> "
 msgstr "<RESPOSTA-AUTOMÀTICA> "
 
@@ -670,8 +673,8 @@
 msgstr[0] "Llista d'%d usuari:\n"
 msgstr[1] "Llista de %d usuaris:\n"
 
-msgid "Supported debug options are:  version"
-msgstr "Les opcions de depuració disponibles són:  version"
+msgid "Supported debug options are: plugins version"
+msgstr "Les opcions de depuració disponibles són:  plugins version"
 
 msgid "No such command (in this context)."
 msgstr "L'ordre no existeix (en aquest context)."
@@ -977,6 +980,9 @@
 msgid "(none)"
 msgstr "(cap)"
 
+#. XXX: The following expects that finch_notify_message gets called. This
+#. * may not always happen, e.g. when another plugin sets its own
+#. * notify_message. So tread carefully.
 msgid "URI"
 msgstr "URI"
 
@@ -1554,9 +1560,15 @@
 "\n"
 "S'està aconseguint un TinyURL..."
 
-#, fuzzy
+#, c-format
+msgid "TinyURL for above: %s"
+msgstr "Fes un TinyURL d'això d'aquí dalt: %s"
+
+msgid "Please wait while TinyURL fetches a shorter URL ..."
+msgstr "Espreu mentre TinyURL obté una URL més curta..."
+
 msgid "Only create TinyURL for URLs of this length or greater"
-msgstr "Crea TinyURL per a URL així de llargues o més"
+msgstr "Només crea TinyURL per a URL així de llargues o més"
 
 msgid "TinyURL (or other) address prefix"
 msgstr "Prefix de l'adreça TinyURL (o altra)"
@@ -1567,10 +1579,9 @@
 msgid "TinyURL plugin"
 msgstr "Connector TinyURL"
 
-#, fuzzy
 msgid "When receiving a message with URL(s), use TinyURL for easier copying"
 msgstr ""
-"Quan rebeu missagtes amb URL, feu servir TinyURL per a copiar més fàcilment"
+"En rebre missagtes amb URL, s'empra TinyURL perquè sigui més fàcil copiar"
 
 msgid "Online"
 msgstr "En línia"
@@ -1676,27 +1687,25 @@
 msgid "buddy list"
 msgstr "llista d'amics"
 
-#, fuzzy
 msgid "The certificate is self-signed and cannot be automatically checked."
-msgstr ""
-"No es pot comprovar el certificat que presenta «%s» atès que està auto-signat."
-
-#, fuzzy
-msgid "The root certificate this one claims to be issued by is unknown."
-msgstr "El Pidgin no coneix el certificat arrel d'aquest certificat."
-
-#, fuzzy
+msgstr "No es pot comprovar el certificat atès que està auto-signat."
+
+msgid ""
+"The certificate is not trusted because no certificate that can verify it is "
+"currently trusted."
+msgstr ""
+"No es pot confiar en el certificat atès que no hi hi ha cap altre certificat "
+"de confiança que el pugui verificar."
+
 msgid "The certificate is not valid yet."
-msgstr "La cadena de certificació que presenta %s no és vàlida."
-
-#, fuzzy
+msgstr "El certificat encara no és vàlid."
+
 msgid "The certificate has expired and should not be considered valid."
-msgstr "La cadena de certificació que presenta %s no és vàlida."
+msgstr "El certificat ha expirat i no s'hauria de considerar vàlid."
 
 #. Translators: "domain" refers to a DNS domain (e.g. talk.google.com)
-#, fuzzy
 msgid "The certificate presented is not issued to this domain."
-msgstr "La cadena de certificació que presenta %s no és vàlida."
+msgstr "El certificat que s'ha presentat no ha estat emès per a aquest domini."
 
 msgid ""
 "You have no database of root certificates, so this certificate cannot be "
@@ -1705,17 +1714,14 @@
 "Aquest certificat no es pot validar perquè no teniu cap base de dades de "
 "certificats arrel."
 
-#, fuzzy
 msgid "The certificate chain presented is invalid."
-msgstr "La cadena de certificació que presenta %s no és vàlida."
-
-#, fuzzy
+msgstr "La cadena de certificació que s'ha presentat no és vàlida."
+
 msgid "The certificate has been revoked."
-msgstr "Ha finalitzat la trucada."
-
-#, fuzzy
+msgstr "El certificat ha estat revocat."
+
 msgid "An unknown certificate error occurred."
-msgstr "Hi ha hagut un error de connexió desconegut: %s."
+msgstr "S'ha produït un error desconegut en el certificat."
 
 msgid "(DOES NOT MATCH)"
 msgstr "(NO COINCIDEIX)"
@@ -1760,26 +1766,25 @@
 msgid "_View Certificate..."
 msgstr "_Mostra el certificat..."
 
-#, fuzzy, c-format
+#, c-format
 msgid "The certificate for %s could not be validated."
-msgstr "La cadena de certificació que presenta %s no és vàlida."
+msgstr "No s'ha pogut validar el certificat de %s."
 
 # Títol de finestra (josep)
 #. TODO: Probably wrong.
 msgid "SSL Certificate Error"
 msgstr "Error en el certificat SSL"
 
-#, fuzzy
 msgid "Unable to validate certificate"
-msgstr "No s'ha pogut autenticar: %s"
-
-#, fuzzy, c-format
+msgstr "No s'ha pogut certificar"
+
+#, c-format
 msgid ""
 "The certificate claims to be from \"%s\" instead. This could mean that you "
 "are not connecting to the service you believe you are."
 msgstr ""
-"El certificat de «%s» sembla indicar que és de «%s». Això podria voler dir que "
-"us esteu connectant a un servei diferent del que us penseu."
+"El certificat indica que és de «%s». Això podria voler dir que us esteu "
+"connectant a un servei diferent del que us penseu."
 
 #. Make messages
 #, c-format
@@ -1927,6 +1932,10 @@
 msgstr "El procés resoledor ha acabat sense respondre la nostra sol·licitud"
 
 #, c-format
+msgid "Error converting %s to punycode: %d"
+msgstr "S'ha produït un error en convertir %s a punycode: %d"
+
+#, c-format
 msgid "Thread creation failure: %s"
 msgstr "S'ha produït un error en crear un fil: %s"
 
@@ -2020,18 +2029,18 @@
 msgid "File transfer complete"
 msgstr "S'ha completat la transferència del fitxer"
 
-#, fuzzy, c-format
+#, c-format
 msgid "You cancelled the transfer of %s"
 msgstr "Heu cancel·lat la transferència de %s"
 
 msgid "File transfer cancelled"
 msgstr "S'ha cancel·lat la transferència del fitxer"
 
-#, fuzzy, c-format
+#, c-format
 msgid "%s cancelled the transfer of %s"
 msgstr "%s ha cancel·lat la transferència de %s"
 
-#, fuzzy, c-format
+#, c-format
 msgid "%s cancelled the file transfer"
 msgstr "%s ha cancel·lat la transferència del fitxer"
 
@@ -2224,28 +2233,30 @@
 "No codecs found. Install some GStreamer codecs found in GStreamer plugins "
 "packages."
 msgstr ""
+"No s'ha trobat cap còdec. Instal·leu els còdecs del GStreamer que podeu "
+"trobar en els paquests de connectors del GStreamer."
 
 msgid ""
 "No codecs left. Your codec preferences in fs-codecs.conf are too strict."
 msgstr ""
-
-#, fuzzy
+"No hi ha cap més còdec. Les preferències dels còdecs al fitxer fs-codecs."
+"conf són massa estrictes."
+
 msgid "A non-recoverable Farsight2 error has occurred."
-msgstr "Hi ha hagut un error de connexió desconegut: %s."
-
-#, fuzzy
-msgid "Conference error."
-msgstr "Conferència tancada"
-
-msgid "Error with your microphone."
-msgstr ""
-
-msgid "Error with your webcam."
-msgstr ""
-
-#, fuzzy, c-format
+msgstr "S'ha produït un error no recuperable del Farsight2."
+
+msgid "Conference error"
+msgstr "Error en la conferència"
+
+msgid "Error with your microphone"
+msgstr "S'ha produït un error amb el micròfon"
+
+msgid "Error with your webcam"
+msgstr "S'ha produït un error amb la càmera web"
+
+#, c-format
 msgid "Error creating session: %s"
-msgstr "S'ha produït un error en crear la connexió"
+msgstr "S'ha produït un error en crear la sessió: %s"
 
 msgid "Error creating conference."
 msgstr "S'ha produït un error en crear la conferència."
@@ -2511,16 +2522,15 @@
 msgid "Test plugin IPC support, as a server. This registers the IPC commands."
 msgstr "Connector de proves per a servidor d'IPC, que registra les ordres IPC."
 
-#, fuzzy
 msgid "Hide Joins/Parts"
-msgstr "Oculta els errors en entrar"
+msgstr "Oculta en entrar/sortir"
 
 #. Translators: Followed by an input request a number of people
 msgid "For rooms with more than this many people"
-msgstr ""
+msgstr "Per sales amb més persones que"
 
 msgid "If user has not spoken in this many minutes"
-msgstr ""
+msgstr "Si l'usuari no ha parlat en"
 
 msgid "Apply hiding rules to buddies"
 msgstr "Aplica les normes d'ocultació als amics"
@@ -3973,11 +3983,11 @@
 msgid "Logo"
 msgstr "Logotip"
 
-#, fuzzy, c-format
+#, c-format
 msgid ""
 "%s will no longer be able to see your status updates.  Do you want to "
 "continue?"
-msgstr "Esteu segur que voleu suprimir %s de la llista d'amics?"
+msgstr "%s no podrà veure l'actualització del vostre estat. Voleu continuar?"
 
 msgid "Cancel Presence Notification"
 msgstr "Cancel·la la notificació de presència"
@@ -3996,6 +4006,9 @@
 msgid "Unsubscribe"
 msgstr "Cancel·la la subscripció"
 
+msgid "Initiate _Chat"
+msgstr "Inicia un _xat"
+
 msgid "Log In"
 msgstr "Connecta"
 
@@ -4580,7 +4593,7 @@
 msgid "configure:  Configure a chat room."
 msgstr "configure:  configura la sala de xat."
 
-msgid "part [room]:  Leave the room."
+msgid "part [message]:  Leave the room."
 msgstr "part [sala]:  surt de la sala."
 
 msgid "register:  Register with a chat room."
@@ -4599,13 +4612,12 @@
 "affiliate &lt;owner|admin|member|outcast|none&gt; [sobrenom1] "
 "[sobrenom2] ...: obtén els usuaris amb una afiliació, o els l'estableix."
 
-#, fuzzy
 msgid ""
 "role &lt;moderator|participant|visitor|none&gt; [nick1] [nick2] ...: Get the "
 "users with a role or set users' role with the room."
 msgstr ""
-"role &lt;usuari&gt; &lt;moderator|participant|visitor|none&gt; [sobrenom1] "
-"[sobrenom2] ...: obtén els usuaris amb el rol especificat, o els l'estableix."
+"role &lt;moderator|participant|visitor|none&gt; [sobrenom1] [sobrenom2] ...: "
+"obtén els usuaris amb el rol especificat, o els l'estableix."
 
 msgid "invite &lt;user&gt; [message]:  Invite a user to the room."
 msgstr "invite &lt;usuari&gt; [sala]:  convida un usuari a la sala."
@@ -5025,7 +5037,6 @@
 msgid "Not expected"
 msgstr "Inesperat"
 
-#, fuzzy
 msgid "Friendly name is changing too rapidly"
 msgstr "El nom amistós canvia massa de pressa"
 
@@ -5245,19 +5256,16 @@
 msgid "Send to Mobile"
 msgstr "Envia a un mòbil"
 
-msgid "Initiate _Chat"
-msgstr "Inicia un _xat"
-
 msgid "SSL support is needed for MSN. Please install a supported SSL library."
 msgstr "L'MSN necessita SSL, instal·leu alguna biblioteca d'SSL permesa."
 
 #, c-format
 msgid ""
 "Unable to add the buddy %s because the username is invalid.  Usernames must "
-"be a valid email address."
+"be valid email addresses."
 msgstr ""
 "No s'ha pogut afegir l'amic %s perquè el nom d'usuari no és vàlid. Els noms "
-"d'usuari han de ser adreces de correu vàlides."
+"d'usuari han de ser adreces de correu electròniques vàlides."
 
 msgid "Unable to Add"
 msgstr "No s'ha pogut afegir"
@@ -5597,10 +5605,10 @@
 "%s ha sol·licitat poder veure la vostra càmera web, però això encara no està "
 "implementat."
 
-#, fuzzy, c-format
+#, c-format
 msgid "%s invited you to view his/her webcam, but this is not yet supported."
 msgstr ""
-"%s ha sol·licitat poder veure la vostra càmera web, però això encara no està "
+"%s us ha convidat a veure la seva càmera web, però això encara no està "
 "implementat."
 
 msgid "Away From Computer"
@@ -6354,9 +6362,9 @@
 msgstr "Port en el servidor"
 
 #. Note to translators: %s in this string is a URL
-#, fuzzy, c-format
+#, c-format
 msgid "Received unexpected response from %s"
-msgstr "S'ha rebut una resposta inesperada de "
+msgstr "S'ha rebut una resposta inesperada de %s"
 
 #. username connecting too frequently
 msgid ""
@@ -6369,9 +6377,9 @@
 
 #. Note to translators: The first %s is a URL, the second is an
 #. error message.
-#, fuzzy, c-format
+#, c-format
 msgid "Error requesting %s: %s"
-msgstr "S'ha produït en sol·licitar "
+msgstr "S'ha produït un error en sol·licitar %s: %s"
 
 msgid "AOL does not allow your screen name to authenticate here"
 msgstr "AOL no permet que us autentiqueu amb aquest nom d'usuari aquí"
@@ -7149,9 +7157,8 @@
 msgid "C_onnect"
 msgstr "C_onnecta"
 
-#, fuzzy
 msgid "You closed the connection."
-msgstr "El servidor ha tancat la connexió"
+msgstr "Heu tancat la connexió."
 
 msgid "Get AIM Info"
 msgstr "Obtén informació de AIM"
@@ -7163,9 +7170,8 @@
 msgid "Get Status Msg"
 msgstr "Aconsegueix el missatge d'estat"
 
-#, fuzzy
 msgid "End Direct IM Session"
-msgstr "S'ha establert una connexió directa de MI"
+msgstr "Finalitzar la sessió de MI directa"
 
 msgid "Direct IM"
 msgstr "MI directa"
@@ -8007,7 +8013,7 @@
 msgid "File Send"
 msgstr "S'ha enviat el fitxer"
 
-#, fuzzy, c-format
+#, c-format
 msgid "%d cancelled the transfer of %s"
 msgstr "%d ha cancel·lat la transferència de %s"
 
@@ -9545,7 +9551,7 @@
 msgstr "Bloca invitacions a conferències i sales de xat"
 
 msgid "Use account proxy for SSL connections"
-msgstr ""
+msgstr "Empra un compte per al servidor intermediàri per a connexions SSL"
 
 msgid "Chat room list URL"
 msgstr "URL de la llista de sales de xat"
@@ -9653,26 +9659,26 @@
 msgid "Ignore buddy?"
 msgstr "Voleu ignorar l'amic?"
 
-#, fuzzy
 msgid "Invalid username or password"
-msgstr "El sobrenom o la contrasenya no són correctes"
-
-#, fuzzy
+msgstr "El sobrenom o la contrasenya no són vàlides"
+
 msgid ""
 "Your account has been locked due to too many failed login attempts.  Please "
 "try logging into the Yahoo! website."
 msgstr ""
-"El compte està blocat perquè s'ha intentat entrar massa cops. Això es pot "
-"solucionar entrant al web de Yahoo!"
+"S'ha blocat el vostre compte perquè s'ha intentat entrar massa cops. Entreu "
+"al web de Yahoo! per solucionar això."
 
 #, c-format
 msgid "Unknown error 52.  Reconnecting should fix this."
-msgstr ""
+msgstr "Error desconegut 52. Es pot sol·lucionar connectant de nou."
 
 msgid ""
 "Error 1013: The username you have entered is invalid.  The most common cause "
 "of this error is entering your email address instead of your Yahoo! ID."
 msgstr ""
+"Error 1013: el nom d'usuari no és vàlid. Pot ser que hagueu introduït la "
+"vostra adreça de correu en lloc del nom d'usuari de Yahoo!"
 
 #, c-format
 msgid "Unknown error number %d. Logging into the Yahoo! website may fix this."
@@ -9764,6 +9770,16 @@
 msgid "Open Inbox"
 msgstr "Obre la safata d'entrada"
 
+msgid "Can't send SMS. Unable to obtain mobile carrier."
+msgstr ""
+"No es poden enviar SMS, no s'ha pogut obtenir l'operador de telefonia mòbil."
+
+msgid "Can't send SMS. Unknown mobile carrier."
+msgstr "No es poden enviar SMS, no es coneix l'operador de telefona mòbil."
+
+msgid "Getting mobile carrier to send the SMS."
+msgstr "S'està obtenint l'operador de telefonia mòbil per a poder enviar SMS."
+
 #. Write a local message to this conversation showing that a request for a
 #. * Doodle session has been made
 #.
@@ -10456,7 +10472,6 @@
 msgid "Layout"
 msgstr "Format"
 
-#, fuzzy
 msgid "The layout of icons, name, and status of the buddy list"
 msgstr "El format de les icones, el nom, i l'estat de la llista d'amics"
 
@@ -10513,9 +10528,8 @@
 
 #. Note to translators: These two strings refer to the font and color
 #. of a buddy list buddy when it is online
-#, fuzzy
 msgid "Online Text"
-msgstr "Text en línia"
+msgstr "Text en estar en línia"
 
 msgid "The text information for when a buddy is online"
 msgstr "Text informatiu per quan un amic estigui en línia"
@@ -10523,18 +10537,16 @@
 #. Note to translators: These two strings refer to the font and color
 #. of a buddy list buddy when it is away
 msgid "Away Text"
-msgstr "Text d'absència"
+msgstr "Text en estar absent"
 
 msgid "The text information for when a buddy is away"
 msgstr "Text informatiu per quan un amic estigui absent"
 
 #. Note to translators: These two strings refer to the font and color
 #. of a buddy list buddy when it is offline
-#, fuzzy
 msgid "Offline Text"
-msgstr "Text de fora de línia"
-
-#, fuzzy
+msgstr "Text fora de línia"
+
 msgid "The text information for when a buddy is offline"
 msgstr "Text informatiu per quan un amic estigui fora de línia"
 
@@ -10559,7 +10571,6 @@
 msgid "Message (Nick Said) Text"
 msgstr "Text del missatge (on s'hi ha dit el sobrenom)"
 
-#, fuzzy
 msgid ""
 "The text information for when a chat has an unread message that mentions "
 "your nickname"
@@ -11005,9 +11016,8 @@
 msgid "_Group:"
 msgstr "_Grup:"
 
-#, fuzzy
 msgid "Auto_join when account connects."
-msgstr "_Entra automàticament quant el compte estigui connectat."
+msgstr "_Entra automàticament quant es connecti el compte."
 
 msgid "_Remain in chat after window is closed."
 msgstr "Co_ntinua al xat quan la finestra es tanqui."
@@ -11121,9 +11131,8 @@
 msgid "/Conversation/New Instant _Message..."
 msgstr "/Conversa/_Missatge instantani nou..."
 
-#, fuzzy
 msgid "/Conversation/Join a _Chat..."
-msgstr "/Conversa/Con_vida..."
+msgstr "/Conversa/Entra a un _xat..."
 
 msgid "/Conversation/_Find..."
 msgstr "/Conversa/_Cerca..."
@@ -11514,7 +11523,7 @@
 msgstr "Estonià"
 
 msgid "Basque"
-msgstr ""
+msgstr "Basc"
 
 msgid "Persian"
 msgstr "Persa"
@@ -11728,6 +11737,13 @@
 "primary language is <b>English</b>.  You are welcome to post in another "
 "language, but the responses may be less helpful.<br/><br/>"
 msgstr ""
+"<font size=\"4\">Ajuda d'altres usuaris del Pidgin:</font> <a href=\"mailto:"
+"support@pidgin.im\">support@pidgin.im</a><br/>Aquesta és una llista de "
+"correu <b>pública</b>. (<a href=\"http://pidgin.im/pipermail/support/"
+"\">arxiu</a>)<br/>No us podem ajudar amb connectors d'altres proveïdors.<br/"
+">En aquesta llista s'hi empra principalment l'<b>anglès</b>.  Podeu escriure-"
+"hi en un altre idioma, però és possible que les respostes no siguin de gaire "
+"ajuda.<br/><br/>"
 
 #, c-format
 msgid ""
@@ -12299,45 +12315,48 @@
 "Usage: %s [OPTION]...\n"
 "\n"
 msgstr ""
-
-#, fuzzy
+"Forma d'ús: %s [OPCIÓ]...\n"
+"\n"
+
 msgid "DIR"
-msgstr "IRC"
+msgstr "DIR"
 
 msgid "use DIR for config files"
-msgstr ""
+msgstr "empra DIR per a fitxers de configuració"
 
 msgid "print debugging messages to stdout"
-msgstr ""
+msgstr "escriu missatges de depuració a la sortida estàndard"
 
 msgid "force online, regardless of network status"
-msgstr ""
+msgstr "força estar en línia, independentment de l'estat de la xarxa"
 
 msgid "display this help and exit"
-msgstr ""
+msgstr "mostra aquesta ajuda i surt"
 
 # FIXME: entrades/registres?
-#, fuzzy
 msgid "allow multiple instances"
-msgstr "Permet diverses entrades simultànies"
+msgstr "permet diverses instàncies"
 
 msgid "don't automatically login"
-msgstr ""
+msgstr "no entra als comptes"
 
 msgid "NAME"
-msgstr ""
+msgstr "NOM"
 
 msgid ""
 "enable specified account(s) (optional argument NAME\n"
 "                      specifies account(s) to use, separated by commas.\n"
 "                      Without this only the first account will be enabled)."
 msgstr ""
+"habilita els comptes especificats (l'argument opcional NAME especifica\n"
+"                      els comptes a emprar, separats per comes. Sense això\n"
+"                      només s'habilitarà el primer compte)."
 
 msgid "X display to use"
-msgstr ""
+msgstr "pantalla d'X a emprar"
 
 msgid "display the current version and exit"
-msgstr ""
+msgstr "mostra la versió actual i surt"
 
 # FIXME: backtrace -> traça (bug-buddy) ?
 #, c-format
@@ -12394,7 +12413,7 @@
 msgstr "%s vol iniciar una sessió de vídeo."
 
 msgid "Incoming Call"
-msgstr ""
+msgstr "Trucada entrant"
 
 msgid "_Pause"
 msgstr "_Pausa"
@@ -12576,50 +12595,54 @@
 msgid "Pounce Target"
 msgstr "Objectiu de l'avís"
 
-#, c-format
 msgid "Started typing"
 msgstr "Hagi començat a escriure"
 
-#, c-format
 msgid "Paused while typing"
 msgstr "S'aturi mentre tecleja"
 
-#, c-format
 msgid "Signed on"
 msgstr "Es connecti"
 
-#, c-format
 msgid "Returned from being idle"
 msgstr "Torna a estar actiu"
 
-#, c-format
 msgid "Returned from being away"
 msgstr "Torni a estar present"
 
-#, c-format
 msgid "Stopped typing"
 msgstr "Pari d'escriure"
 
-#, c-format
 msgid "Signed off"
 msgstr "Es desconnecti"
 
-#, c-format
 msgid "Became idle"
 msgstr "Passi a inactiu"
 
-#, c-format
 msgid "Went away"
 msgstr "En estar absent"
 
-#, c-format
 msgid "Sent a message"
 msgstr "Envia un missatge"
 
-#, c-format
 msgid "Unknown.... Please report this!"
 msgstr "Esdeveniment d'avís desconegut, informeu-nos-en."
 
+msgid "(Custom)"
+msgstr "(Personalitzat)"
+
+msgid "(Default)"
+msgstr "(Predeterminat)"
+
+msgid "The default Pidgin sound theme"
+msgstr "El tema de sons predeterminat del pidgin"
+
+msgid "The default Pidgin buddy list theme"
+msgstr "El tema per a la llista d'amics predeterminat del Pidgin"
+
+msgid "The default Pidgin status icon theme"
+msgstr "El tema de les icones d'estat predeterminat del Pidgin"
+
 msgid "Theme failed to unpack."
 msgstr "No s'ha pogut desempaquetar el tema."
 
@@ -12764,14 +12787,16 @@
 msgid "Cannot start browser configuration program."
 msgstr "No s'ha pogut iniciar el programa de configuració del navegador."
 
-#, fuzzy
 msgid "Disabled"
-msgstr "_Inhabilita"
+msgstr "Inhabilitat"
 
 #, c-format
 msgid "Use _automatically detected IP address: %s"
 msgstr "Empra l'_adreça IP detectada automàticament: %s"
 
+msgid "ST_UN server:"
+msgstr "Servidor ST_UN:"
+
 msgid "<span style=\"italic\">Example: stunserver.org</span>"
 msgstr "<span style=\"italic\">Exemple: stunserver.org</span>"
 
@@ -12797,9 +12822,8 @@
 msgid "Relay Server (TURN)"
 msgstr "Servidor repetidor (TURN)"
 
-#, fuzzy
 msgid "_TURN server:"
-msgstr "Servidor ST_UN:"
+msgstr "Servidor _TURN:"
 
 msgid "Proxy Server &amp; Browser"
 msgstr "Servidor intermediari i navegador"
@@ -14372,35 +14396,29 @@
 "Aquest connector permet a l'usuari personalitzar els formats de les marques "
 "horàries de les converses i dels registres."
 
-#, fuzzy
 msgid "Audio"
-msgstr "Auto"
-
-#, fuzzy
+msgstr "Àudio"
+
 msgid "Video"
 msgstr " Vídeo"
 
 msgid "Output"
-msgstr ""
-
-#, fuzzy
+msgstr "Sortida"
+
 msgid "_Plugin"
-msgstr "Connectors"
-
-#, fuzzy
+msgstr "_Connectors"
+
 msgid "_Device"
-msgstr "Dispositiu"
+msgstr "_Dispositiu"
 
 msgid "Input"
-msgstr ""
-
-#, fuzzy
+msgstr "Entrada"
+
 msgid "P_lugin"
-msgstr "Connectors"
-
-#, fuzzy
+msgstr "C_onnectors"
+
 msgid "D_evice"
-msgstr "Dispositiu"
+msgstr "D_ispositiu"
 
 #. *< magic
 #. *< major version
@@ -14411,18 +14429,19 @@
 #. *< dependencies
 #. *< priority
 #. *< id
-#, fuzzy
 msgid "Voice/Video Settings"
-msgstr "Edita els paràmetres"
+msgstr "Configuració del so/vídeo"
 
 #. *< name
 #. *< version
 msgid "Configure your microphone and webcam."
-msgstr ""
+msgstr "Configureu el micròfon i la càmera web."
 
 #. *< summary
 msgid "Configure microphone and webcam settings for voice/video calls."
 msgstr ""
+"Configureu els paràmetres del micròfon i la càmera web per a trucades de veu/"
+"vídeo."
 
 msgid "Opacity:"
 msgstr "Opacitat:"
@@ -14481,9 +14500,6 @@
 "\n"
 "* Nota: aquest connector requereix Windows 2000 o superior."
 
-msgid "GTK+ Runtime Version"
-msgstr "Versió del mòdul d'execució de GTK+"
-
 #. Autostart
 msgid "Startup"
 msgstr "Inicialització"
@@ -14492,6 +14508,10 @@
 msgid "_Start %s on Windows startup"
 msgstr "_Inicia el %s en iniciar Windows"
 
+# FIXME: entrades/registres?
+msgid "Allow multiple instances"
+msgstr "Permet diverses instàncies"
+
 msgid "_Dockable Buddy List"
 msgstr "Llista _d'amics acoblable"
 
@@ -14553,6 +14573,9 @@
 msgid "This plugin is useful for debbuging XMPP servers or clients."
 msgstr "Aquest connector és útil per a depurar servidors i clients XMPP."
 
+#~ msgid "GTK+ Runtime Version"
+#~ msgstr "Versió del mòdul d'execució de GTK+"
+
 #~ msgid "Calling ... "
 #~ msgstr "S'està trucant..."
 
@@ -14796,9 +14819,6 @@
 #~ msgid "Could not write"
 #~ msgstr "No s'ha pogut escriure"
 
-#~ msgid "Could not connect"
-#~ msgstr "No s'ha pogut connectar"
-
 #~ msgid "Could not create listen socket"
 #~ msgstr "No s'ha pogut crear el sòcol per a escoltar"