im.pidgin.pidgin: 00214fd51f7f68a6949548fb6104c092b283f671

nosnilmot at pidgin.im nosnilmot at pidgin.im
Sun Nov 11 08:10:43 EST 2007


-----------------------------------------------------------------
Revision: 00214fd51f7f68a6949548fb6104c092b283f671
Ancestor: 2949a97655781984e84ce155afbb210804033ea4
Author: nosnilmot at pidgin.im
Date: 2007-11-11T12:57:52
Branch: im.pidgin.pidgin

Added files:
        libpurple/protocols/msnp9/Makefile.am
        libpurple/protocols/msnp9/Makefile.mingw
        libpurple/protocols/msnp9/cmdproc.c
        libpurple/protocols/msnp9/cmdproc.h
        libpurple/protocols/msnp9/command.c
        libpurple/protocols/msnp9/command.h
        libpurple/protocols/msnp9/dialog.c
        libpurple/protocols/msnp9/dialog.h
        libpurple/protocols/msnp9/directconn.c
        libpurple/protocols/msnp9/directconn.h
        libpurple/protocols/msnp9/error.c
        libpurple/protocols/msnp9/error.h
        libpurple/protocols/msnp9/group.c
        libpurple/protocols/msnp9/group.h
        libpurple/protocols/msnp9/history.c
        libpurple/protocols/msnp9/history.h
        libpurple/protocols/msnp9/httpconn.c
        libpurple/protocols/msnp9/httpconn.h
        libpurple/protocols/msnp9/msg.c
        libpurple/protocols/msnp9/msg.h
        libpurple/protocols/msnp9/msn-utils.c
        libpurple/protocols/msnp9/msn-utils.h
        libpurple/protocols/msnp9/msn.c
        libpurple/protocols/msnp9/msn.h
        libpurple/protocols/msnp9/nexus.c
        libpurple/protocols/msnp9/nexus.h
        libpurple/protocols/msnp9/notification.c
        libpurple/protocols/msnp9/notification.h
        libpurple/protocols/msnp9/object.c
        libpurple/protocols/msnp9/object.h
        libpurple/protocols/msnp9/page.c
        libpurple/protocols/msnp9/page.h
        libpurple/protocols/msnp9/servconn.c
        libpurple/protocols/msnp9/servconn.h
        libpurple/protocols/msnp9/session.c
        libpurple/protocols/msnp9/session.h
        libpurple/protocols/msnp9/slp.c
        libpurple/protocols/msnp9/slp.h
        libpurple/protocols/msnp9/slpcall.c
        libpurple/protocols/msnp9/slpcall.h
        libpurple/protocols/msnp9/slplink.c
        libpurple/protocols/msnp9/slplink.h
        libpurple/protocols/msnp9/slpmsg.c
        libpurple/protocols/msnp9/slpmsg.h
        libpurple/protocols/msnp9/slpsession.c
        libpurple/protocols/msnp9/slpsession.h
        libpurple/protocols/msnp9/state.c
        libpurple/protocols/msnp9/state.h
        libpurple/protocols/msnp9/switchboard.c
        libpurple/protocols/msnp9/switchboard.h
        libpurple/protocols/msnp9/sync.c
        libpurple/protocols/msnp9/sync.h
        libpurple/protocols/msnp9/table.c
        libpurple/protocols/msnp9/table.h
        libpurple/protocols/msnp9/transaction.c
        libpurple/protocols/msnp9/transaction.h
        libpurple/protocols/msnp9/user.c
        libpurple/protocols/msnp9/user.h
        libpurple/protocols/msnp9/userlist.c
        libpurple/protocols/msnp9/userlist.h
Added directories:
        libpurple/protocols/msnp9
Modified files:
        configure.ac

ChangeLog: 

Add MSNP9 back as an alternative alongside the existing MSN prpl. Cowardly
old fools like me who prefer the stability of our MSNP9 code over the
features of MSNP14 can enable this using the --disable-msnp14 ./configure
option.

If we want to release from i.p.p and MSN stability is the only blocker, we
can trivially flick the default to use MSNP9 in configure.ac

-------------- next part --------------
============================================================
--- libpurple/protocols/msnp9/Makefile.am	abb8580629e70836efed2dd5724c724b478be896
+++ libpurple/protocols/msnp9/Makefile.am	abb8580629e70836efed2dd5724c724b478be896
@@ -0,0 +1,90 @@
+EXTRA_DIST = \
+		Makefile.mingw
+
+pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
+
+MSNP9SOURCES = \
+	cmdproc.c \
+	cmdproc.h \
+	command.c \
+	command.h \
+	dialog.c \
+	dialog.h \
+	directconn.c \
+	directconn.h \
+	error.c \
+	error.h \
+	group.c \
+	group.h \
+	history.c \
+	history.h \
+	httpconn.c \
+	httpconn.h \
+	msg.c \
+	msg.h \
+	msn.c \
+	msn.h \
+	nexus.c \
+	nexus.h \
+	notification.c \
+	notification.h \
+	object.c \
+	object.h \
+	page.c \
+	page.h \
+	servconn.c \
+	servconn.h \
+	session.c \
+	session.h \
+	slp.c \
+	slp.h \
+	slpcall.c \
+	slpcall.h \
+	slplink.c \
+	slplink.h \
+	slpmsg.c \
+	slpmsg.h \
+	slpsession.c \
+	slpsession.h \
+	state.c \
+	state.h \
+	switchboard.c \
+	switchboard.h \
+	sync.c \
+	sync.h \
+	table.c \
+	table.h \
+	transaction.c \
+	transaction.h \
+	user.c \
+	user.h \
+	userlist.c \
+	userlist.h \
+	msn-utils.c \
+	msn-utils.h
+
+AM_CFLAGS = $(st)
+
+libmsn_la_LDFLAGS = -module -avoid-version
+
+if STATIC_MSN
+
+st = -DPURPLE_STATIC_PRPL
+noinst_LIBRARIES = libmsn.a
+libmsn_a_SOURCES = $(MSNP9SOURCES)
+libmsn_a_CFLAGS  = $(AM_CFLAGS)
+
+else
+
+st =
+pkg_LTLIBRARIES   = libmsn.la
+libmsn_la_SOURCES = $(MSNP9SOURCES)
+libmsn_la_LIBADD  = $(GLIB_LIBS)
+
+endif
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/libpurple \
+	-I$(top_builddir)/libpurple \
+	$(GLIB_CFLAGS) \
+	$(DEBUG_CFLAGS)
============================================================
--- libpurple/protocols/msnp9/Makefile.mingw	5fac12f6d0be965e45235f056d01bc0eb6c32c67
+++ libpurple/protocols/msnp9/Makefile.mingw	5fac12f6d0be965e45235f056d01bc0eb6c32c67
@@ -0,0 +1,105 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of libmsn
+#
+
+PIDGIN_TREE_TOP := ../../..
+include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+
+TARGET = libmsn
+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 =			cmdproc.c \
+			command.c \
+			dialog.c \
+			directconn.c \
+			error.c \
+			group.c \
+			history.c \
+			httpconn.c \
+			msg.c \
+			msn.c \
+			nexus.c \
+			notification.c \
+			object.c \
+			page.c \
+			servconn.c \
+			session.c \
+			slp.c \
+			slpcall.c \
+			slplink.c \
+			slpmsg.c \
+			slpsession.c \
+			state.c \
+			switchboard.c \
+			sync.c \
+			table.c \
+			transaction.c \
+			user.c \
+			userlist.c \
+			msn-utils.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)
============================================================
--- libpurple/protocols/msnp9/cmdproc.c	800a37bba57fe06cbb24458eb0f3cc4ff5291af0
+++ libpurple/protocols/msnp9/cmdproc.c	800a37bba57fe06cbb24458eb0f3cc4ff5291af0
@@ -0,0 +1,336 @@
+/**
+ * @file cmdproc.c MSN command processor functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "cmdproc.h"
+
+MsnCmdProc *
+msn_cmdproc_new(MsnSession *session)
+{
+	MsnCmdProc *cmdproc;
+
+	cmdproc = g_new0(MsnCmdProc, 1);
+
+	cmdproc->session = session;
+	cmdproc->txqueue = g_queue_new();
+	cmdproc->history = msn_history_new();
+
+	return cmdproc;
+}
+
+void
+msn_cmdproc_destroy(MsnCmdProc *cmdproc)
+{
+	MsnTransaction *trans;
+
+	while ((trans = g_queue_pop_head(cmdproc->txqueue)) != NULL)
+		msn_transaction_destroy(trans);
+
+	g_queue_free(cmdproc->txqueue);
+
+	msn_history_destroy(cmdproc->history);
+
+	if (cmdproc->last_cmd != NULL)
+		msn_command_destroy(cmdproc->last_cmd);
+
+	g_free(cmdproc);
+}
+
+void
+msn_cmdproc_process_queue(MsnCmdProc *cmdproc)
+{
+	MsnTransaction *trans;
+
+	while ((trans = g_queue_pop_head(cmdproc->txqueue)) != NULL)
+		msn_cmdproc_send_trans(cmdproc, trans);
+}
+
+void
+msn_cmdproc_queue_trans(MsnCmdProc *cmdproc, MsnTransaction *trans)
+{
+	g_return_if_fail(cmdproc != NULL);
+	g_return_if_fail(trans   != NULL);
+
+	g_queue_push_tail(cmdproc->txqueue, trans);
+}
+
+static void
+show_debug_cmd(MsnCmdProc *cmdproc, gboolean incoming, const char *command)
+{
+	MsnServConn *servconn;
+	const char *names[] = { "NS", "SB" };
+	char *show;
+	char tmp;
+	size_t len;
+
+	servconn = cmdproc->servconn;
+	len = strlen(command);
+	show = g_strdup(command);
+
+	tmp = (incoming) ? 'S' : 'C';
+
+	if ((show[len - 1] == '\n') && (show[len - 2] == '\r'))
+	{
+		show[len - 2] = '\0';
+	}
+
+	purple_debug_misc("msn", "%c: %s %03d: %s\n", tmp,
+					names[servconn->type], servconn->num, show);
+
+	g_free(show);
+}
+
+void
+msn_cmdproc_send_trans(MsnCmdProc *cmdproc, MsnTransaction *trans)
+{
+	MsnServConn *servconn;
+	char *data;
+	size_t len;
+
+	g_return_if_fail(cmdproc != NULL);
+	g_return_if_fail(trans != NULL);
+
+	servconn = cmdproc->servconn;
+
+	if (!servconn->connected)
+		return;
+
+	msn_history_add(cmdproc->history, trans);
+
+	data = msn_transaction_to_string(trans);
+
+	len = strlen(data);
+
+	show_debug_cmd(cmdproc, FALSE, data);
+
+	if (trans->callbacks == NULL)
+		trans->callbacks = g_hash_table_lookup(cmdproc->cbs_table->cmds,
+											   trans->command);
+
+	if (trans->payload != NULL)
+	{
+		data = g_realloc(data, len + trans->payload_len);
+		memcpy(data + len, trans->payload, trans->payload_len);
+		len += trans->payload_len;
+	}
+
+	msn_servconn_write(servconn, data, len);
+
+	g_free(data);
+}
+
+void
+msn_cmdproc_send_quick(MsnCmdProc *cmdproc, const char *command,
+					   const char *format, ...)
+{
+	MsnServConn *servconn;
+	char *data;
+	char *params = NULL;
+	va_list arg;
+	size_t len;
+
+	g_return_if_fail(cmdproc != NULL);
+	g_return_if_fail(command != NULL);
+
+	servconn = cmdproc->servconn;
+
+	if (!servconn->connected)
+		return;
+
+	if (format != NULL)
+	{
+		va_start(arg, format);
+		params = g_strdup_vprintf(format, arg);
+		va_end(arg);
+	}
+
+	if (params != NULL)
+		data = g_strdup_printf("%s %s\r\n", command, params);
+	else
+		data = g_strdup_printf("%s\r\n", command);
+
+	g_free(params);
+
+	len = strlen(data);
+
+	show_debug_cmd(cmdproc, FALSE, data);
+
+	msn_servconn_write(servconn, data, len);
+
+	g_free(data);
+}
+
+void
+msn_cmdproc_send(MsnCmdProc *cmdproc, const char *command,
+				 const char *format, ...)
+{
+	MsnTransaction *trans;
+	va_list arg;
+
+	g_return_if_fail(cmdproc != NULL);
+	g_return_if_fail(command != NULL);
+
+	if (!cmdproc->servconn->connected)
+		return;
+
+	trans = g_new0(MsnTransaction, 1);
+
+	trans->command = g_strdup(command);
+
+	if (format != NULL)
+	{
+		va_start(arg, format);
+		trans->params = g_strdup_vprintf(format, arg);
+		va_end(arg);
+	}
+
+	msn_cmdproc_send_trans(cmdproc, trans);
+}
+
+void
+msn_cmdproc_process_payload(MsnCmdProc *cmdproc, char *payload,
+							int payload_len)
+{
+	MsnCommand *last;
+
+	g_return_if_fail(cmdproc != NULL);
+
+	last = cmdproc->last_cmd;
+	last->payload = g_memdup(payload, payload_len);
+	last->payload_len = payload_len;
+
+	if (last->payload_cb != NULL)
+		last->payload_cb(cmdproc, last, payload, payload_len);
+}
+
+void
+msn_cmdproc_process_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	MsnMsgTypeCb cb;
+
+	if (msn_message_get_content_type(msg) == NULL)
+	{
+		purple_debug_misc("msn", "failed to find message content\n");
+		return;
+	}
+
+	cb = g_hash_table_lookup(cmdproc->cbs_table->msgs,
+							 msn_message_get_content_type(msg));
+
+	if (cb == NULL)
+	{
+		purple_debug_warning("msn", "Unhandled content-type '%s'\n",
+						   msn_message_get_content_type(msg));
+
+		return;
+	}
+
+	cb(cmdproc, msg);
+}
+
+void
+msn_cmdproc_process_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnTransCb cb = NULL;
+	MsnTransaction *trans = NULL;
+
+	if (cmd->trId)
+		trans = msn_history_find(cmdproc->history, cmd->trId);
+
+	if (trans != NULL)
+		if (trans->timer)
+			purple_timeout_remove(trans->timer);
+
+	if (g_ascii_isdigit(cmd->command[0]))
+	{
+		if (trans != NULL)
+		{
+			MsnErrorCb error_cb = NULL;
+			int error;
+
+			error = atoi(cmd->command);
+
+			if (trans->error_cb != NULL)
+				error_cb = trans->error_cb;
+
+			if (error_cb == NULL && cmdproc->cbs_table->errors != NULL)
+				error_cb = g_hash_table_lookup(cmdproc->cbs_table->errors, trans->command);
+
+			if (error_cb != NULL)
+			{
+				error_cb(cmdproc, trans, error);
+			}
+			else
+			{
+#if 1
+				msn_error_handle(cmdproc->session, error);
+#else
+				purple_debug_warning("msn", "Unhandled error '%s'\n",
+								   cmd->command);
+#endif
+			}
+
+			return;
+		}
+	}
+
+	if (cmdproc->cbs_table->async != NULL)
+		cb = g_hash_table_lookup(cmdproc->cbs_table->async, cmd->command);
+
+	if (cb == NULL && trans != NULL)
+	{
+		cmd->trans = trans;
+
+		if (trans->callbacks != NULL)
+			cb = g_hash_table_lookup(trans->callbacks, cmd->command);
+	}
+
+	if (cb == NULL && cmdproc->cbs_table->fallback != NULL)
+		cb = g_hash_table_lookup(cmdproc->cbs_table->fallback, cmd->command);
+
+	if (cb != NULL)
+	{
+		cb(cmdproc, cmd);
+	}
+	else
+	{
+		purple_debug_warning("msn", "Unhandled command '%s'\n",
+						   cmd->command);
+	}
+
+	if (trans != NULL && trans->pendent_cmd != NULL)
+		msn_transaction_unqueue_cmd(trans, cmdproc);
+}
+
+void
+msn_cmdproc_process_cmd_text(MsnCmdProc *cmdproc, const char *command)
+{
+	show_debug_cmd(cmdproc, TRUE, command);
+
+	if (cmdproc->last_cmd != NULL)
+		msn_command_destroy(cmdproc->last_cmd);
+
+	cmdproc->last_cmd = msn_command_from_string(command);
+
+	msn_cmdproc_process_cmd(cmdproc, cmdproc->last_cmd);
+}
============================================================
--- libpurple/protocols/msnp9/cmdproc.h	14484cdc00317352590dcc0dd06a0cbb9999caf2
+++ libpurple/protocols/msnp9/cmdproc.h	14484cdc00317352590dcc0dd06a0cbb9999caf2
@@ -0,0 +1,74 @@
+/**
+ * @file cmdproc.h MSN command processor functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_CMDPROC_H_
+#define _MSN_CMDPROC_H_
+
+typedef struct _MsnCmdProc MsnCmdProc;
+
+#include "session.h"
+#include "servconn.h"
+#include "error.h"
+#include "command.h"
+#include "table.h"
+#include "history.h"
+
+struct _MsnCmdProc
+{
+	MsnSession *session;
+	MsnServConn *servconn;
+
+	GQueue *txqueue;
+
+	MsnCommand *last_cmd;
+
+	MsnTable *cbs_table;
+
+	MsnHistory *history;
+
+	void *data; /**< Extra data, like the switchboard. */
+};
+
+MsnCmdProc *msn_cmdproc_new(MsnSession *session);
+void msn_cmdproc_destroy(MsnCmdProc *cmdproc);
+
+void msn_cmdproc_process_queue(MsnCmdProc *cmdproc);
+
+void msn_cmdproc_send_trans(MsnCmdProc *cmdproc, MsnTransaction *trans);
+void msn_cmdproc_queue_trans(MsnCmdProc *cmdproc,
+							 MsnTransaction *trans);
+void msn_cmdproc_send(MsnCmdProc *cmdproc, const char *command,
+					  const char *format, ...);
+void msn_cmdproc_send_quick(MsnCmdProc *cmdproc, const char *command,
+							const char *format, ...);
+
+void msn_cmdproc_process_msg(MsnCmdProc *cmdproc,
+							 MsnMessage *msg);
+void msn_cmdproc_process_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd);
+void msn_cmdproc_process_cmd_text(MsnCmdProc *cmdproc, const char *command);
+void msn_cmdproc_process_payload(MsnCmdProc *cmdproc,
+								 char *payload, int payload_len);
+
+void msn_cmdproc_disconnect(MsnCmdProc *cmdproc);
+
+#endif /* _MSN_CMDPROC_H_ */
============================================================
--- libpurple/protocols/msnp9/command.c	101a5c45600bfd0151f5a1c2f8f683df92e58288
+++ libpurple/protocols/msnp9/command.c	101a5c45600bfd0151f5a1c2f8f683df92e58288
@@ -0,0 +1,123 @@
+/**
+ * @file command.c MSN command functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "command.h"
+
+static gboolean
+is_num(char *str)
+{
+	char *c;
+	for (c = str; *c; c++) {
+		if (!(g_ascii_isdigit(*c)))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+MsnCommand *
+msn_command_from_string(const char *string)
+{
+	MsnCommand *cmd;
+	char *tmp;
+	char *param_start;
+
+	g_return_val_if_fail(string != NULL, NULL);
+
+	tmp = g_strdup(string);
+	param_start = strchr(tmp, ' ');
+
+	cmd = g_new0(MsnCommand, 1);
+	cmd->command = tmp;
+
+	if (param_start)
+	{
+		*param_start++ = '\0';
+		cmd->params = g_strsplit(param_start, " ", 0);
+	}
+
+	if (cmd->params != NULL)
+	{
+		char *param;
+		int c;
+
+		for (c = 0; cmd->params[c]; c++);
+		cmd->param_count = c;
+
+		param = cmd->params[0];
+
+		cmd->trId = is_num(param) ? atoi(param) : 0;
+	}
+	else
+		cmd->trId = 0;
+
+	msn_command_ref(cmd);
+
+	return cmd;
+}
+
+void
+msn_command_destroy(MsnCommand *cmd)
+{
+	g_return_if_fail(cmd != NULL);
+
+	if (cmd->ref_count > 0)
+	{
+		msn_command_unref(cmd);
+		return;
+	}
+
+	if (cmd->payload != NULL)
+		g_free(cmd->payload);
+
+	g_free(cmd->command);
+	g_strfreev(cmd->params);
+	g_free(cmd);
+}
+
+MsnCommand *
+msn_command_ref(MsnCommand *cmd)
+{
+	g_return_val_if_fail(cmd != NULL, NULL);
+
+	cmd->ref_count++;
+	return cmd;
+}
+
+MsnCommand *
+msn_command_unref(MsnCommand *cmd)
+{
+	g_return_val_if_fail(cmd != NULL, NULL);
+	g_return_val_if_fail(cmd->ref_count > 0, NULL);
+
+	cmd->ref_count--;
+
+	if (cmd->ref_count == 0)
+	{
+		msn_command_destroy(cmd);
+		return NULL;
+	}
+
+	return cmd;
+}
============================================================
--- libpurple/protocols/msnp9/command.h	b9b8a2de19d0682c75a6b999d2bb0d6826f4a24d
+++ libpurple/protocols/msnp9/command.h	b9b8a2de19d0682c75a6b999d2bb0d6826f4a24d
@@ -0,0 +1,61 @@
+/**
+ * @file command.h MSN command functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_COMMAND_H
+#define _MSN_COMMAND_H
+
+typedef struct _MsnCommand MsnCommand;
+
+#include "cmdproc.h"
+#include "transaction.h"
+
+typedef void (*MsnPayloadCb)(MsnCmdProc *cmdproc, MsnCommand *cmd,
+							 char *payload, size_t len);
+
+/**
+ * A received command.
+ */
+struct _MsnCommand
+{
+	unsigned int trId;
+
+	char *command;
+	char **params;
+	int param_count;
+
+	int ref_count;
+
+	MsnTransaction *trans;
+
+	char *payload;
+	size_t payload_len;
+
+	MsnPayloadCb payload_cb;
+};
+
+MsnCommand *msn_command_from_string(const char *string);
+void msn_command_destroy(MsnCommand *cmd);
+MsnCommand *msn_command_ref(MsnCommand *cmd);
+MsnCommand *msn_command_unref(MsnCommand *cmd);
+
+#endif /* _MSN_COMMAND_H */
============================================================
--- libpurple/protocols/msnp9/dialog.c	57b42e3a8d2639980d44ced5bf0af756eb944a14
+++ libpurple/protocols/msnp9/dialog.c	57b42e3a8d2639980d44ced5bf0af756eb944a14
@@ -0,0 +1,138 @@
+/**
+ * @file dialog.c Dialog functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "msn.h"
+#include "dialog.h"
+
+typedef struct
+{
+	PurpleConnection *gc;
+	char *who;
+	char *group;
+	gboolean add;
+
+} MsnAddRemData;
+
+/* Remove the buddy referenced by the MsnAddRemData before the serverside list is changed.
+ * If the buddy will be added, he'll be added back; if he will be removed, he won't be. */
+static void
+msn_complete_sync_issue(MsnAddRemData *data)
+{
+	PurpleBuddy *buddy;
+	PurpleGroup *group = NULL;
+
+	if (data->group != NULL)
+		group = purple_find_group(data->group);
+	
+	if (group != NULL)
+		buddy = purple_find_buddy_in_group(purple_connection_get_account(data->gc), data->who, group);
+	else
+		buddy = purple_find_buddy(purple_connection_get_account(data->gc), data->who);
+	
+	if (buddy != NULL)
+		purple_blist_remove_buddy(buddy);
+}
+
+static void
+msn_add_cb(MsnAddRemData *data)
+{
+	MsnSession *session;
+	MsnUserList *userlist;
+
+	msn_complete_sync_issue(data);
+
+	session = data->gc->proto_data;
+	userlist = session->userlist;
+
+	msn_userlist_add_buddy(userlist, data->who, MSN_LIST_FL, data->group);
+
+	g_free(data->group);
+	g_free(data->who);
+	g_free(data);
+}
+
+static void
+msn_rem_cb(MsnAddRemData *data)
+{
+	MsnSession *session;
+	MsnUserList *userlist;
+
+	msn_complete_sync_issue(data);
+
+	session = data->gc->proto_data;
+	userlist = session->userlist;
+
+	msn_userlist_rem_buddy(userlist, data->who, MSN_LIST_FL, data->group);
+
+	g_free(data->group);
+	g_free(data->who);
+	g_free(data);
+}
+
+void
+msn_show_sync_issue(MsnSession *session, const char *passport,
+					const char *group_name)
+{
+	PurpleConnection *gc;
+	PurpleAccount *account;
+	MsnAddRemData *data;
+	char *msg, *reason;
+
+	account = session->account;
+	gc = purple_account_get_connection(account);
+
+	data        = g_new0(MsnAddRemData, 1);
+	data->who   = g_strdup(passport);
+	data->group = g_strdup(group_name);
+	data->gc    = gc;
+
+	msg = g_strdup_printf(_("Buddy list synchronization issue in %s (%s)"),
+						  purple_account_get_username(account),
+						  purple_account_get_protocol_name(account));
+
+	if (group_name != NULL)
+	{
+		reason = g_strdup_printf(_("%s on the local list is "
+								   "inside the group \"%s\" but not on "
+								   "the server list. "
+								   "Do you want this buddy to be added?"),
+								 passport, group_name);
+	}
+	else
+	{
+		reason = g_strdup_printf(_("%s is on the local list but "
+								   "not on the server list. "
+								   "Do you want this buddy to be added?"),
+								 passport);
+	}
+
+	purple_request_action(gc, NULL, msg, reason, PURPLE_DEFAULT_ACTION_NONE, 
+						purple_connection_get_account(gc), data->who, NULL,
+						data, 2,
+						_("Yes"), G_CALLBACK(msn_add_cb),
+						_("No"), G_CALLBACK(msn_rem_cb));
+
+	g_free(reason);
+	g_free(msg);
+}
============================================================
--- libpurple/protocols/msnp9/dialog.h	d768420a339adb93de770a9c8134b050bb85dc2d
+++ libpurple/protocols/msnp9/dialog.h	d768420a339adb93de770a9c8134b050bb85dc2d
@@ -0,0 +1,30 @@
+/**
+ * @file dialog.h Dialog functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_DIALOG_H_
+#define _MSN_DIALOG_H_
+
+void msn_show_sync_issue(MsnSession *session, const char *passport,
+						 const char *group_name);
+
+#endif /* _MSN_DIALOG_H_ */
============================================================
--- libpurple/protocols/msnp9/directconn.c	2f7f5cfe58c4cb9f306b8b2ef6009f3d9fda810b
+++ libpurple/protocols/msnp9/directconn.c	2f7f5cfe58c4cb9f306b8b2ef6009f3d9fda810b
@@ -0,0 +1,494 @@
+/**
+ * @file directconn.c MSN direct connection functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "directconn.h"
+
+#include "slp.h"
+#include "slpmsg.h"
+
+/**************************************************************************
+ * Directconn Specific
+ **************************************************************************/
+
+void
+msn_directconn_send_handshake(MsnDirectConn *directconn)
+{
+	MsnSlpLink *slplink;
+	MsnSlpMessage *slpmsg;
+
+	g_return_if_fail(directconn != NULL);
+
+	slplink = directconn->slplink;
+
+	slpmsg = msn_slpmsg_new(slplink);
+	slpmsg->flags = 0x100;
+
+	if (directconn->nonce != NULL)
+	{
+		guint32 t1;
+		guint16 t2;
+		guint16 t3;
+		guint16 t4;
+		guint64 t5;
+
+		sscanf (directconn->nonce, "%08X-%04hX-%04hX-%04hX-%012" G_GINT64_MODIFIER "X", &t1, &t2, &t3, &t4, &t5);
+
+		t1 = GUINT32_TO_LE(t1);
+		t2 = GUINT16_TO_LE(t2);
+		t3 = GUINT16_TO_LE(t3);
+		t4 = GUINT16_TO_BE(t4);
+		t5 = GUINT64_TO_BE(t5);
+
+		slpmsg->ack_id     = t1;
+		slpmsg->ack_sub_id = t2 | (t3 << 16);
+		slpmsg->ack_size   = t4 | t5;
+	}
+
+	g_free(directconn->nonce);
+
+	msn_slplink_send_slpmsg(slplink, slpmsg);
+
+	directconn->acked =TRUE;
+}
+
+/**************************************************************************
+ * Connection Functions
+ **************************************************************************/
+
+#if 0
+static int
+create_listener(int port)
+{
+	int fd;
+	const int on = 1;
+
+#if 0
+	struct addrinfo hints;
+	struct addrinfo *c, *res;
+	char port_str[5];
+
+	snprintf(port_str, sizeof(port_str), "%d", port);
+
+	memset(&hints, 0, sizeof(hints));
+
+	hints.ai_flags = AI_PASSIVE;
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+
+	if (getaddrinfo(NULL, port_str, &hints, &res) != 0)
+	{
+		purple_debug_error("msn", "Could not get address info: %s.\n",
+						 port_str);
+		return -1;
+	}
+
+	for (c = res; c != NULL; c = c->ai_next)
+	{
+		fd = socket(c->ai_family, c->ai_socktype, c->ai_protocol);
+
+		if (fd < 0)
+			continue;
+
+		setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+		if (bind(fd, c->ai_addr, c->ai_addrlen) == 0)
+			break;
+
+		close(fd);
+	}
+
+	if (c == NULL)
+	{
+		purple_debug_error("msn", "Could not find socket: %s.\n", port_str);
+		return -1;
+	}
+
+	freeaddrinfo(res);
+#else
+	struct sockaddr_in sockin;
+
+	fd = socket(AF_INET, SOCK_STREAM, 0);
+
+	if (fd < 0)
+		return -1;
+
+	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) != 0)
+	{
+		close(fd);
+		return -1;
+	}
+
+	memset(&sockin, 0, sizeof(struct sockaddr_in));
+	sockin.sin_family = AF_INET;
+	sockin.sin_port = htons(port);
+
+	if (bind(fd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0)
+	{
+		close(fd);
+		return -1;
+	}
+#endif
+
+	if (listen (fd, 4) != 0)
+	{
+		close (fd);
+		return -1;
+	}
+
+	fcntl(fd, F_SETFL, O_NONBLOCK);
+
+	return fd;
+}
+#endif
+
+static size_t
+msn_directconn_write(MsnDirectConn *directconn,
+					 const char *data, size_t len)
+{
+	char *buffer, *tmp;
+	size_t buf_size;
+	size_t ret;
+	guint32 sent_len;
+
+	g_return_val_if_fail(directconn != NULL, 0);
+
+	buf_size = len + 4;
+	buffer = tmp = g_malloc(buf_size);
+
+	sent_len = GUINT32_TO_LE(len);
+
+	memcpy(tmp, &sent_len, 4);
+	tmp += 4;
+	memcpy(tmp, data, len);
+	tmp += len;
+
+	ret = write(directconn->fd, buffer, buf_size);
+
+#ifdef DEBUG_DC
+	char *str;
+	str = g_strdup_printf("%s/msntest/w%.4d.bin", g_get_home_dir(), directconn->c);
+
+	FILE *tf = g_fopen(str, "w");
+	fwrite(buffer, 1, buf_size, tf);
+	fclose(tf);
+
+	g_free(str);
+#endif
+
+	g_free(buffer);
+
+#if 0
+	/* Let's write the length of the data. */
+	ret = write(directconn->fd, &len, sizeof(len));
+
+	/* Let's write the data. */
+	ret = write(directconn->fd, data, len);
+
+	char *str;
+	str = g_strdup_printf("/home/revo/msntest/w%.4d.bin", directconn->c);
+
+	FILE *tf = g_fopen(str, "w");
+	fwrite(&len, 1, sizeof(len), tf);
+	fwrite(data, 1, len, tf);
+	fclose(tf);
+
+	g_free(str);
+#endif
+
+	directconn->c++;
+
+	return ret;
+}
+
+#if 0
+void
+msn_directconn_parse_nonce(MsnDirectConn *directconn, const char *nonce)
+{
+	guint32 t1;
+	guint16 t2;
+	guint16 t3;
+	guint16 t4;
+	guint64 t5;
+
+	g_return_if_fail(directconn != NULL);
+	g_return_if_fail(nonce      != NULL);
+
+	sscanf (nonce, "%08X-%04hX-%04hX-%04hX-%012llX", &t1, &t2, &t3, &t4, &t5);
+
+	t1 = GUINT32_TO_LE(t1);
+	t2 = GUINT16_TO_LE(t2);
+	t3 = GUINT16_TO_LE(t3);
+	t4 = GUINT16_TO_BE(t4);
+	t5 = GUINT64_TO_BE(t5);
+
+	directconn->slpheader = g_new0(MsnSlpHeader, 1);
+
+	directconn->slpheader->ack_id     = t1;
+	directconn->slpheader->ack_sub_id = t2 | (t3 << 16);
+	directconn->slpheader->ack_size   = t4 | t5;
+}
+#endif
+
+void
+msn_directconn_send_msg(MsnDirectConn *directconn, MsnMessage *msg)
+{
+	char *body;
+	size_t body_len;
+
+	body = msn_message_gen_slp_body(msg, &body_len);
+
+	msn_directconn_write(directconn, body, body_len);
+}
+
+static void
+msn_directconn_process_msg(MsnDirectConn *directconn, MsnMessage *msg)
+{
+	purple_debug_info("msn", "directconn: process_msg\n");
+
+	msn_slplink_process_msg(directconn->slplink, msg);
+}
+
+static void
+read_cb(gpointer data, gint source, PurpleInputCondition cond)
+{
+	MsnDirectConn* directconn;
+	char *body;
+	size_t len, body_len;
+
+	purple_debug_info("msn", "read_cb: %d, %d\n", source, cond);
+
+	directconn = data;
+
+	/* Let's read the length of the data. */
+	len = read(directconn->fd, &body_len, sizeof(body_len));
+
+	if (len <= 0)
+	{
+		/* ERROR */
+		purple_debug_error("msn", "error reading\n");
+
+		msn_directconn_destroy(directconn);
+
+		return;
+	}
+
+	body_len = GUINT32_FROM_LE(body_len);
+
+	purple_debug_info("msn", "body_len=%d\n", body_len);
+
+	if (body_len <= 0)
+	{
+		/* ERROR */
+		purple_debug_error("msn", "error reading\n");
+
+		msn_directconn_destroy(directconn);
+
+		return;
+	}
+
+	body = g_try_malloc(body_len);
+
+	if (body != NULL)
+	{
+		/* Let's read the data. */
+		len = read(directconn->fd, body, body_len);
+
+		purple_debug_info("msn", "len=%d\n", len);
+	}
+	else
+	{
+		purple_debug_error("msn", "Failed to allocate memory for read\n");
+		len = 0;
+	}
+
+	if (len > 0)
+	{
+		MsnMessage *msg;
+
+#ifdef DEBUG_DC
+		str = g_strdup_printf("/home/revo/msntest/r%.4d.bin", directconn->c);
+
+		FILE *tf = g_fopen(str, "w");
+		fwrite(body, 1, len, tf);
+		fclose(tf);
+
+		g_free(str);
+#endif
+
+		directconn->c++;
+
+		msg = msn_message_new_msnslp();
+		msn_message_parse_slp_body(msg, body, body_len);
+
+		msn_directconn_process_msg(directconn, msg);
+	}
+	else
+	{
+		/* ERROR */
+		purple_debug_error("msn", "error reading\n");
+
+		msn_directconn_destroy(directconn);
+	}
+}
+
+static void
+connect_cb(gpointer data, gint source, const gchar *error_message)
+{
+	MsnDirectConn* directconn;
+	int fd;
+
+	purple_debug_misc("msn", "directconn: connect_cb: %d\n", source);
+
+	directconn = data;
+	directconn->connect_data = NULL;
+
+	if (TRUE)
+	{
+		fd = source;
+	}
+	else
+	{
+		struct sockaddr_in client_addr;
+		socklen_t client;
+		fd = accept (source, (struct sockaddr *)&client_addr, &client);
+	}
+
+	directconn->fd = fd;
+
+	if (fd > 0)
+	{
+		directconn->inpa = purple_input_add(fd, PURPLE_INPUT_READ, read_cb,
+										  directconn);
+
+		if (TRUE)
+		{
+			/* Send foo. */
+			msn_directconn_write(directconn, "foo", strlen("foo") + 1);
+
+			/* Send Handshake */
+			msn_directconn_send_handshake(directconn);
+		}
+		else
+		{
+		}
+	}
+	else
+	{
+		/* ERROR */
+		purple_debug_error("msn", "could not add input\n");
+
+		if (directconn->inpa)
+			purple_input_remove(directconn->inpa);
+
+		close(directconn->fd);
+	}
+}
+
+gboolean
+msn_directconn_connect(MsnDirectConn *directconn, const char *host, int port)
+{
+	MsnSession *session;
+
+	g_return_val_if_fail(directconn != NULL, FALSE);
+	g_return_val_if_fail(host       != NULL, TRUE);
+	g_return_val_if_fail(port        > 0,    FALSE);
+
+	session = directconn->slplink->session;
+
+#if 0
+	if (session->http_method)
+	{
+		servconn->http_data->gateway_host = g_strdup(host);
+	}
+#endif
+
+	directconn->connect_data = purple_proxy_connect(NULL, session->account,
+			host, port, connect_cb, directconn);
+
+	if (directconn->connect_data != NULL)
+	{
+		return TRUE;
+	}
+	else
+		return FALSE;
+}
+
+#if 0
+void
+msn_directconn_listen(MsnDirectConn *directconn)
+{
+	int port;
+	int fd;
+
+	port = 7000;
+
+	for (fd = -1; fd < 0;)
+		fd = create_listener(++port);
+
+	directconn->fd = fd;
+
+	directconn->inpa = purple_input_add(fd, PURPLE_INPUT_READ, connect_cb,
+		directconn);
+
+	directconn->port = port;
+	directconn->c = 0;
+}
+#endif
+
+MsnDirectConn*
+msn_directconn_new(MsnSlpLink *slplink)
+{
+	MsnDirectConn *directconn;
+
+	directconn = g_new0(MsnDirectConn, 1);
+
+	directconn->slplink = slplink;
+
+	if (slplink->directconn != NULL)
+		purple_debug_info("msn", "got_transresp: LEAK\n");
+
+	slplink->directconn = directconn;
+
+	return directconn;
+}
+
+void
+msn_directconn_destroy(MsnDirectConn *directconn)
+{
+	if (directconn->connect_data != NULL)
+		purple_proxy_connect_cancel(directconn->connect_data);
+
+	if (directconn->inpa != 0)
+		purple_input_remove(directconn->inpa);
+
+	if (directconn->fd >= 0)
+		close(directconn->fd);
+
+	if (directconn->nonce != NULL)
+		g_free(directconn->nonce);
+
+	directconn->slplink->directconn = NULL;
+
+	g_free(directconn);
+}
============================================================
--- libpurple/protocols/msnp9/directconn.h	5065f1c28a423d7cef4da3ef0dd0d77d2b8ad74d
+++ libpurple/protocols/msnp9/directconn.h	5065f1c28a423d7cef4da3ef0dd0d77d2b8ad74d
@@ -0,0 +1,63 @@
+/**
+ * @file directconn.h MSN direct connection functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_DIRECTCONN_H_
+#define _MSN_DIRECTCONN_H_
+
+typedef struct _MsnDirectConn MsnDirectConn;
+
+#include "slplink.h"
+#include "slp.h"
+#include "msg.h"
+
+struct _MsnDirectConn
+{
+	MsnSlpLink *slplink;
+	MsnSlpCall *initial_call;
+
+	PurpleProxyConnectData *connect_data;
+
+	gboolean acked;
+
+	char *nonce;
+
+	int fd;
+
+	int port;
+	int inpa;
+
+	int c;
+};
+
+MsnDirectConn *msn_directconn_new(MsnSlpLink *slplink);
+gboolean msn_directconn_connect(MsnDirectConn *directconn,
+								const char *host, int port);
+#if 0
+void msn_directconn_listen(MsnDirectConn *directconn);
+#endif
+void msn_directconn_send_msg(MsnDirectConn *directconn, MsnMessage *msg);
+void msn_directconn_parse_nonce(MsnDirectConn *directconn, const char *nonce);
+void msn_directconn_destroy(MsnDirectConn *directconn);
+void msn_directconn_send_handshake(MsnDirectConn *directconn);
+
+#endif /* _MSN_DIRECTCONN_H_ */
============================================================
--- libpurple/protocols/msnp9/error.c	8aea775766e6ab7bb9f6f45a75da2af996b13712
+++ libpurple/protocols/msnp9/error.c	8aea775766e6ab7bb9f6f45a75da2af996b13712
@@ -0,0 +1,269 @@
+/**
+ * @file error.c Error functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "error.h"
+
+const char *
+msn_error_get_text(unsigned int type, gboolean *debug)
+{
+	static char msg[MSN_BUF_LEN];
+	*debug = FALSE;
+
+	switch (type) {
+		case 0:
+			g_snprintf(msg, sizeof(msg),
+					   _("Unable to parse message"));
+			*debug = TRUE;
+			break;
+		case 200:
+			g_snprintf(msg, sizeof(msg),
+					   _("Syntax Error (probably a client bug)"));
+			*debug = TRUE;
+			break;
+		case 201:
+			g_snprintf(msg, sizeof(msg),
+					   _("Invalid e-mail address"));
+			break;
+		case 205:
+			g_snprintf(msg, sizeof(msg), _("User does not exist"));
+			break;
+		case 206:
+			g_snprintf(msg, sizeof(msg),
+					   _("Fully qualified domain name missing"));
+			break;
+		case 207:
+			g_snprintf(msg, sizeof(msg), _("Already logged in"));
+			break;
+		case 208:
+			g_snprintf(msg, sizeof(msg), _("Invalid screen name"));
+			break;
+		case 209:
+			g_snprintf(msg, sizeof(msg), _("Invalid friendly name"));
+			break;
+		case 210:
+			g_snprintf(msg, sizeof(msg), _("List full"));
+			break;
+		case 215:
+			g_snprintf(msg, sizeof(msg), _("Already there"));
+			*debug = TRUE;
+			break;
+		case 216:
+			g_snprintf(msg, sizeof(msg), _("Not on list"));
+			break;
+		case 217:
+			g_snprintf(msg, sizeof(msg), _("User is offline"));
+			break;
+		case 218:
+			g_snprintf(msg, sizeof(msg), _("Already in the mode"));
+			*debug = TRUE;
+			break;
+		case 219:
+			g_snprintf(msg, sizeof(msg), _("Already in opposite list"));
+			*debug = TRUE;
+			break;
+		case 223:
+			g_snprintf(msg, sizeof(msg), _("Too many groups"));
+			break;
+		case 224:
+			g_snprintf(msg, sizeof(msg), _("Invalid group"));
+			break;
+		case 225:
+			g_snprintf(msg, sizeof(msg), _("User not in group"));
+			break;
+		case 229:
+			g_snprintf(msg, sizeof(msg), _("Group name too long"));
+			break;
+		case 230:
+			g_snprintf(msg, sizeof(msg), _("Cannot remove group zero"));
+			*debug = TRUE;
+			break;
+		case 231:
+			g_snprintf(msg, sizeof(msg),
+					   _("Tried to add a user to a group "
+						 "that doesn't exist"));
+			break;
+		case 280:
+			g_snprintf(msg, sizeof(msg), _("Switchboard failed"));
+			*debug = TRUE;
+			break;
+		case 281:
+			g_snprintf(msg, sizeof(msg), _("Notify transfer failed"));
+			*debug = TRUE;
+			break;
+
+		case 300:
+			g_snprintf(msg, sizeof(msg), _("Required fields missing"));
+			*debug = TRUE;
+			break;
+		case 301:
+			g_snprintf(msg, sizeof(msg), _("Too many hits to a FND"));
+			*debug = TRUE;
+			break;
+		case 302:
+			g_snprintf(msg, sizeof(msg), _("Not logged in"));
+			break;
+
+		case 500:
+			g_snprintf(msg, sizeof(msg), _("Service temporarily unavailable"));
+			break;
+		case 501:
+			g_snprintf(msg, sizeof(msg), _("Database server error"));
+			*debug = TRUE;
+			break;
+		case 502:
+			g_snprintf(msg, sizeof(msg), _("Command disabled"));
+			*debug = TRUE;
+			break;
+		case 510:
+			g_snprintf(msg, sizeof(msg), _("File operation error"));
+			*debug = TRUE;
+			break;
+		case 520:
+			g_snprintf(msg, sizeof(msg), _("Memory allocation error"));
+			*debug = TRUE;
+			break;
+		case 540:
+			g_snprintf(msg, sizeof(msg), _("Wrong CHL value sent to server"));
+			*debug = TRUE;
+			break;
+
+		case 600:
+			g_snprintf(msg, sizeof(msg), _("Server busy"));
+			break;
+		case 601:
+			g_snprintf(msg, sizeof(msg), _("Server unavailable"));
+			break;
+		case 602:
+			g_snprintf(msg, sizeof(msg), _("Peer notification server down"));
+			*debug = TRUE;
+			break;
+		case 603:
+			g_snprintf(msg, sizeof(msg), _("Database connect error"));
+			*debug = TRUE;
+			break;
+		case 604:
+			g_snprintf(msg, sizeof(msg),
+					   _("Server is going down (abandon ship)"));
+			break;
+		case 605:
+			g_snprintf(msg, sizeof(msg), _("Server unavailable"));
+			break;
+
+		case 707:
+			g_snprintf(msg, sizeof(msg), _("Error creating connection"));
+			*debug = TRUE;
+			break;
+		case 710:
+			g_snprintf(msg, sizeof(msg),
+					   _("CVR parameters are either unknown or not allowed"));
+			*debug = TRUE;
+			break;
+		case 711:
+			g_snprintf(msg, sizeof(msg), _("Unable to write"));
+			break;
+		case 712:
+			g_snprintf(msg, sizeof(msg), _("Session overload"));
+			*debug = TRUE;
+			break;
+		case 713:
+			g_snprintf(msg, sizeof(msg), _("User is too active"));
+			break;
+		case 714:
+			g_snprintf(msg, sizeof(msg), _("Too many sessions"));
+			break;
+		case 715:
+			g_snprintf(msg, sizeof(msg), _("Passport not verified"));
+			break;
+		case 717:
+			g_snprintf(msg, sizeof(msg), _("Bad friend file"));
+			*debug = TRUE;
+			break;
+		case 731:
+			g_snprintf(msg, sizeof(msg), _("Not expected"));
+			*debug = TRUE;
+			break;
+
+		case 800:
+			g_snprintf(msg, sizeof(msg),
+					   _("Friendly name changes too rapidly"));
+			break;
+
+		case 910:
+		case 912:
+		case 918:
+		case 919:
+		case 921:
+		case 922:
+			g_snprintf(msg, sizeof(msg), _("Server too busy"));
+			break;
+		case 911:
+		case 917:
+			g_snprintf(msg, sizeof(msg), _("Authentication failed"));
+			break;
+		case 913:
+			g_snprintf(msg, sizeof(msg), _("Not allowed when offline"));
+			break;
+		case 914:
+		case 915:
+		case 916:
+			g_snprintf(msg, sizeof(msg), _("Server unavailable"));
+			break;
+		case 920:
+			g_snprintf(msg, sizeof(msg), _("Not accepting new users"));
+			break;
+		case 923:
+			g_snprintf(msg, sizeof(msg),
+					   _("Kids Passport without parental consent"));
+			break;
+		case 924:
+			g_snprintf(msg, sizeof(msg),
+					   _("Passport account not yet verified"));
+			break;
+		case 928:
+			g_snprintf(msg, sizeof(msg), _("Bad ticket"));
+			*debug = TRUE;
+			break;
+
+		default:
+			g_snprintf(msg, sizeof(msg), _("Unknown Error Code %d"), type);
+			*debug = TRUE;
+			break;
+	}
+
+	return msg;
+}
+
+void
+msn_error_handle(MsnSession *session, unsigned int type)
+{
+	char buf[MSN_BUF_LEN];
+	gboolean debug;
+	
+	g_snprintf(buf, sizeof(buf), _("MSN Error: %s\n"),
+			   msn_error_get_text(type, &debug));
+	if (debug)
+		purple_debug_warning("msn", "error %d: %s\n", type, buf);
+	else
+		purple_notify_error(session->account->gc, NULL, buf, NULL);
+}
============================================================
--- libpurple/protocols/msnp9/error.h	a09c412ec93117866704692259b993e6d2499fb9
+++ libpurple/protocols/msnp9/error.h	a09c412ec93117866704692259b993e6d2499fb9
@@ -0,0 +1,47 @@
+/**
+ * @file error.h Error functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_ERROR_H_
+#define _MSN_ERROR_H_
+
+#include "session.h"
+
+/**
+ * Returns the string representation of an error type.
+ *
+ * @param type The error type.
+ * @param debug Whether this should be treated as a debug log message or a user-visible error
+ *
+ * @return The string representation of the error type.
+ */
+const char *msn_error_get_text(unsigned int type, gboolean *debug);
+
+/**
+ * Handles an error.
+ *
+ * @param session The current session.
+ * @param type    The error type.
+ */
+void msn_error_handle(MsnSession *session, unsigned int type);
+
+#endif /* _MSN_ERROR_H_ */
============================================================
--- libpurple/protocols/msnp9/group.c	119dd2cf3c885ab374dc9e802d6eabb0e17de384
+++ libpurple/protocols/msnp9/group.c	119dd2cf3c885ab374dc9e802d6eabb0e17de384
@@ -0,0 +1,89 @@
+/**
+ * @file group.c Group functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "group.h"
+
+MsnGroup *
+msn_group_new(MsnUserList *userlist, int id, const char *name)
+{
+	MsnGroup *group;
+
+	g_return_val_if_fail(id >= 0,      NULL);
+	g_return_val_if_fail(name != NULL, NULL);
+
+	group = g_new0(MsnGroup, 1);
+
+	msn_userlist_add_group(userlist, group);
+
+	group->id      = id;
+	group->name    = g_strdup(name);
+
+	return group;
+}
+
+void
+msn_group_destroy(MsnGroup *group)
+{
+	g_return_if_fail(group != NULL);
+
+	g_free(group->name);
+	g_free(group);
+}
+
+void
+msn_group_set_id(MsnGroup *group, int id)
+{
+	g_return_if_fail(group != NULL);
+	g_return_if_fail(id >= 0);
+
+	group->id = id;
+}
+
+void
+msn_group_set_name(MsnGroup *group, const char *name)
+{
+	g_return_if_fail(group != NULL);
+	g_return_if_fail(name  != NULL);
+
+	if (group->name != NULL)
+		g_free(group->name);
+
+	group->name = g_strdup(name);
+}
+
+int
+msn_group_get_id(const MsnGroup *group)
+{
+	g_return_val_if_fail(group != NULL, -1);
+
+	return group->id;
+}
+
+const char *
+msn_group_get_name(const MsnGroup *group)
+{
+	g_return_val_if_fail(group != NULL, NULL);
+
+	return group->name;
+}
============================================================
--- libpurple/protocols/msnp9/group.h	17d4364ef3e403a3441e9e4906314e8aa619afa8
+++ libpurple/protocols/msnp9/group.h	17d4364ef3e403a3441e9e4906314e8aa619afa8
@@ -0,0 +1,103 @@
+/**
+ * @file group.h Group functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_GROUP_H_
+#define _MSN_GROUP_H_
+
+typedef struct _MsnGroup  MsnGroup;
+
+#include <stdio.h>
+
+#include "session.h"
+#include "user.h"
+
+#include "userlist.h"
+
+/**
+ * A group.
+ */
+struct _MsnGroup
+{
+	MsnSession *session;    /**< The MSN session.           */
+
+	int id;                 /**< The group ID.              */
+	char *name;             /**< The name of the group.     */
+};
+
+/**************************************************************************/
+/** @name Group API                                                       */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a new group structure.
+ *
+ * @param session The MSN session.
+ * @param id      The group ID.
+ * @param name    The name of the group.
+ *
+ * @return A new group structure.
+ */
+MsnGroup *msn_group_new(MsnUserList *userlist, int id, const char *name);
+
+/**
+ * Destroys a group structure.
+ *
+ * @param group The group to destroy.
+ */
+void msn_group_destroy(MsnGroup *group);
+
+/**
+ * Sets the ID for a group.
+ *
+ * @param group The group.
+ * @param id    The ID.
+ */
+void msn_group_set_id(MsnGroup *group, int id);
+
+/**
+ * Sets the name for a group.
+ *
+ * @param group The group.
+ * @param name  The name.
+ */
+void msn_group_set_name(MsnGroup *group, const char *name);
+
+/**
+ * Returns the ID for a group.
+ *
+ * @param group The group.
+ *
+ * @return The ID.
+ */
+int msn_group_get_id(const MsnGroup *group);
+
+/**
+ * Returns the name for a group.
+ *
+ * @param group The group.
+ *
+ * @return The name.
+ */
+const char *msn_group_get_name(const MsnGroup *group);
+#endif /* _MSN_GROUP_H_ */
============================================================
--- libpurple/protocols/msnp9/history.c	c7d0b86a66efd9246d3042c44d0ff20bf056681a
+++ libpurple/protocols/msnp9/history.c	c7d0b86a66efd9246d3042c44d0ff20bf056681a
@@ -0,0 +1,86 @@
+/**
+ * @file history.c MSN history functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "history.h"
+
+MsnHistory *
+msn_history_new(void)
+{
+	MsnHistory *history = g_new0(MsnHistory, 1);
+
+	history->trId = 1;
+
+	history->queue = g_queue_new();
+
+	return history;
+}
+
+void
+msn_history_destroy(MsnHistory *history)
+{
+	MsnTransaction *trans;
+
+	while ((trans = g_queue_pop_head(history->queue)) != NULL)
+		msn_transaction_destroy(trans);
+
+	g_queue_free(history->queue);
+	g_free(history);
+}
+
+MsnTransaction *
+msn_history_find(MsnHistory *history, unsigned int trId)
+{
+	MsnTransaction *trans;
+	GList *list;
+
+	for (list = history->queue->head; list != NULL; list = list->next)
+	{
+		trans = list->data;
+		if (trans->trId == trId)
+			return trans;
+	}
+
+	return NULL;
+}
+
+void
+msn_history_add(MsnHistory *history, MsnTransaction *trans)
+{
+	GQueue *queue;
+
+	g_return_if_fail(history != NULL);
+	g_return_if_fail(trans   != NULL);
+
+	queue = history->queue;
+
+	trans->trId = history->trId++;
+
+	g_queue_push_tail(queue, trans);
+
+	if (queue->length > MSN_HIST_ELEMS)
+	{
+		trans = g_queue_pop_head(queue);
+		msn_transaction_destroy(trans);
+	}
+}
============================================================
--- libpurple/protocols/msnp9/history.h	956cf58d7d7821e9e8cdf3d319e81205c351b994
+++ libpurple/protocols/msnp9/history.h	956cf58d7d7821e9e8cdf3d319e81205c351b994
@@ -0,0 +1,47 @@
+/**
+ * @file history.h MSN history functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_HISTORY_H
+#define _MSN_HISTORY_H
+
+#define MSN_HIST_ELEMS 0x30
+
+typedef struct _MsnHistory MsnHistory;
+
+#include "transaction.h"
+
+/**
+ * The history.
+ */
+struct _MsnHistory
+{
+	GQueue *queue;
+	unsigned int trId;
+};
+
+MsnHistory *msn_history_new(void);
+void msn_history_destroy(MsnHistory *history);
+MsnTransaction *msn_history_find(MsnHistory *history, unsigned int triId);
+void msn_history_add(MsnHistory *history, MsnTransaction *trans);
+
+#endif /* _MSN_HISTORY_H */
============================================================
--- libpurple/protocols/msnp9/httpconn.c	31e00e6a8b2e1e4f1742af9e8ca6a5e5ea9abb22
+++ libpurple/protocols/msnp9/httpconn.c	31e00e6a8b2e1e4f1742af9e8ca6a5e5ea9abb22
@@ -0,0 +1,775 @@
+/**
+ * @file httpmethod.c HTTP connection method
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "debug.h"
+#include "httpconn.h"
+
+typedef struct
+{
+	MsnHttpConn *httpconn;
+	char *body;
+	size_t body_len;
+} MsnHttpQueueData;
+
+static void
+msn_httpconn_process_queue(MsnHttpConn *httpconn)
+{
+	httpconn->waiting_response = FALSE;
+
+	if (httpconn->queue != NULL)
+	{
+		MsnHttpQueueData *queue_data;
+
+		queue_data = (MsnHttpQueueData *)httpconn->queue->data;
+
+		httpconn->queue = g_list_remove(httpconn->queue, queue_data);
+
+		msn_httpconn_write(queue_data->httpconn,
+						   queue_data->body,
+						   queue_data->body_len);
+
+		g_free(queue_data->body);
+		g_free(queue_data);
+	}
+}
+
+static gboolean
+msn_httpconn_parse_data(MsnHttpConn *httpconn, const char *buf,
+						size_t size, char **ret_buf, size_t *ret_size,
+						gboolean *error)
+{
+	const char *s, *c;
+	char *header, *body;
+	const char *body_start;
+	char *tmp;
+	size_t body_len = 0;
+	gboolean wasted = FALSE;
+
+	g_return_val_if_fail(httpconn != NULL, FALSE);
+	g_return_val_if_fail(buf      != NULL, FALSE);
+	g_return_val_if_fail(size      > 0,    FALSE);
+	g_return_val_if_fail(ret_buf  != NULL, FALSE);
+	g_return_val_if_fail(ret_size != NULL, FALSE);
+	g_return_val_if_fail(error    != NULL, FALSE);
+
+#if 0
+	purple_debug_info("msn", "HTTP: parsing data {%s}\n", buf);
+#endif
+
+	/* Healthy defaults. */
+	body = NULL;
+
+	*ret_buf  = NULL;
+	*ret_size = 0;
+	*error    = FALSE;
+
+	/* First, some tests to see if we have a full block of stuff. */
+	if (((strncmp(buf, "HTTP/1.1 200 OK\r\n", 17) != 0) &&
+		 (strncmp(buf, "HTTP/1.1 100 Continue\r\n", 23) != 0)) &&
+		((strncmp(buf, "HTTP/1.0 200 OK\r\n", 17) != 0) &&
+		 (strncmp(buf, "HTTP/1.0 100 Continue\r\n", 23) != 0)))
+	{
+		*error = TRUE;
+
+		return FALSE;
+	}
+
+	if (strncmp(buf, "HTTP/1.1 100 Continue\r\n", 23) == 0)
+	{
+		if ((s = strstr(buf, "\r\n\r\n")) == NULL)
+			return FALSE;
+
+		s += 4;
+
+		if (*s == '\0')
+		{
+			*ret_buf = g_strdup("");
+			*ret_size = 0;
+
+			msn_httpconn_process_queue(httpconn);
+
+			return TRUE;
+		}
+
+		buf = s;
+		size -= (s - buf);
+	}
+
+	if ((s = strstr(buf, "\r\n\r\n")) == NULL)
+		/* Need to wait for the full HTTP header to arrive */
+		return FALSE;
+
+	s += 4; /* Skip \r\n */
+	header = g_strndup(buf, s - buf);
+	body_start = s;
+	body_len = size - (body_start - buf);
+
+	if ((s = purple_strcasestr(header, "Content-Length: ")) != NULL)
+	{
+		int tmp_len;
+
+		s += strlen("Content-Length: ");
+
+		if ((c = strchr(s, '\r')) == NULL)
+		{
+			g_free(header);
+
+			return FALSE;
+		}
+
+		tmp = g_strndup(s, c - s);
+		tmp_len = atoi(tmp);
+		g_free(tmp);
+
+		if (body_len != tmp_len)
+		{
+			/* Need to wait for the full packet to arrive */
+
+			g_free(header);
+
+#if 0
+			purple_debug_warning("msn",
+							   "body length (%d) != content length (%d)\n",
+							   body_len, tmp_len);
+#endif
+
+			return FALSE;
+		}
+	}
+
+	body = g_malloc0(body_len + 1);
+	memcpy(body, body_start, body_len);
+
+#ifdef MSN_DEBUG_HTTP
+	purple_debug_misc("msn", "Incoming HTTP buffer (header): {%s\r\n}\n",
+					header);
+#endif
+
+	/* Now we should be able to process the data. */
+	if ((s = purple_strcasestr(header, "X-MSN-Messenger: ")) != NULL)
+	{
+		char *full_session_id, *gw_ip, *session_action;
+		char *t, *session_id;
+		char **elems, **cur, **tokens;
+
+		full_session_id = gw_ip = session_action = NULL;
+
+		s += strlen("X-MSN-Messenger: ");
+
+		if ((c = strchr(s, '\r')) == NULL)
+		{
+			msn_session_set_error(httpconn->session,
+								  MSN_ERROR_HTTP_MALFORMED, NULL);
+			purple_debug_error("msn", "Malformed X-MSN-Messenger field.\n{%s}\n",
+							 buf);
+
+			g_free(body);
+			return FALSE;
+		}
+
+		tmp = g_strndup(s, c - s);
+
+		elems = g_strsplit(tmp, "; ", 0);
+
+		for (cur = elems; *cur != NULL; cur++)
+		{
+			tokens = g_strsplit(*cur, "=", 2);
+
+			if (strcmp(tokens[0], "SessionID") == 0)
+				full_session_id = tokens[1];
+			else if (strcmp(tokens[0], "GW-IP") == 0)
+				gw_ip = tokens[1];
+			else if (strcmp(tokens[0], "Session") == 0)
+				session_action = tokens[1];
+			else
+				g_free(tokens[1]);
+
+			g_free(tokens[0]);
+			/* Don't free each of the tokens, only the array. */
+			g_free(tokens);
+		}
+
+		g_strfreev(elems);
+
+		g_free(tmp);
+
+		if ((session_action != NULL) && (strcmp(session_action, "close") == 0))
+			wasted = TRUE;
+
+		g_free(session_action);
+
+		t = strchr(full_session_id, '.');
+		session_id = g_strndup(full_session_id, t - full_session_id);
+
+		if (!wasted)
+		{
+			g_free(httpconn->full_session_id);
+			httpconn->full_session_id = full_session_id;
+
+			g_free(httpconn->session_id);
+			httpconn->session_id = session_id;
+
+			g_free(httpconn->host);
+			httpconn->host = gw_ip;
+		}
+		else
+		{
+			MsnServConn *servconn;
+
+			/* It's going to die. */
+			/* poor thing */
+
+			servconn = httpconn->servconn;
+
+			/* I'll be honest, I don't fully understand all this, but this
+			 * causes crashes, Stu. */
+			/* if (servconn != NULL)
+				servconn->wasted = TRUE; */
+
+			g_free(full_session_id);
+			g_free(session_id);
+			g_free(gw_ip);
+		}
+	}
+
+	g_free(header);
+
+	*ret_buf  = body;
+	*ret_size = body_len;
+
+	msn_httpconn_process_queue(httpconn);
+
+	return TRUE;
+}
+
+static void
+read_cb(gpointer data, gint source, PurpleInputCondition cond)
+{
+	MsnHttpConn *httpconn;
+	MsnServConn *servconn;
+	MsnSession *session;
+	char buf[MSN_BUF_LEN];
+	char *cur, *end, *old_rx_buf;
+	int len, cur_len;
+	char *result_msg = NULL;
+	size_t result_len = 0;
+	gboolean error = FALSE;
+
+	httpconn = data;
+	servconn = NULL;
+	session = httpconn->session;
+
+	len = read(httpconn->fd, buf, sizeof(buf) - 1);
+
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len <= 0)
+	{
+		purple_debug_error("msn", "HTTP: Read error\n");
+		msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_READ);
+
+		return;
+	}
+
+	buf[len] = '\0';
+
+	httpconn->rx_buf = g_realloc(httpconn->rx_buf, len + httpconn->rx_len + 1);
+	memcpy(httpconn->rx_buf + httpconn->rx_len, buf, len + 1);
+	httpconn->rx_len += len;
+
+	if (!msn_httpconn_parse_data(httpconn, httpconn->rx_buf, httpconn->rx_len,
+								 &result_msg, &result_len, &error))
+	{
+		/* Either we must wait for more input, or something went wrong */
+		if (error)
+			msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_READ);
+
+		return;
+	}
+
+	httpconn->servconn->processing = FALSE;
+
+	servconn = httpconn->servconn;
+
+	if (error)
+	{
+		purple_debug_error("msn", "HTTP: Special error\n");
+		msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_READ);
+
+		return;
+	}
+
+	g_free(httpconn->rx_buf);
+	httpconn->rx_buf = NULL;
+	httpconn->rx_len = 0;
+
+	if (result_len == 0)
+	{
+		/* Nothing to do here */
+#if 0
+		purple_debug_info("msn", "HTTP: nothing to do here\n");
+#endif
+		g_free(result_msg);
+		return;
+	}
+
+	g_free(servconn->rx_buf);
+	servconn->rx_buf = result_msg;
+	servconn->rx_len = result_len;
+
+	end = old_rx_buf = servconn->rx_buf;
+
+	servconn->processing = TRUE;
+
+	do
+	{
+		cur = end;
+
+		if (servconn->payload_len)
+		{
+			if (servconn->payload_len > servconn->rx_len)
+				/* The payload is still not complete. */
+				break;
+
+			cur_len = servconn->payload_len;
+			end += cur_len;
+		}
+		else
+		{
+			end = strstr(cur, "\r\n");
+
+			if (end == NULL)
+				/* The command is still not complete. */
+				break;
+
+			*end = '\0';
+			end += 2;
+			cur_len = end - cur;
+		}
+
+		servconn->rx_len -= cur_len;
+
+		if (servconn->payload_len)
+		{
+			msn_cmdproc_process_payload(servconn->cmdproc, cur, cur_len);
+			servconn->payload_len = 0;
+		}
+		else
+		{
+			msn_cmdproc_process_cmd_text(servconn->cmdproc, cur);
+		}
+	} while (servconn->connected && servconn->rx_len > 0);
+
+	if (servconn->connected)
+	{
+		if (servconn->rx_len > 0)
+			servconn->rx_buf = g_memdup(cur, servconn->rx_len);
+		else
+			servconn->rx_buf = NULL;
+	}
+
+	servconn->processing = FALSE;
+
+	if (servconn->wasted)
+		msn_servconn_destroy(servconn);
+
+	g_free(old_rx_buf);
+}
+
+static void
+httpconn_write_cb(gpointer data, gint source, PurpleInputCondition cond)
+{
+	MsnHttpConn *httpconn;
+	int ret, writelen;
+
+	httpconn = data;
+	writelen = purple_circ_buffer_get_max_read(httpconn->tx_buf);
+
+	if (writelen == 0)
+	{
+		purple_input_remove(httpconn->tx_handler);
+		httpconn->tx_handler = 0;
+		return;
+	}
+
+	ret = write(httpconn->fd, httpconn->tx_buf->outptr, writelen);
+	if (ret <= 0)
+	{
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			/* No worries */
+			return;
+
+		/* Error! */
+		msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_WRITE);
+		return;
+	}
+
+	purple_circ_buffer_mark_read(httpconn->tx_buf, ret);
+
+	/* TODO: I don't think these 2 lines are needed.  Remove them? */
+	if (ret == writelen)
+		httpconn_write_cb(data, source, cond);
+}
+
+static gboolean
+write_raw(MsnHttpConn *httpconn, const char *data, size_t data_len)
+{
+	ssize_t res; /* result of the write operation */
+
+	if (httpconn->tx_handler == 0)
+		res = write(httpconn->fd, data, data_len);
+	else
+	{
+		res = -1;
+		errno = EAGAIN;
+	}
+
+	if ((res <= 0) && ((errno != EAGAIN) && (errno != EWOULDBLOCK)))
+	{
+		msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_WRITE);
+		return FALSE;
+	}
+
+	if (res < 0 || res < data_len)
+	{
+		if (res < 0)
+			res = 0;
+		if (httpconn->tx_handler == 0 && httpconn->fd)
+			httpconn->tx_handler = purple_input_add(httpconn->fd,
+				PURPLE_INPUT_WRITE, httpconn_write_cb, httpconn);
+		purple_circ_buffer_append(httpconn->tx_buf, data + res,
+			data_len - res);
+	}
+
+	return TRUE;
+}
+
+static char *
+msn_httpconn_proxy_auth(MsnHttpConn *httpconn)
+{
+	PurpleAccount *account;
+	PurpleProxyInfo *gpi;
+	const char *username, *password;
+	char *auth = NULL;
+
+	account = httpconn->session->account;
+
+	gpi = purple_proxy_get_setup(account);
+
+	if (gpi == NULL || !(purple_proxy_info_get_type(gpi) == PURPLE_PROXY_HTTP ||
+						 purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_ENVVAR))
+		return NULL;
+
+	username = purple_proxy_info_get_username(gpi);
+	password = purple_proxy_info_get_password(gpi);
+
+	if (username != NULL) {
+		char *tmp;
+		auth = g_strdup_printf("%s:%s", username, password ? password : "");
+		tmp = purple_base64_encode((const guchar *)auth, strlen(auth));
+		g_free(auth);
+		auth = g_strdup_printf("Proxy-Authorization: Basic %s\r\n", tmp);
+		g_free(tmp);
+	}
+
+	return auth;
+}
+
+static gboolean
+msn_httpconn_poll(gpointer data)
+{
+	MsnHttpConn *httpconn;
+	char *header;
+	char *auth;
+
+	httpconn = data;
+
+	g_return_val_if_fail(httpconn != NULL, FALSE);
+
+	if ((httpconn->host == NULL) || (httpconn->full_session_id == NULL))
+	{
+		/* There's no need to poll if the session is not fully established */
+		return TRUE;
+	}
+
+	if (httpconn->waiting_response)
+	{
+		/* There's no need to poll if we're already waiting for a response */
+		return TRUE;
+	}
+
+	auth = msn_httpconn_proxy_auth(httpconn);
+
+	header = g_strdup_printf(
+		"POST http://%s/gateway/gateway.dll?Action=poll&SessionID=%s HTTP/1.1\r\n"
+		"Accept: */*\r\n"
+		"Accept-Language: en-us\r\n"
+		"User-Agent: MSMSGS\r\n"
+		"Host: %s\r\n"
+		"Proxy-Connection: Keep-Alive\r\n"
+		"%s" /* Proxy auth */
+		"Connection: Keep-Alive\r\n"
+		"Pragma: no-cache\r\n"
+		"Content-Type: application/x-msn-messenger\r\n"
+		"Content-Length: 0\r\n\r\n",
+		httpconn->host,
+		httpconn->full_session_id,
+		httpconn->host,
+		auth ? auth : "");
+
+	g_free(auth);
+
+	if (write_raw(httpconn, header, strlen(header)))
+		httpconn->waiting_response = TRUE;
+
+	g_free(header);
+
+	return TRUE;
+}
+
+ssize_t
+msn_httpconn_write(MsnHttpConn *httpconn, const char *body, size_t body_len)
+{
+	char *params;
+	char *data;
+	int header_len;
+	char *auth;
+	const char *server_types[] = { "NS", "SB" };
+	const char *server_type;
+	char *host;
+	MsnServConn *servconn;
+
+	/* TODO: remove http data from servconn */
+
+	g_return_val_if_fail(httpconn != NULL, 0);
+	g_return_val_if_fail(body != NULL, 0);
+	g_return_val_if_fail(body_len > 0, 0);
+
+	servconn = httpconn->servconn;
+
+	if (httpconn->waiting_response)
+	{
+		MsnHttpQueueData *queue_data = g_new0(MsnHttpQueueData, 1);
+
+		queue_data->httpconn = httpconn;
+		queue_data->body     = g_memdup(body, body_len);
+		queue_data->body_len = body_len;
+
+		httpconn->queue = g_list_append(httpconn->queue, queue_data);
+
+		return body_len;
+	}
+
+	server_type = server_types[servconn->type];
+
+	if (httpconn->virgin)
+	{
+		host = "gateway.messenger.hotmail.com";
+
+		/* The first time servconn->host is the host we should connect to. */
+		params = g_strdup_printf("Action=open&Server=%s&IP=%s",
+								 server_type,
+								 servconn->host);
+		httpconn->virgin = FALSE;
+	}
+	else
+	{
+		/* The rest of the times servconn->host is the gateway host. */
+		host = httpconn->host;
+
+		if (host == NULL || httpconn->full_session_id == NULL)
+		{
+			purple_debug_warning("msn", "Attempted HTTP write before session is established\n");
+			return -1;
+		}
+
+		params = g_strdup_printf("SessionID=%s",
+			httpconn->full_session_id);
+	}
+
+	auth = msn_httpconn_proxy_auth(httpconn);
+
+	data = g_strdup_printf(
+		"POST http://%s/gateway/gateway.dll?%s HTTP/1.1\r\n"
+		"Accept: */*\r\n"
+		"Accept-Language: en-us\r\n"
+		"User-Agent: MSMSGS\r\n"
+		"Host: %s\r\n"
+		"Proxy-Connection: Keep-Alive\r\n"
+		"%s" /* Proxy auth */
+		"Connection: Keep-Alive\r\n"
+		"Pragma: no-cache\r\n"
+		"Content-Type: application/x-msn-messenger\r\n"
+		"Content-Length: %d\r\n\r\n",
+		host,
+		params,
+		host,
+		auth ? auth : "",
+		(int) body_len);
+
+	g_free(params);
+
+	g_free(auth);
+
+	header_len = strlen(data);
+	data = g_realloc(data, header_len + body_len);
+	memcpy(data + header_len, body, body_len);
+
+	if (write_raw(httpconn, data, header_len + body_len))
+		httpconn->waiting_response = TRUE;
+
+	g_free(data);
+
+	return body_len;
+}
+
+MsnHttpConn *
+msn_httpconn_new(MsnServConn *servconn)
+{
+	MsnHttpConn *httpconn;
+
+	g_return_val_if_fail(servconn != NULL, NULL);
+
+	httpconn = g_new0(MsnHttpConn, 1);
+
+	purple_debug_info("msn", "new httpconn (%p)\n", httpconn);
+
+	/* TODO: Remove this */
+	httpconn->session = servconn->session;
+
+	httpconn->servconn = servconn;
+
+	httpconn->tx_buf = purple_circ_buffer_new(MSN_BUF_LEN);
+	httpconn->tx_handler = 0;
+
+	return httpconn;
+}
+
+void
+msn_httpconn_destroy(MsnHttpConn *httpconn)
+{
+	g_return_if_fail(httpconn != NULL);
+
+	purple_debug_info("msn", "destroy httpconn (%p)\n", httpconn);
+
+	if (httpconn->connected)
+		msn_httpconn_disconnect(httpconn);
+
+	g_free(httpconn->full_session_id);
+
+	g_free(httpconn->session_id);
+
+	g_free(httpconn->host);
+
+	purple_circ_buffer_destroy(httpconn->tx_buf);
+	if (httpconn->tx_handler > 0)
+		purple_input_remove(httpconn->tx_handler);
+
+	g_free(httpconn);
+}
+
+static void
+connect_cb(gpointer data, gint source, const gchar *error_message)
+{
+	MsnHttpConn *httpconn;
+
+	httpconn = data;
+	httpconn->connect_data = NULL;
+	httpconn->fd = source;
+
+	if (source >= 0)
+	{
+		httpconn->inpa = purple_input_add(httpconn->fd, PURPLE_INPUT_READ,
+			read_cb, data);
+
+		httpconn->timer = purple_timeout_add(2000, msn_httpconn_poll, httpconn);
+
+		msn_httpconn_process_queue(httpconn);
+	}
+	else
+	{
+		purple_debug_error("msn", "HTTP: Connection error\n");
+		msn_servconn_got_error(httpconn->servconn, MSN_SERVCONN_ERROR_CONNECT);
+	}
+}
+
+gboolean
+msn_httpconn_connect(MsnHttpConn *httpconn, const char *host, int port)
+{
+	g_return_val_if_fail(httpconn != NULL, FALSE);
+	g_return_val_if_fail(host     != NULL, FALSE);
+	g_return_val_if_fail(port      > 0,    FALSE);
+
+	if (httpconn->connected)
+		msn_httpconn_disconnect(httpconn);
+
+	httpconn->connect_data = purple_proxy_connect(NULL, httpconn->session->account,
+		host, 80, connect_cb, httpconn);
+
+	if (httpconn->connect_data != NULL)
+	{
+		httpconn->waiting_response = TRUE;
+		httpconn->connected = TRUE;
+	}
+
+	return httpconn->connected;
+}
+
+void
+msn_httpconn_disconnect(MsnHttpConn *httpconn)
+{
+	g_return_if_fail(httpconn != NULL);
+
+	if (!httpconn->connected)
+		return;
+
+	if (httpconn->connect_data != NULL)
+	{
+		purple_proxy_connect_cancel(httpconn->connect_data);
+		httpconn->connect_data = NULL;
+	}
+
+	if (httpconn->timer)
+	{
+		purple_timeout_remove(httpconn->timer);
+		httpconn->timer = 0;
+	}
+
+	if (httpconn->inpa > 0)
+	{
+		purple_input_remove(httpconn->inpa);
+		httpconn->inpa = 0;
+	}
+
+	close(httpconn->fd);
+	httpconn->fd = -1;
+
+	g_free(httpconn->rx_buf);
+	httpconn->rx_buf = NULL;
+	httpconn->rx_len = 0;
+
+	httpconn->connected = FALSE;
+
+	/* msn_servconn_disconnect(httpconn->servconn); */
+}
============================================================
--- libpurple/protocols/msnp9/httpconn.h	e40d1fa32618034a5b18e3783648d7f1cc536b3b
+++ libpurple/protocols/msnp9/httpconn.h	e40d1fa32618034a5b18e3783648d7f1cc536b3b
@@ -0,0 +1,111 @@
+/**
+ * @file httpconn.h HTTP connection
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_HTTPCONN_H_
+#define _MSN_HTTPCONN_H_
+
+typedef struct _MsnHttpConn MsnHttpConn;
+
+#include "circbuffer.h"
+#include "servconn.h"
+
+/**
+ * An HTTP Connection.
+ */
+struct _MsnHttpConn
+{
+	MsnSession *session; /**< The MSN Session. */
+	MsnServConn *servconn; /**< The connection object. */
+
+	PurpleProxyConnectData *connect_data;
+
+	char *full_session_id; /**< The full session id. */
+	char *session_id; /**< The trimmed session id. */
+
+	int timer; /**< The timer for polling. */
+
+	gboolean waiting_response; /**< The flag that states if we are waiting
+								 a response from the server. */
+	gboolean connected;        /**< The flag that states if the connection is on. */
+	gboolean virgin;           /**< The flag that states if this connection
+								 should specify the host (not gateway) to
+								 connect to. */
+
+	char *host; /**< The HTTP gateway host. */
+	GList *queue; /**< The queue of data chunks to write. */
+
+	int fd; /**< The connection's file descriptor. */
+	guint inpa; /**< The connection's input handler. */
+
+	char *rx_buf; /**< The receive buffer. */
+	int rx_len; /**< The receive buffer length. */
+
+	PurpleCircBuffer *tx_buf;
+	guint tx_handler;
+};
+
+/**
+ * Creates a new HTTP connection object.
+ *
+ * @param servconn The connection object.
+ *
+ * @return The new object.
+ */
+MsnHttpConn *msn_httpconn_new(MsnServConn *servconn);
+
+/**
+ * Destroys an HTTP connection object.
+ *
+ * @param httpconn The HTTP connection object.
+ */
+void msn_httpconn_destroy(MsnHttpConn *httpconn);
+
+/**
+ * Writes a chunk of data to the HTTP connection.
+ *
+ * @param servconn    The server connection.
+ * @param data        The data to write.
+ * @param data_len    The size of the data to write.
+ *
+ * @return The number of bytes written.
+ */
+ssize_t msn_httpconn_write(MsnHttpConn *httpconn, const char *data, size_t data_len);
+
+/**
+ * Connects the HTTP connection object to a host.
+ *
+ * @param httpconn The HTTP connection object.
+ * @param host The host to connect to.
+ * @param port The port to connect to.
+ */
+gboolean msn_httpconn_connect(MsnHttpConn *httpconn,
+							  const char *host, int port);
+
+/**
+ * Disconnects the HTTP connection object.
+ *
+ * @param httpconn The HTTP connection object.
+ */
+void msn_httpconn_disconnect(MsnHttpConn *httpconn);
+
+#endif /* _MSN_HTTPCONN_H_ */
============================================================
--- libpurple/protocols/msnp9/msg.c	df21cf628802ad67fb9a8759fb3a188c56d391fa
+++ libpurple/protocols/msnp9/msg.c	df21cf628802ad67fb9a8759fb3a188c56d391fa
@@ -0,0 +1,787 @@
+/**
+ * @file msg.c Message functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "msg.h"
+
+MsnMessage *
+msn_message_new(MsnMsgType type)
+{
+	MsnMessage *msg;
+
+	msg = g_new0(MsnMessage, 1);
+	msg->type = type;
+
+#ifdef MSN_DEBUG_MSG
+	purple_debug_info("msn", "message new (%p)(%d)\n", msg, type);
+#endif
+
+	msg->attr_table = g_hash_table_new_full(g_str_hash, g_str_equal,
+											g_free, g_free);
+
+	msn_message_ref(msg);
+
+	return msg;
+}
+
+void
+msn_message_destroy(MsnMessage *msg)
+{
+	g_return_if_fail(msg != NULL);
+
+	if (msg->ref_count > 0)
+	{
+		msn_message_unref(msg);
+
+		return;
+	}
+
+#ifdef MSN_DEBUG_MSG
+	purple_debug_info("msn", "message destroy (%p)\n", msg);
+#endif
+
+	if (msg->remote_user != NULL)
+		g_free(msg->remote_user);
+
+	if (msg->body != NULL)
+		g_free(msg->body);
+
+	if (msg->content_type != NULL)
+		g_free(msg->content_type);
+
+	if (msg->charset != NULL)
+		g_free(msg->charset);
+
+	g_hash_table_destroy(msg->attr_table);
+	g_list_free(msg->attr_list);
+
+	g_free(msg);
+}
+
+MsnMessage *
+msn_message_ref(MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	msg->ref_count++;
+
+#ifdef MSN_DEBUG_MSG
+	purple_debug_info("msn", "message ref (%p)[%d]\n", msg, msg->ref_count);
+#endif
+
+	return msg;
+}
+
+MsnMessage *
+msn_message_unref(MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+	g_return_val_if_fail(msg->ref_count > 0, NULL);
+
+	msg->ref_count--;
+
+#ifdef MSN_DEBUG_MSG
+	purple_debug_info("msn", "message unref (%p)[%d]\n", msg, msg->ref_count);
+#endif
+
+	if (msg->ref_count == 0)
+	{
+		msn_message_destroy(msg);
+
+		return NULL;
+	}
+
+	return msg;
+}
+
+MsnMessage *
+msn_message_new_plain(const char *message)
+{
+	MsnMessage *msg;
+	char *message_cr;
+
+	msg = msn_message_new(MSN_MSG_TEXT);
+	msn_message_set_attr(msg, "User-Agent", PACKAGE_NAME "/" DISPLAY_VERSION);
+	msn_message_set_content_type(msg, "text/plain");
+	msn_message_set_charset(msg, "UTF-8");
+	msn_message_set_flag(msg, 'A');
+	msn_message_set_attr(msg, "X-MMS-IM-Format",
+						 "FN=MS%20Sans%20Serif; EF=; CO=0; PF=0");
+
+	message_cr = purple_str_add_cr(message);
+	msn_message_set_bin_data(msg, message_cr, strlen(message_cr));
+	g_free(message_cr);
+
+	return msg;
+}
+
+MsnMessage *
+msn_message_new_msnslp(void)
+{
+	MsnMessage *msg;
+
+	msg = msn_message_new(MSN_MSG_SLP);
+
+	msn_message_set_attr(msg, "User-Agent", NULL);
+
+	msg->msnslp_message = TRUE;
+
+	msn_message_set_flag(msg, 'D');
+	msn_message_set_content_type(msg, "application/x-msnmsgrp2p");
+
+	return msg;
+}
+
+MsnMessage *
+msn_message_new_nudge(void)
+{
+	MsnMessage *msg;
+
+	msg = msn_message_new(MSN_MSG_NUDGE);
+	msn_message_set_content_type(msg, "text/x-msnmsgr-datacast\r\n");
+	msn_message_set_flag(msg, 'N');
+	msn_message_set_attr(msg,"ID","1\r\n");
+
+	return msg;
+}
+
+void
+msn_message_parse_slp_body(MsnMessage *msg, const char *body, size_t len)
+{
+	MsnSlpHeader header;
+	const char *tmp;
+	int body_len;
+
+	tmp = body;
+
+	if (len < sizeof(header)) {
+		g_return_if_reached();
+	}
+
+	/* Import the header. */
+	memcpy(&header, tmp, sizeof(header));
+	tmp += sizeof(header);
+
+	msg->msnslp_header.session_id = GUINT32_FROM_LE(header.session_id);
+	msg->msnslp_header.id         = GUINT32_FROM_LE(header.id);
+	msg->msnslp_header.offset     = GUINT64_FROM_LE(header.offset);
+	msg->msnslp_header.total_size = GUINT64_FROM_LE(header.total_size);
+	msg->msnslp_header.length     = GUINT32_FROM_LE(header.length);
+	msg->msnslp_header.flags      = GUINT32_FROM_LE(header.flags);
+	msg->msnslp_header.ack_id     = GUINT32_FROM_LE(header.ack_id);
+	msg->msnslp_header.ack_sub_id = GUINT32_FROM_LE(header.ack_sub_id);
+	msg->msnslp_header.ack_size   = GUINT64_FROM_LE(header.ack_size);
+
+	/* Import the body. */
+	body_len = len - (tmp - body);
+	/* msg->body_len = msg->msnslp_header.length; */
+
+	if (body_len > 0) {
+		msg->body_len = len - (tmp - body);
+		msg->body = g_malloc0(msg->body_len + 1);
+		memcpy(msg->body, tmp, msg->body_len);
+		tmp += body_len;
+	}
+}
+
+void
+msn_message_parse_payload(MsnMessage *msg,
+						  const char *payload, size_t payload_len)
+{
+	char *tmp_base, *tmp;
+	const char *content_type;
+	char *end;
+	char **elems, **cur, **tokens;
+
+	g_return_if_fail(payload != NULL);
+
+	tmp_base = tmp = g_malloc0(payload_len + 1);
+	memcpy(tmp_base, payload, payload_len);
+
+	/* Parse the attributes. */
+	end = strstr(tmp, "\r\n\r\n");
+	/* TODO? some clients use \r delimiters instead of \r\n, the official client
+	 * doesn't send such messages, but does handle receiving them. We'll just
+	 * avoid crashing for now */
+	if (end == NULL) {
+		g_free(tmp_base);
+		g_return_if_reached();
+	}
+	*end = '\0';
+
+	elems = g_strsplit(tmp, "\r\n", 0);
+
+	for (cur = elems; *cur != NULL; cur++)
+	{
+		const char *key, *value;
+
+		tokens = g_strsplit(*cur, ": ", 2);
+
+		key = tokens[0];
+		value = tokens[1];
+
+		if (!strcmp(key, "MIME-Version"))
+		{
+			g_strfreev(tokens);
+			continue;
+		}
+
+		if (!strcmp(key, "Content-Type"))
+		{
+			char *charset, *c;
+
+			if ((c = strchr(value, ';')) != NULL)
+			{
+				if ((charset = strchr(c, '=')) != NULL)
+				{
+					charset++;
+					msn_message_set_charset(msg, charset);
+				}
+
+				*c = '\0';
+			}
+
+			msn_message_set_content_type(msg, value);
+		}
+		else
+		{
+			msn_message_set_attr(msg, key, value);
+		}
+
+		g_strfreev(tokens);
+	}
+
+	g_strfreev(elems);
+
+	/* Proceed to the end of the "\r\n\r\n" */
+	tmp = end + 4;
+
+	/* Now we *should* be at the body. */
+	content_type = msn_message_get_content_type(msg);
+
+	if (content_type != NULL &&
+		!strcmp(content_type, "application/x-msnmsgrp2p"))
+	{
+		MsnSlpHeader header;
+		MsnSlpFooter footer;
+		int body_len;
+
+		if (payload_len - (tmp - tmp_base) < sizeof(header)) {
+			g_free(tmp_base);
+			g_return_if_reached();
+		}
+
+		msg->msnslp_message = TRUE;
+
+		/* Import the header. */
+		memcpy(&header, tmp, sizeof(header));
+		tmp += sizeof(header);
+
+		msg->msnslp_header.session_id = GUINT32_FROM_LE(header.session_id);
+		msg->msnslp_header.id         = GUINT32_FROM_LE(header.id);
+		msg->msnslp_header.offset     = GUINT64_FROM_LE(header.offset);
+		msg->msnslp_header.total_size = GUINT64_FROM_LE(header.total_size);
+		msg->msnslp_header.length     = GUINT32_FROM_LE(header.length);
+		msg->msnslp_header.flags      = GUINT32_FROM_LE(header.flags);
+		msg->msnslp_header.ack_id     = GUINT32_FROM_LE(header.ack_id);
+		msg->msnslp_header.ack_sub_id = GUINT32_FROM_LE(header.ack_sub_id);
+		msg->msnslp_header.ack_size   = GUINT64_FROM_LE(header.ack_size);
+
+		body_len = payload_len - (tmp - tmp_base) - sizeof(footer);
+
+		/* Import the body. */
+		if (body_len > 0) {
+			msg->body_len = body_len;
+			msg->body = g_malloc0(msg->body_len + 1);
+			memcpy(msg->body, tmp, msg->body_len);
+			tmp += body_len;
+		}
+
+		/* Import the footer. */
+		if (body_len >= 0) {
+			memcpy(&footer, tmp, sizeof(footer));
+			tmp += sizeof(footer);
+			msg->msnslp_footer.value = GUINT32_FROM_BE(footer.value);
+		}
+	}
+	else
+	{
+		if (payload_len - (tmp - tmp_base) > 0) {
+			msg->body_len = payload_len - (tmp - tmp_base);
+			msg->body = g_malloc0(msg->body_len + 1);
+			memcpy(msg->body, tmp, msg->body_len);
+		}
+	}
+
+	g_free(tmp_base);
+}
+
+MsnMessage *
+msn_message_new_from_cmd(MsnSession *session, MsnCommand *cmd)
+{
+	MsnMessage *msg;
+
+	g_return_val_if_fail(cmd != NULL, NULL);
+
+	msg = msn_message_new(MSN_MSG_UNKNOWN);
+
+	msg->remote_user = g_strdup(cmd->params[0]);
+	/* msg->size = atoi(cmd->params[2]); */
+	msg->cmd = cmd;
+
+	return msg;
+}
+
+char *
+msn_message_gen_slp_body(MsnMessage *msg, size_t *ret_size)
+{
+	MsnSlpHeader header;
+
+	char *tmp, *base;
+	const void *body;
+	size_t len, body_len;
+
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	len = MSN_BUF_LEN;
+
+	base = tmp = g_malloc(len + 1);
+
+	body = msn_message_get_bin_data(msg, &body_len);
+
+	header.session_id = GUINT32_TO_LE(msg->msnslp_header.session_id);
+	header.id         = GUINT32_TO_LE(msg->msnslp_header.id);
+	header.offset     = GUINT64_TO_LE(msg->msnslp_header.offset);
+	header.total_size = GUINT64_TO_LE(msg->msnslp_header.total_size);
+	header.length     = GUINT32_TO_LE(msg->msnslp_header.length);
+	header.flags      = GUINT32_TO_LE(msg->msnslp_header.flags);
+	header.ack_id     = GUINT32_TO_LE(msg->msnslp_header.ack_id);
+	header.ack_sub_id = GUINT32_TO_LE(msg->msnslp_header.ack_sub_id);
+	header.ack_size   = GUINT64_TO_LE(msg->msnslp_header.ack_size);
+
+	memcpy(tmp, &header, 48);
+	tmp += 48;
+
+	if (body != NULL)
+	{
+		memcpy(tmp, body, body_len);
+		tmp += body_len;
+	}
+
+	if (ret_size != NULL)
+		*ret_size = tmp - base;
+
+	return base;
+}
+
+char *
+msn_message_gen_payload(MsnMessage *msg, size_t *ret_size)
+{
+	GList *l;
+	char *n, *base, *end;
+	int len;
+	size_t body_len = 0;
+	const void *body;
+
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	len = MSN_BUF_LEN;
+
+	base = n = end = g_malloc(len + 1);
+	end += len;
+
+	/* Standard header. */
+	if (msg->charset == NULL)
+	{
+		g_snprintf(n, len,
+				   "MIME-Version: 1.0\r\n"
+				   "Content-Type: %s\r\n",
+				   msg->content_type);
+	}
+	else
+	{
+		g_snprintf(n, len,
+				   "MIME-Version: 1.0\r\n"
+				   "Content-Type: %s; charset=%s\r\n",
+				   msg->content_type, msg->charset);
+	}
+
+	n += strlen(n);
+
+	for (l = msg->attr_list; l != NULL; l = l->next)
+	{
+		const char *key;
+		const char *value;
+
+		key = l->data;
+		value = msn_message_get_attr(msg, key);
+
+		g_snprintf(n, end - n, "%s: %s\r\n", key, value);
+		n += strlen(n);
+	}
+
+	n += g_strlcpy(n, "\r\n", end - n);
+
+	body = msn_message_get_bin_data(msg, &body_len);
+
+	if (msg->msnslp_message)
+	{
+		MsnSlpHeader header;
+		MsnSlpFooter footer;
+
+		header.session_id = GUINT32_TO_LE(msg->msnslp_header.session_id);
+		header.id         = GUINT32_TO_LE(msg->msnslp_header.id);
+		header.offset     = GUINT64_TO_LE(msg->msnslp_header.offset);
+		header.total_size = GUINT64_TO_LE(msg->msnslp_header.total_size);
+		header.length     = GUINT32_TO_LE(msg->msnslp_header.length);
+		header.flags      = GUINT32_TO_LE(msg->msnslp_header.flags);
+		header.ack_id     = GUINT32_TO_LE(msg->msnslp_header.ack_id);
+		header.ack_sub_id = GUINT32_TO_LE(msg->msnslp_header.ack_sub_id);
+		header.ack_size   = GUINT64_TO_LE(msg->msnslp_header.ack_size);
+
+		memcpy(n, &header, 48);
+		n += 48;
+
+		if (body != NULL)
+		{
+			memcpy(n, body, body_len);
+
+			n += body_len;
+		}
+
+		footer.value = GUINT32_TO_BE(msg->msnslp_footer.value);
+
+		memcpy(n, &footer, 4);
+		n += 4;
+	}
+	else
+	{
+		if (body != NULL)
+		{
+			memcpy(n, body, body_len);
+			n += body_len;
+		}
+	}
+
+	if (ret_size != NULL)
+	{
+		*ret_size = n - base;
+
+		if (*ret_size > 1664)
+			*ret_size = 1664;
+	}
+
+	return base;
+}
+
+void
+msn_message_set_flag(MsnMessage *msg, char flag)
+{
+	g_return_if_fail(msg != NULL);
+	g_return_if_fail(flag != 0);
+
+	msg->flag = flag;
+}
+
+char
+msn_message_get_flag(const MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, 0);
+
+	return msg->flag;
+}
+
+void
+msn_message_set_bin_data(MsnMessage *msg, const void *data, size_t len)
+{
+	g_return_if_fail(msg != NULL);
+
+	/* There is no need to waste memory on data we cannot send anyway */
+	if (len > 1664)
+		len = 1664;
+
+	if (msg->body != NULL)
+		g_free(msg->body);
+
+	if (data != NULL && len > 0)
+	{
+		msg->body = g_malloc0(len + 1);
+		memcpy(msg->body, data, len);
+		msg->body_len = len;
+	}
+	else
+	{
+		msg->body = NULL;
+		msg->body_len = 0;
+	}
+}
+
+const void *
+msn_message_get_bin_data(const MsnMessage *msg, size_t *len)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	if (len)
+		*len = msg->body_len;
+
+	return msg->body;
+}
+
+void
+msn_message_set_content_type(MsnMessage *msg, const char *type)
+{
+	g_return_if_fail(msg != NULL);
+
+	if (msg->content_type != NULL)
+		g_free(msg->content_type);
+
+	msg->content_type = (type != NULL) ? g_strdup(type) : NULL;
+}
+
+const char *
+msn_message_get_content_type(const MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	return msg->content_type;
+}
+
+void
+msn_message_set_charset(MsnMessage *msg, const char *charset)
+{
+	g_return_if_fail(msg != NULL);
+
+	if (msg->charset != NULL)
+		g_free(msg->charset);
+
+	msg->charset = (charset != NULL) ? g_strdup(charset) : NULL;
+}
+
+const char *
+msn_message_get_charset(const MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	return msg->charset;
+}
+
+void
+msn_message_set_attr(MsnMessage *msg, const char *attr, const char *value)
+{
+	const char *temp;
+	char *new_attr;
+
+	g_return_if_fail(msg != NULL);
+	g_return_if_fail(attr != NULL);
+
+	temp = msn_message_get_attr(msg, attr);
+
+	if (value == NULL)
+	{
+		if (temp != NULL)
+		{
+			GList *l;
+
+			for (l = msg->attr_list; l != NULL; l = l->next)
+			{
+				if (!g_ascii_strcasecmp(l->data, attr))
+				{
+					msg->attr_list = g_list_remove(msg->attr_list, l->data);
+
+					break;
+				}
+			}
+
+			g_hash_table_remove(msg->attr_table, attr);
+		}
+
+		return;
+	}
+
+	new_attr = g_strdup(attr);
+
+	g_hash_table_insert(msg->attr_table, new_attr, g_strdup(value));
+
+	if (temp == NULL)
+		msg->attr_list = g_list_append(msg->attr_list, new_attr);
+}
+
+const char *
+msn_message_get_attr(const MsnMessage *msg, const char *attr)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+	g_return_val_if_fail(attr != NULL, NULL);
+
+	return g_hash_table_lookup(msg->attr_table, attr);
+}
+
+GHashTable *
+msn_message_get_hashtable_from_body(const MsnMessage *msg)
+{
+	GHashTable *table;
+	size_t body_len;
+	const char *body;
+	char **elems, **cur, **tokens, *body_str;
+
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+	body = msn_message_get_bin_data(msg, &body_len);
+
+	g_return_val_if_fail(body != NULL, NULL);
+
+	body_str = g_strndup(body, body_len);
+	elems = g_strsplit(body_str, "\r\n", 0);
+	g_free(body_str);
+
+	for (cur = elems; *cur != NULL; cur++)
+	{
+		if (**cur == '\0')
+			break;
+
+		tokens = g_strsplit(*cur, ": ", 2);
+
+		if (tokens[0] != NULL && tokens[1] != NULL)
+			g_hash_table_insert(table, tokens[0], tokens[1]);
+
+		g_free(tokens);
+	}
+
+	g_strfreev(elems);
+
+	return table;
+}
+
+char *
+msn_message_to_string(MsnMessage *msg)
+{
+	size_t body_len;
+	const char *body;
+
+	g_return_val_if_fail(msg != NULL, NULL);
+	g_return_val_if_fail(msg->type == MSN_MSG_TEXT, NULL);
+
+	body = msn_message_get_bin_data(msg, &body_len);
+
+	return g_strndup(body, body_len);
+}
+
+void
+msn_message_show_readable(MsnMessage *msg, const char *info,
+						  gboolean text_body)
+{
+	GString *str;
+	size_t body_len;
+	const char *body;
+	GList *l;
+
+	g_return_if_fail(msg != NULL);
+
+	str = g_string_new(NULL);
+
+	/* Standard header. */
+	if (msg->charset == NULL)
+	{
+		g_string_append_printf(str,
+				   "MIME-Version: 1.0\r\n"
+				   "Content-Type: %s\r\n",
+				   msg->content_type);
+	}
+	else
+	{
+		g_string_append_printf(str,
+				   "MIME-Version: 1.0\r\n"
+				   "Content-Type: %s; charset=%s\r\n",
+				   msg->content_type, msg->charset);
+	}
+
+	for (l = msg->attr_list; l; l = l->next)
+	{
+		char *key;
+		const char *value;
+
+		key = l->data;
+		value = msn_message_get_attr(msg, key);
+
+		g_string_append_printf(str, "%s: %s\r\n", key, value);
+	}
+
+	g_string_append(str, "\r\n");
+
+	body = msn_message_get_bin_data(msg, &body_len);
+
+	if (msg->msnslp_message)
+	{
+		g_string_append_printf(str, "Session ID: %u\r\n", msg->msnslp_header.session_id);
+		g_string_append_printf(str, "ID:         %u\r\n", msg->msnslp_header.id);
+		g_string_append_printf(str, "Offset:     %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.offset);
+		g_string_append_printf(str, "Total size: %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.total_size);
+		g_string_append_printf(str, "Length:     %u\r\n", msg->msnslp_header.length);
+		g_string_append_printf(str, "Flags:      0x%x\r\n", msg->msnslp_header.flags);
+		g_string_append_printf(str, "ACK ID:     %u\r\n", msg->msnslp_header.ack_id);
+		g_string_append_printf(str, "SUB ID:     %u\r\n", msg->msnslp_header.ack_sub_id);
+		g_string_append_printf(str, "ACK Size:   %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.ack_size);
+
+#ifdef MSN_DEBUG_SLP_VERBOSE
+		if (body != NULL)
+		{
+			if (text_body)
+			{
+				g_string_append_len(str, body, body_len);
+				if (body[body_len - 1] == '\0')
+				{
+					str->len--;
+					g_string_append(str, " 0x00");
+				}
+				g_string_append(str, "\r\n");
+			}
+			else
+			{
+				int i;
+				for (i = 0; i < msg->body_len; i++)
+				{
+					g_string_append_printf(str, "%.2hhX ", body[i]);
+					if ((i % 16) == 15)
+						g_string_append(str, "\r\n");
+				}
+				g_string_append(str, "\r\n");
+			}
+		}
+#endif
+
+		g_string_append_printf(str, "Footer:     %u\r\n", msg->msnslp_footer.value);
+	}
+	else
+	{
+		if (body != NULL)
+		{
+			g_string_append_len(str, body, body_len);
+			g_string_append(str, "\r\n");
+		}
+	}
+
+	purple_debug_info("msn", "Message %s:\n{%s}\n", info, str->str);
+
+	g_string_free(str, TRUE);
+}
============================================================
--- libpurple/protocols/msnp9/msg.h	324f7868d7cf78c423a56614c64b77bb6578308a
+++ libpurple/protocols/msnp9/msg.h	324f7868d7cf78c423a56614c64b77bb6578308a
@@ -0,0 +1,349 @@
+/**
+ * @file msg.h Message functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_MSG_H_
+#define _MSN_MSG_H_
+
+typedef struct _MsnMessage MsnMessage;
+
+#include "session.h"
+#include "user.h"
+
+#include "command.h"
+#include "transaction.h"
+
+typedef void (*MsnMsgCb)(MsnMessage *, void *data);
+
+/*
+typedef enum
+{
+	MSN_MSG_NORMAL,
+	MSN_MSG_SLP_SB,
+	MSN_MSG_SLP_DC
+
+} MsnMsgType;
+*/
+
+typedef enum
+{
+	MSN_MSG_UNKNOWN,
+	MSN_MSG_TEXT,
+	MSN_MSG_TYPING,
+	MSN_MSG_CAPS,
+	MSN_MSG_SLP,
+	MSN_MSG_NUDGE
+
+} MsnMsgType;
+
+typedef enum
+{
+	MSN_MSG_ERROR_NONE, /**< No error. */
+	MSN_MSG_ERROR_TIMEOUT, /**< The message timedout. */
+	MSN_MSG_ERROR_NAK, /**< The message could not be sent. */
+	MSN_MSG_ERROR_SB, /**< The error comes from the switchboard. */
+	MSN_MSG_ERROR_UNKNOWN /**< An unknown error occurred. */
+
+} MsnMsgErrorType;
+
+typedef struct
+{
+	guint32 session_id;
+	guint32 id;
+	guint64 offset;
+	guint64 total_size;
+	guint32 length;
+	guint32 flags;
+	guint32 ack_id;
+	guint32 ack_sub_id;
+	guint64 ack_size;
+
+} MsnSlpHeader;
+
+typedef struct
+{
+	guint32 value;
+
+} MsnSlpFooter;
+
+/**
+ * A message.
+ */
+struct _MsnMessage
+{
+	size_t ref_count;           /**< The reference count.       */
+
+	MsnMsgType type;
+
+	gboolean msnslp_message;
+
+	char *remote_user;
+	char flag;
+
+	char *content_type;
+	char *charset;
+	char *body;
+	gsize body_len;
+
+	MsnSlpHeader msnslp_header;
+	MsnSlpFooter msnslp_footer;
+
+	GHashTable *attr_table;
+	GList *attr_list;
+
+	gboolean ack_ref;           /**< A flag that states if this message has
+								  been ref'ed for using it in a callback. */
+
+	MsnCommand *cmd;
+	MsnTransaction *trans;
+
+	MsnMsgCb ack_cb; /**< The callback to call when we receive an ACK of this
+					   message. */
+	MsnMsgCb nak_cb; /**< The callback to call when we receive a NAK of this
+					   message. */
+	void *ack_data; /**< The data used by callbacks. */
+
+	MsnMsgErrorType error; /**< The error of the message. */
+};
+
+/**
+ * Creates a new, empty message.
+ *
+ * @return A new message.
+ */
+MsnMessage *msn_message_new(MsnMsgType type);
+
+/**
+ * Creates a new, empty MSNSLP message.
+ *
+ * @return A new MSNSLP message.
+ */
+MsnMessage *msn_message_new_msnslp(void);
+
+/**
+ * Creates a new nudge message.
+ *
+ * @return A new nudge message.
+ */
+MsnMessage *msn_message_new_nudge(void);
+
+/**
+ * Creates a new plain message.
+ *
+ * @return A new plain message.
+ */
+MsnMessage *msn_message_new_plain(const char *message);
+
+/**
+ * Creates a MSNSLP ack message.
+ *
+ * @param acked_msg The message to acknowledge.
+ *
+ * @return A new MSNSLP ack message.
+ */
+MsnMessage *msn_message_new_msnslp_ack(MsnMessage *acked_msg);
+
+/**
+ * Creates a new message based off a command.
+ *
+ * @param session The MSN session.
+ * @param cmd     The command.
+ *
+ * @return The new message.
+ */
+MsnMessage *msn_message_new_from_cmd(MsnSession *session, MsnCommand *cmd);
+
+/**
+ * Parses the payload of a message.
+ *
+ * @param msg         The message.
+ * @param payload     The payload.
+ * @param payload_len The length of the payload.
+ */
+void msn_message_parse_payload(MsnMessage *msg, const char *payload,
+							   size_t payload_len);
+
+/**
+ * Destroys a message.
+ *
+ * @param msg The message to destroy.
+ */
+void msn_message_destroy(MsnMessage *msg);
+
+/**
+ * Increments the reference count on a message.
+ *
+ * @param msg The message.
+ *
+ * @return @a msg
+ */
+MsnMessage *msn_message_ref(MsnMessage *msg);
+
+/**
+ * Decrements the reference count on a message.
+ *
+ * This will destroy the structure if the count hits 0.
+ *
+ * @param msg The message.
+ *
+ * @return @a msg, or @c NULL if the new count is 0.
+ */
+MsnMessage *msn_message_unref(MsnMessage *msg);
+
+/**
+ * Generates the payload data of a message.
+ *
+ * @param msg      The message.
+ * @param ret_size The returned size of the payload.
+ *
+ * @return The payload data of the message.
+ */
+char *msn_message_gen_payload(MsnMessage *msg, size_t *ret_size);
+
+/**
+ * Sets the flag for an outgoing message.
+ *
+ * @param msg  The message.
+ * @param flag The flag.
+ */
+void msn_message_set_flag(MsnMessage *msg, char flag);
+
+/**
+ * Returns the flag for an outgoing message.
+ *
+ * @param msg The message.
+ *
+ * @return The flag.
+ */
+char msn_message_get_flag(const MsnMessage *msg);
+
+#if 0
+/**
+ * Sets the body of a message.
+ *
+ * @param msg  The message.
+ * @param body The body of the message.
+ */
+void msn_message_set_body(MsnMessage *msg, const char *body);
+
+/**
+ * Returns the body of the message.
+ *
+ * @param msg The message.
+ *
+ * @return The body of the message.
+ */
+const char *msn_message_get_body(const MsnMessage *msg);
+#endif
+/**
+ * Sets the binary content of the message.
+ *
+ * @param msg  The message.
+ * @param data The binary data.
+ * @param len  The length of the data.
+ */
+void msn_message_set_bin_data(MsnMessage *msg, const void *data, size_t len);
+
+/**
+ * Returns the binary content of the message.
+ *
+ * @param msg The message.
+ * @param len The returned length of the data.
+ *
+ * @return The binary data.
+ */
+const void *msn_message_get_bin_data(const MsnMessage *msg, size_t *len);
+
+/**
+ * Sets the content type in a message.
+ *
+ * @param msg  The message.
+ * @param type The content-type.
+ */
+void msn_message_set_content_type(MsnMessage *msg, const char *type);
+
+/**
+ * Returns the content type in a message.
+ *
+ * @param msg The message.
+ *
+ * @return The content-type.
+ */
+const char *msn_message_get_content_type(const MsnMessage *msg);
+
+/**
+ * Sets the charset in a message.
+ *
+ * @param msg     The message.
+ * @param charset The charset.
+ */
+void msn_message_set_charset(MsnMessage *msg, const char *charset);
+
+/**
+ * Returns the charset in a message.
+ *
+ * @param msg The message.
+ *
+ * @return The charset.
+ */
+const char *msn_message_get_charset(const MsnMessage *msg);
+
+/**
+ * Sets an attribute in a message.
+ *
+ * @param msg   The message.
+ * @param attr  The attribute name.
+ * @param value The attribute value.
+ */
+void msn_message_set_attr(MsnMessage *msg, const char *attr,
+						  const char *value);
+
+/**
+ * Returns an attribute from a message.
+ *
+ * @param msg  The message.
+ * @param attr The attribute.
+ *
+ * @return The value, or @c NULL if not found.
+ */
+const char *msn_message_get_attr(const MsnMessage *msg, const char *attr);
+
+/**
+ * Parses the body and returns it in the form of a hashtable.
+ *
+ * @param msg The message.
+ *
+ * @return The resulting hashtable.
+ */
+GHashTable *msn_message_get_hashtable_from_body(const MsnMessage *msg);
+
+void msn_message_show_readable(MsnMessage *msg, const char *info,
+							   gboolean text_body);
+
+void msn_message_parse_slp_body(MsnMessage *msg, const char *body,
+								size_t len);
+
+char *msn_message_gen_slp_body(MsnMessage *msg, size_t *ret_size);
+
+char *msn_message_to_string(MsnMessage *msg);
+
+#endif /* _MSN_MSG_H_ */
============================================================
--- libpurple/protocols/msnp9/msn-utils.c	ddcf0627c9bb68b8317f5ac2ce782b560d1f25fa
+++ libpurple/protocols/msnp9/msn-utils.c	ddcf0627c9bb68b8317f5ac2ce782b560d1f25fa
@@ -0,0 +1,440 @@
+/**
+ * @file msn-utils.c Utility functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "msn-utils.h"
+
+void
+msn_parse_format(const char *mime, char **pre_ret, char **post_ret)
+{
+	char *cur;
+	GString *pre  = g_string_new(NULL);
+	GString *post = g_string_new(NULL);
+	unsigned int colors[3];
+
+	if (pre_ret  != NULL) *pre_ret  = NULL;
+	if (post_ret != NULL) *post_ret = NULL;
+
+	cur = strstr(mime, "FN=");
+
+	if (cur && (*(cur = cur + 3) != ';'))
+	{
+		pre = g_string_append(pre, "<FONT FACE=\"");
+
+		while (*cur && *cur != ';')
+		{
+			pre = g_string_append_c(pre, *cur);
+			cur++;
+		}
+
+		pre = g_string_append(pre, "\">");
+		post = g_string_prepend(post, "</FONT>");
+	}
+
+	cur = strstr(mime, "EF=");
+
+	if (cur && (*(cur = cur + 3) != ';'))
+	{
+		while (*cur && *cur != ';')
+		{
+			pre = g_string_append_c(pre, '<');
+			pre = g_string_append_c(pre, *cur);
+			pre = g_string_append_c(pre, '>');
+			post = g_string_prepend_c(post, '>');
+			post = g_string_prepend_c(post, *cur);
+			post = g_string_prepend_c(post, '/');
+			post = g_string_prepend_c(post, '<');
+			cur++;
+		}
+	}
+
+	cur = strstr(mime, "CO=");
+
+	if (cur && (*(cur = cur + 3) != ';'))
+	{
+		int i;
+
+		i = sscanf(cur, "%02x%02x%02x;", &colors[0], &colors[1], &colors[2]);
+
+		if (i > 0)
+		{
+			char tag[64];
+
+			if (i == 1)
+			{
+				colors[1] = 0;
+				colors[2] = 0;
+			}
+			else if (i == 2)
+			{
+				unsigned int temp = colors[0];
+
+				colors[0] = colors[1];
+				colors[1] = temp;
+				colors[2] = 0;
+			}
+			else if (i == 3)
+			{
+				unsigned int temp = colors[2];
+
+				colors[2] = colors[0];
+				colors[0] = temp;
+			}
+
+			g_snprintf(tag, sizeof(tag),
+					   "<FONT COLOR=\"#%02hhx%02hhx%02hhx\">",
+					   colors[0], colors[1], colors[2]);
+
+			pre = g_string_append(pre, tag);
+			post = g_string_prepend(post, "</FONT>");
+		}
+	}
+
+	cur = strstr(mime, "RL=");
+
+	if (cur && (*(cur = cur + 3) != ';'))
+	{
+		if (*cur == '1')
+		{
+			/* RTL text was received */
+			pre = g_string_append(pre, "<SPAN style=\"direction:rtl;text-align:right;\">");
+			post = g_string_prepend(post, "</SPAN>");
+		}
+	}
+
+	cur = g_strdup(purple_url_decode(pre->str));
+	g_string_free(pre, TRUE);
+
+	if (pre_ret != NULL)
+		*pre_ret = cur;
+	else
+		g_free(cur);
+
+	cur = g_strdup(purple_url_decode(post->str));
+	g_string_free(post, TRUE);
+
+	if (post_ret != NULL)
+		*post_ret = cur;
+	else
+		g_free(cur);
+}
+
+/*
+ * We need this because we're only supposed to encode spaces in the font
+ * names. purple_url_encode() isn't acceptable.
+ */
+static const char *
+encode_spaces(const char *str)
+{
+	static char buf[BUF_LEN];
+	const char *c;
+	char *d;
+
+	g_return_val_if_fail(str != NULL, NULL);
+
+	for (c = str, d = buf; *c != '\0'; c++)
+	{
+		if (*c == ' ')
+		{
+			*d++ = '%';
+			*d++ = '2';
+			*d++ = '0';
+		}
+		else
+			*d++ = *c;
+	}
+
+	return buf;
+}
+
+/*
+ * Taken from the zephyr plugin.
+ * This parses HTML formatting (put out by one of the gtkimhtml widgets
+ * and converts it to msn formatting. It doesn't deal with the tag closing,
+ * but gtkimhtml widgets give valid html.
+ * It currently deals properly with <b>, <u>, <i>, <font face=...>,
+ * <font color=...>, <span dir=...>, <span style="direction: ...">.
+ * It ignores <font back=...> and <font size=...>
+ */
+void
+msn_import_html(const char *html, char **attributes, char **message)
+{
+	int len, retcount = 0;
+	const char *c;
+	char *msg;
+	char *fontface = NULL;
+	char fonteffect[4];
+	char fontcolor[7];
+	char direction = '0';
+
+	gboolean has_bold = FALSE;
+	gboolean has_italic = FALSE;
+	gboolean has_underline = FALSE;
+	gboolean has_strikethrough = FALSE;
+
+	g_return_if_fail(html       != NULL);
+	g_return_if_fail(attributes != NULL);
+	g_return_if_fail(message    != NULL);
+
+	len = strlen(html);
+	msg = g_malloc0(len + 1);
+
+	memset(fontcolor, 0, sizeof(fontcolor));
+	strcat(fontcolor, "0");
+	memset(fonteffect, 0, sizeof(fonteffect));
+
+	for (c = html; *c != '\0';)
+	{
+		if (*c == '<')
+		{
+			if (!g_ascii_strncasecmp(c + 1, "br>", 3))
+			{
+				msg[retcount++] = '\r';
+				msg[retcount++] = '\n';
+				c += 4;
+			}
+			else if (!g_ascii_strncasecmp(c + 1, "i>", 2))
+			{
+				if (!has_italic)
+				{
+					strcat(fonteffect, "I");
+					has_italic = TRUE;
+				}
+				c += 3;
+			}
+			else if (!g_ascii_strncasecmp(c + 1, "b>", 2))
+			{
+				if (!has_bold)
+				{
+					strcat(fonteffect, "B");
+					has_bold = TRUE;
+				}
+				c += 3;
+			}
+			else if (!g_ascii_strncasecmp(c + 1, "u>", 2))
+			{
+				if (!has_underline)
+				{
+					strcat(fonteffect, "U");
+					has_underline = TRUE;
+				}
+				c += 3;
+			}
+			else if (!g_ascii_strncasecmp(c + 1, "s>", 2))
+			{
+				if (!has_strikethrough)
+				{
+					strcat(fonteffect, "S");
+					has_strikethrough = TRUE;
+				}
+				c += 3;
+			}
+			else if (!g_ascii_strncasecmp(c + 1, "a href=\"", 8))
+			{
+				c += 9;
+
+				if (!g_ascii_strncasecmp(c, "mailto:", 7))
+					c += 7;
+
+				while ((*c != '\0') && g_ascii_strncasecmp(c, "\">", 2))
+					msg[retcount++] = *c++;
+
+				if (*c != '\0')
+					c += 2;
+
+				/* ignore descriptive string */
+				while ((*c != '\0') && g_ascii_strncasecmp(c, "</a>", 4))
+					c++;
+
+				if (*c != '\0')
+					c += 4;
+			}
+			else if (!g_ascii_strncasecmp(c + 1, "span", 4))
+			{
+				/* Bi-directional text support using CSS properties in span tags */
+				c += 5;
+
+				while (*c != '\0' && *c != '>')
+				{
+					while (*c == ' ')
+						c++;
+					if (!g_ascii_strncasecmp(c, "dir=\"rtl\"", 9))
+					{
+						c += 9;
+						direction = '1';
+					}
+					else if (!g_ascii_strncasecmp(c, "style=\"", 7))
+					{
+						/* Parse inline CSS attributes */
+						char *attributes;
+						int attr_len = 0;
+						c += 7;
+						while (*(c + attr_len) != '\0' && *(c + attr_len) != '"')
+							attr_len++;
+						if (*(c + attr_len) == '"')
+						{
+							char *attr_dir;
+							attributes = g_strndup(c, attr_len);
+							attr_dir = purple_markup_get_css_property(attributes, "direction");
+							if (attr_dir && (!g_ascii_strncasecmp(attr_dir, "RTL", 3)))
+								direction = '1';
+							g_free(attr_dir);
+							g_free(attributes);
+						}
+
+					}
+					else
+					{
+						c++;
+					}
+				}
+				if (*c == '>')
+					c++;
+			}
+			else if (!g_ascii_strncasecmp(c + 1, "font", 4))
+			{
+				c += 5;
+
+				while ((*c != '\0') && !g_ascii_strncasecmp(c, " ", 1))
+					c++;
+
+				if (!g_ascii_strncasecmp(c, "color=\"#", 7))
+				{
+					c += 8;
+
+					fontcolor[0] = *(c + 4);
+					fontcolor[1] = *(c + 5);
+					fontcolor[2] = *(c + 2);
+					fontcolor[3] = *(c + 3);
+					fontcolor[4] = *c;
+					fontcolor[5] = *(c + 1);
+
+					c += 8;
+				}
+				else if (!g_ascii_strncasecmp(c, "face=\"", 6))
+				{
+					const char *end = NULL;
+					const char *comma = NULL;
+					unsigned int namelen = 0;
+
+					c += 6;
+					end = strchr(c, '\"');
+					comma = strchr(c, ',');
+
+					if (comma == NULL || comma > end)
+						namelen = (unsigned int)(end - c);
+					else
+						namelen = (unsigned int)(comma - c);
+
+					fontface = g_strndup(c, namelen);
+					c = end + 2;
+				}
+				else
+				{
+					/* Drop all unrecognized/misparsed font tags */
+					while ((*c != '\0') && g_ascii_strncasecmp(c, "\">", 2))
+						c++;
+
+					if (*c != '\0')
+						c += 2;
+				}
+			}
+			else
+			{
+				while ((*c != '\0') && (*c != '>'))
+					c++;
+				if (*c != '\0')
+					c++;
+			}
+		}
+		else if (*c == '&')
+		{
+			if (!g_ascii_strncasecmp(c, "&lt;", 4))
+			{
+				msg[retcount++] = '<';
+				c += 4;
+			}
+			else if (!g_ascii_strncasecmp(c, "&gt;", 4))
+			{
+				msg[retcount++] = '>';
+				c += 4;
+			}
+			else if (!g_ascii_strncasecmp(c, "&nbsp;", 6))
+			{
+				msg[retcount++] = ' ';
+				c += 6;
+			}
+			else if (!g_ascii_strncasecmp(c, "&quot;", 6))
+			{
+				msg[retcount++] = '"';
+				c += 6;
+			}
+			else if (!g_ascii_strncasecmp(c, "&amp;", 5))
+			{
+				msg[retcount++] = '&';
+				c += 5;
+			}
+			else if (!g_ascii_strncasecmp(c, "&apos;", 6))
+			{
+				msg[retcount++] = '\'';
+				c += 6;
+			}
+			else
+				msg[retcount++] = *c++;
+		}
+		else
+			msg[retcount++] = *c++;
+	}
+
+	if (fontface == NULL)
+		fontface = g_strdup("MS Sans Serif");
+
+	*attributes = g_strdup_printf("FN=%s; EF=%s; CO=%s; PF=0; RL=%c",
+								  encode_spaces(fontface),
+								  fonteffect, fontcolor, direction);
+	*message = g_strdup(msg);
+
+	g_free(fontface);
+	g_free(msg);
+}
+
+void
+msn_parse_socket(const char *str, char **ret_host, int *ret_port)
+{
+	char *host;
+	char *c;
+	int port;
+
+	host = g_strdup(str);
+
+	if ((c = strchr(host, ':')) != NULL)
+	{
+		*c = '\0';
+		port = atoi(c + 1);
+	}
+	else
+		port = 1863;
+
+	*ret_host = host;
+	*ret_port = port;
+}
============================================================
--- libpurple/protocols/msnp9/msn-utils.h	3c4555f0bcf425bea921f4b7111e3512492a697e
+++ libpurple/protocols/msnp9/msn-utils.h	3c4555f0bcf425bea921f4b7111e3512492a697e
@@ -0,0 +1,51 @@
+/**
+ * @file msn-utils.h Utility functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_UTILS_H_
+#define _MSN_UTILS_H_
+
+/**
+ * Parses the MSN message formatting into a format compatible with Purple.
+ *
+ * @param mime     The mime header with the formatting.
+ * @param pre_ret  The returned prefix string.
+ * @param post_ret The returned postfix string.
+ *
+ * @return The new message.
+ */
+void msn_parse_format(const char *mime, char **pre_ret, char **post_ret);
+
+/**
+ * Parses the Purple message formatting (html) into the MSN format.
+ *
+ * @param html			The html message to format.
+ * @param attributes	The returned attributes string.
+ * @param message		The returned message string.
+ *
+ * @return The new message.
+ */
+void msn_import_html(const char *html, char **attributes, char **message);
+
+void msn_parse_socket(const char *str, char **ret_host, int *ret_port);
+
+#endif /* _MSN_UTILS_H_ */
============================================================
--- libpurple/protocols/msnp9/msn.c	2c1978cae8fe46637c0d1be77779e37e35c8b6ee
+++ libpurple/protocols/msnp9/msn.c	2c1978cae8fe46637c0d1be77779e37e35c8b6ee
@@ -0,0 +1,2218 @@
+/**
+ * @file msn.c The MSN protocol plugin
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#define PHOTO_SUPPORT 1
+
+#include <glib.h>
+
+#include "msn.h"
+#include "accountopt.h"
+#include "msg.h"
+#include "page.h"
+#include "pluginpref.h"
+#include "prefs.h"
+#include "session.h"
+#include "state.h"
+#include "util.h"
+#include "cmds.h"
+#include "core.h"
+#include "prpl.h"
+#include "msn-utils.h"
+#include "version.h"
+
+#include "switchboard.h"
+#include "notification.h"
+#include "sync.h"
+#include "slplink.h"
+
+#if PHOTO_SUPPORT
+#include "imgstore.h"
+#endif
+
+typedef struct
+{
+	PurpleConnection *gc;
+	const char *passport;
+
+} MsnMobileData;
+
+typedef struct
+{
+	PurpleConnection *gc;
+	char *name;
+
+} MsnGetInfoData;
+
+typedef struct
+{
+	MsnGetInfoData *info_data;
+	char *stripped;
+	char *url_buffer;
+	PurpleNotifyUserInfo *user_info;
+	char *photo_url_text;
+
+} MsnGetInfoStepTwoData;
+
+typedef struct
+{
+	PurpleConnection *gc;
+	const char *who;
+	char *msg;
+	PurpleMessageFlags flags;
+	time_t when;
+} MsnIMData;
+
+static const char *
+msn_normalize(const PurpleAccount *account, const char *str)
+{
+	static char buf[BUF_LEN];
+	char *tmp;
+
+	g_return_val_if_fail(str != NULL, NULL);
+
+	g_snprintf(buf, sizeof(buf), "%s%s", str,
+			   (strchr(str, '@') ? "" : "@hotmail.com"));
+
+	tmp = g_utf8_strdown(buf, -1);
+	strncpy(buf, tmp, sizeof(buf));
+	g_free(tmp);
+
+	return buf;
+}
+
+static gboolean
+msn_send_attention(PurpleConnection *gc, const char *username, guint type)
+{
+	MsnMessage *msg;
+	MsnSession *session;
+	MsnSwitchBoard *swboard;
+
+	msg = msn_message_new_nudge();
+	session = gc->proto_data;
+	swboard = msn_session_get_swboard(session, username, MSN_SB_FLAG_IM);
+
+	if (swboard == NULL)
+		return FALSE;
+
+	msn_switchboard_send_msg(swboard, msg, TRUE);
+
+	return TRUE;
+}
+
+static GList *
+msn_attention_types(PurpleAccount *account)
+{
+	PurpleAttentionType *attn;
+	static GList *list = NULL;
+
+	if (!list) {
+		attn = g_new0(PurpleAttentionType, 1);
+		attn->name = _("Nudge");
+		attn->incoming_description = _("%s has nudged you!");
+		attn->outgoing_description = _("Nudging %s...");
+		list = g_list_append(list, attn);
+	}
+
+	return list;
+}
+
+
+static PurpleCmdRet
+msn_cmd_nudge(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
+{
+	PurpleAccount *account = purple_conversation_get_account(conv);
+	PurpleConnection *gc = purple_account_get_connection(account);
+	const gchar *username;
+
+	username = purple_conversation_get_name(conv);
+
+	serv_send_attention(gc, username, MSN_NUDGE);
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static void
+msn_act_id(PurpleConnection *gc, const char *entry)
+{
+	MsnCmdProc *cmdproc;
+	MsnSession *session;
+	PurpleAccount *account;
+	const char *alias;
+
+	session = gc->proto_data;
+	cmdproc = session->notification->cmdproc;
+	account = purple_connection_get_account(gc);
+
+	if(entry && strlen(entry))
+		alias = purple_url_encode(entry);
+	else
+		alias = "";
+
+	if (strlen(alias) > BUDDY_ALIAS_MAXLEN)
+	{
+		purple_notify_error(gc, NULL,
+						  _("Your new MSN friendly name is too long."), NULL);
+		return;
+	}
+
+	msn_cmdproc_send(cmdproc, "REA", "%s %s",
+					 purple_account_get_username(account),
+					 alias);
+}
+
+static void
+msn_set_prp(PurpleConnection *gc, const char *type, const char *entry)
+{
+	MsnCmdProc *cmdproc;
+	MsnSession *session;
+
+	session = gc->proto_data;
+	cmdproc = session->notification->cmdproc;
+
+	if (entry == NULL || *entry == '\0')
+	{
+		msn_cmdproc_send(cmdproc, "PRP", "%s", type);
+	}
+	else
+	{
+		msn_cmdproc_send(cmdproc, "PRP", "%s %s", type,
+						 purple_url_encode(entry));
+	}
+}
+
+static void
+msn_set_home_phone_cb(PurpleConnection *gc, const char *entry)
+{
+	msn_set_prp(gc, "PHH", entry);
+}
+
+static void
+msn_set_work_phone_cb(PurpleConnection *gc, const char *entry)
+{
+	msn_set_prp(gc, "PHW", entry);
+}
+
+static void
+msn_set_mobile_phone_cb(PurpleConnection *gc, const char *entry)
+{
+	msn_set_prp(gc, "PHM", entry);
+}
+
+static void
+enable_msn_pages_cb(PurpleConnection *gc)
+{
+	msn_set_prp(gc, "MOB", "Y");
+}
+
+static void
+disable_msn_pages_cb(PurpleConnection *gc)
+{
+	msn_set_prp(gc, "MOB", "N");
+}
+
+static void
+send_to_mobile(PurpleConnection *gc, const char *who, const char *entry)
+{
+	MsnTransaction *trans;
+	MsnSession *session;
+	MsnCmdProc *cmdproc;
+	MsnPage *page;
+	char *payload;
+	size_t payload_len;
+
+	session = gc->proto_data;
+	cmdproc = session->notification->cmdproc;
+
+	page = msn_page_new();
+	msn_page_set_body(page, entry);
+
+	payload = msn_page_gen_payload(page, &payload_len);
+
+	trans = msn_transaction_new(cmdproc, "PGD", "%s 1 %d", who, payload_len);
+
+	msn_transaction_set_payload(trans, payload, payload_len);
+
+	msn_page_destroy(page);
+
+	msn_cmdproc_send_trans(cmdproc, trans);
+}
+
+static void
+send_to_mobile_cb(MsnMobileData *data, const char *entry)
+{
+	send_to_mobile(data->gc, data->passport, entry);
+	g_free(data);
+}
+
+static void
+close_mobile_page_cb(MsnMobileData *data, const char *entry)
+{
+	g_free(data);
+}
+
+/* -- */
+
+static void
+msn_show_set_friendly_name(PurplePluginAction *action)
+{
+	PurpleConnection *gc;
+
+	gc = (PurpleConnection *) action->context;
+
+	purple_request_input(gc, NULL, _("Set your friendly name."),
+					   _("This is the name that other MSN buddies will "
+						 "see you as."),
+					   purple_connection_get_display_name(gc), FALSE, FALSE, NULL,
+					   _("OK"), G_CALLBACK(msn_act_id),
+					   _("Cancel"), NULL,
+					   purple_connection_get_account(gc), NULL, NULL,
+					   gc);
+}
+
+static void
+msn_show_set_home_phone(PurplePluginAction *action)
+{
+	PurpleConnection *gc;
+	MsnSession *session;
+
+	gc = (PurpleConnection *) action->context;
+	session = gc->proto_data;
+
+	purple_request_input(gc, NULL, _("Set your home phone number."), NULL,
+					   msn_user_get_home_phone(session->user), FALSE, FALSE, NULL,
+					   _("OK"), G_CALLBACK(msn_set_home_phone_cb),
+					   _("Cancel"), NULL,
+					   purple_connection_get_account(gc), NULL, NULL,
+					   gc);
+}
+
+static void
+msn_show_set_work_phone(PurplePluginAction *action)
+{
+	PurpleConnection *gc;
+	MsnSession *session;
+
+	gc = (PurpleConnection *) action->context;
+	session = gc->proto_data;
+
+	purple_request_input(gc, NULL, _("Set your work phone number."), NULL,
+					   msn_user_get_work_phone(session->user), FALSE, FALSE, NULL,
+					   _("OK"), G_CALLBACK(msn_set_work_phone_cb),
+					   _("Cancel"), NULL,
+					   purple_connection_get_account(gc), NULL, NULL,
+					   gc);
+}
+
+static void
+msn_show_set_mobile_phone(PurplePluginAction *action)
+{
+	PurpleConnection *gc;
+	MsnSession *session;
+
+	gc = (PurpleConnection *) action->context;
+	session = gc->proto_data;
+
+	purple_request_input(gc, NULL, _("Set your mobile phone number."), NULL,
+					   msn_user_get_mobile_phone(session->user), FALSE, FALSE, NULL,
+					   _("OK"), G_CALLBACK(msn_set_mobile_phone_cb),
+					   _("Cancel"), NULL,
+					   purple_connection_get_account(gc), NULL, NULL,
+					   gc);
+}
+
+static void
+msn_show_set_mobile_pages(PurplePluginAction *action)
+{
+	PurpleConnection *gc;
+
+	gc = (PurpleConnection *) action->context;
+
+	purple_request_action(gc, NULL, _("Allow MSN Mobile pages?"),
+						_("Do you want to allow or disallow people on "
+						  "your buddy list to send you MSN Mobile pages "
+						  "to your cell phone or other mobile device?"),
+						-1,
+						purple_connection_get_account(gc), NULL, NULL,
+						gc, 3,
+						_("Allow"), G_CALLBACK(enable_msn_pages_cb),
+						_("Disallow"), G_CALLBACK(disable_msn_pages_cb),
+						_("Cancel"), NULL);
+}
+
+static void
+msn_show_hotmail_inbox(PurplePluginAction *action)
+{
+	PurpleConnection *gc;
+	MsnSession *session;
+
+	gc = (PurpleConnection *) action->context;
+	session = gc->proto_data;
+
+	if (session->passport_info.file == NULL)
+	{
+		purple_notify_error(gc, NULL,
+						  _("This Hotmail account may not be active."), NULL);
+		return;
+	}
+
+	purple_notify_uri(gc, session->passport_info.file);
+}
+
+static void
+show_send_to_mobile_cb(PurpleBlistNode *node, gpointer ignored)
+{
+	PurpleBuddy *buddy;
+	PurpleConnection *gc;
+	MsnSession *session;
+	MsnMobileData *data;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
+
+	buddy = (PurpleBuddy *) node;
+	gc = purple_account_get_connection(buddy->account);
+
+	session = gc->proto_data;
+
+	data = g_new0(MsnMobileData, 1);
+	data->gc = gc;
+	data->passport = buddy->name;
+
+	purple_request_input(gc, NULL, _("Send a mobile message."), NULL,
+					   NULL, TRUE, FALSE, NULL,
+					   _("Page"), G_CALLBACK(send_to_mobile_cb),
+					   _("Close"), G_CALLBACK(close_mobile_page_cb),
+					   purple_connection_get_account(gc), purple_buddy_get_name(buddy), NULL,
+					   data);
+}
+
+static gboolean
+msn_offline_message(const PurpleBuddy *buddy) {
+	MsnUser *user;
+	if (buddy == NULL)
+		return FALSE;
+	user = buddy->proto_data;
+	return user && user->mobile;
+}
+
+static void
+initiate_chat_cb(PurpleBlistNode *node, gpointer data)
+{
+	PurpleBuddy *buddy;
+	PurpleConnection *gc;
+
+	MsnSession *session;
+	MsnSwitchBoard *swboard;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
+
+	buddy = (PurpleBuddy *) node;
+	gc = purple_account_get_connection(buddy->account);
+
+	session = gc->proto_data;
+
+	swboard = msn_switchboard_new(session);
+	msn_switchboard_request(swboard);
+	msn_switchboard_request_add_user(swboard, buddy->name);
+
+	/* TODO: This might move somewhere else, after USR might be */
+	swboard->chat_id = session->conv_seq++;
+	swboard->conv = serv_got_joined_chat(gc, swboard->chat_id, "MSN Chat");
+	swboard->flag = MSN_SB_FLAG_IM;
+
+	purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv),
+							purple_account_get_username(buddy->account), NULL, PURPLE_CBFLAGS_NONE, TRUE);
+}
+
+static void
+t_msn_xfer_init(PurpleXfer *xfer)
+{
+	MsnSlpLink *slplink = xfer->data;
+	msn_slplink_request_ft(slplink, xfer);
+}
+
+static PurpleXfer*
+msn_new_xfer(PurpleConnection *gc, const char *who)
+{
+	MsnSession *session;
+	MsnSlpLink *slplink;
+	PurpleXfer *xfer;
+
+	session = gc->proto_data;
+
+	xfer = purple_xfer_new(gc->account, PURPLE_XFER_SEND, who);
+	if (xfer)
+	{
+		slplink = msn_session_get_slplink(session, who);
+
+		xfer->data = slplink;
+
+		purple_xfer_set_init_fnc(xfer, t_msn_xfer_init);
+	}
+
+	return xfer;
+}
+
+static void
+msn_send_file(PurpleConnection *gc, const char *who, const char *file)
+{
+	PurpleXfer *xfer = msn_new_xfer(gc, who);
+
+	if (file)
+		purple_xfer_request_accepted(xfer, file);
+	else
+		purple_xfer_request(xfer);
+}
+
+static gboolean
+msn_can_receive_file(PurpleConnection *gc, const char *who)
+{
+	PurpleAccount *account;
+	char *normal;
+	gboolean ret;
+
+	account = purple_connection_get_account(gc);
+
+	normal = g_strdup(msn_normalize(account, purple_account_get_username(account)));
+
+	ret = strcmp(normal, msn_normalize(account, who));
+
+	g_free(normal);
+
+	return ret;
+}
+
+/**************************************************************************
+ * Protocol Plugin ops
+ **************************************************************************/
+
+static const char *
+msn_list_icon(PurpleAccount *a, PurpleBuddy *b)
+{
+	return "msn";
+}
+
+static char *
+msn_status_text(PurpleBuddy *buddy)
+{
+	PurplePresence *presence;
+	PurpleStatus *status;
+
+	presence = purple_buddy_get_presence(buddy);
+	status = purple_presence_get_active_status(presence);
+
+	if (!purple_presence_is_available(presence) && !purple_presence_is_idle(presence))
+	{
+		return g_strdup(purple_status_get_name(status));
+	}
+
+	return NULL;
+}
+
+static void
+msn_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
+{
+	MsnUser *user;
+	PurplePresence *presence = purple_buddy_get_presence(buddy);
+	PurpleStatus *status = purple_presence_get_active_status(presence);
+
+	user = buddy->proto_data;
+
+
+	if (purple_presence_is_online(presence))
+	{
+		purple_notify_user_info_add_pair(user_info, _("Status"),
+									   (purple_presence_is_idle(presence) ? _("Idle") : purple_status_get_name(status)));
+	}
+
+	if (full && user)
+	{
+		purple_notify_user_info_add_pair(user_info, _("Has you"),
+									   ((user->list_op & (1 << MSN_LIST_RL)) ? _("Yes") : _("No")));
+	}
+
+	/* XXX: This is being shown in non-full tooltips because the
+	 * XXX: blocked icon overlay isn't always accurate for MSN.
+	 * XXX: This can die as soon as purple_privacy_check() knows that
+	 * XXX: this prpl always honors both the allow and deny lists. */
+	/* While the above comment may be strictly correct (the privacy API needs
+	 * rewriteing), purple_privacy_check() is going to be more accurate at
+	 * indicating whether a particular buddy is going to be able to message
+	 * you, which is the important information that this is trying to convey. */
+	if (full && user)
+	{
+		purple_notify_user_info_add_pair(user_info, _("Blocked"),
+									   ((user->list_op & (1 << MSN_LIST_BL)) ? _("Yes") : _("No")));
+	}
+}
+
+static GList *
+msn_status_types(PurpleAccount *account)
+{
+	PurpleStatusType *status;
+	GList *types = NULL;
+
+	status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE,
+			NULL, NULL, FALSE, TRUE, FALSE);
+	types = g_list_append(types, status);
+
+	status = purple_status_type_new_full(PURPLE_STATUS_AWAY,
+			NULL, NULL, FALSE, TRUE, FALSE);
+	types = g_list_append(types, status);
+
+	status = purple_status_type_new_full(PURPLE_STATUS_AWAY,
+			"brb", _("Be Right Back"), FALSE, TRUE, FALSE);
+	types = g_list_append(types, status);
+
+	status = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE,
+			"busy", _("Busy"), FALSE, TRUE, FALSE);
+	types = g_list_append(types, status);
+
+	status = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE,
+			"phone", _("On the Phone"), FALSE, TRUE, FALSE);
+	types = g_list_append(types, status);
+
+	status = purple_status_type_new_full(PURPLE_STATUS_AWAY,
+			"lunch", _("Out to Lunch"), FALSE, TRUE, FALSE);
+	types = g_list_append(types, status);
+
+	status = purple_status_type_new_full(PURPLE_STATUS_INVISIBLE,
+			NULL, NULL, FALSE, TRUE, FALSE);
+	types = g_list_append(types, status);
+
+	status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE,
+			NULL, NULL, FALSE, TRUE, FALSE);
+	types = g_list_append(types, status);
+
+	status = purple_status_type_new_full(PURPLE_STATUS_MOBILE,
+			"mobile", NULL, FALSE, FALSE, TRUE);
+	types = g_list_append(types, status);
+
+	return types;
+}
+
+static GList *
+msn_actions(PurplePlugin *plugin, gpointer context)
+{
+	PurpleConnection *gc = (PurpleConnection *)context;
+	PurpleAccount *account;
+	const char *user;
+
+	GList *m = NULL;
+	PurplePluginAction *act;
+
+	act = purple_plugin_action_new(_("Set Friendly Name..."),
+								 msn_show_set_friendly_name);
+	m = g_list_append(m, act);
+	m = g_list_append(m, NULL);
+
+	act = purple_plugin_action_new(_("Set Home Phone Number..."),
+								 msn_show_set_home_phone);
+	m = g_list_append(m, act);
+
+	act = purple_plugin_action_new(_("Set Work Phone Number..."),
+			msn_show_set_work_phone);
+	m = g_list_append(m, act);
+
+	act = purple_plugin_action_new(_("Set Mobile Phone Number..."),
+			msn_show_set_mobile_phone);
+	m = g_list_append(m, act);
+	m = g_list_append(m, NULL);
+
+#if 0
+	act = purple_plugin_action_new(_("Enable/Disable Mobile Devices..."),
+			msn_show_set_mobile_support);
+	m = g_list_append(m, act);
+#endif
+
+	act = purple_plugin_action_new(_("Allow/Disallow Mobile Pages..."),
+			msn_show_set_mobile_pages);
+	m = g_list_append(m, act);
+
+	account = purple_connection_get_account(gc);
+	user = msn_normalize(account, purple_account_get_username(account));
+
+	if ((strstr(user, "@hotmail.") != NULL) ||
+		(strstr(user, "@msn.com") != NULL))
+	{
+		m = g_list_append(m, NULL);
+		act = purple_plugin_action_new(_("Open Hotmail Inbox"),
+				msn_show_hotmail_inbox);
+		m = g_list_append(m, act);
+	}
+
+	return m;
+}
+
+static GList *
+msn_buddy_menu(PurpleBuddy *buddy)
+{
+	MsnUser *user;
+
+	GList *m = NULL;
+	PurpleMenuAction *act;
+
+	g_return_val_if_fail(buddy != NULL, NULL);
+
+	user = buddy->proto_data;
+
+	if (user != NULL)
+	{
+		if (user->mobile)
+		{
+			act = purple_menu_action_new(_("Send to Mobile"),
+			                           PURPLE_CALLBACK(show_send_to_mobile_cb),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		}
+	}
+
+	if (g_ascii_strcasecmp(buddy->name,
+	                       purple_account_get_username(buddy->account)))
+	{
+		act = purple_menu_action_new(_("Initiate _Chat"),
+		                           PURPLE_CALLBACK(initiate_chat_cb),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+	}
+
+	return m;
+}
+
+static GList *
+msn_blist_node_menu(PurpleBlistNode *node)
+{
+	if(PURPLE_BLIST_NODE_IS_BUDDY(node))
+	{
+		return msn_buddy_menu((PurpleBuddy *) node);
+	}
+	else
+	{
+		return NULL;
+	}
+}
+
+static void
+msn_login(PurpleAccount *account)
+{
+	PurpleConnection *gc;
+	MsnSession *session;
+	const char *username;
+	const char *host;
+	gboolean http_method = FALSE;
+	int port;
+
+	gc = purple_account_get_connection(account);
+
+	if (!purple_ssl_is_supported())
+	{
+		gc->wants_to_die = TRUE;
+		purple_connection_error(gc,
+			_("SSL support is needed for MSN. Please install a supported "
+			  "SSL library."));
+		return;
+	}
+
+	http_method = purple_account_get_bool(account, "http_method", FALSE);
+
+	if (http_method)
+		host = purple_account_get_string(account, "http_method_server", MSN_HTTPCONN_SERVER);
+	else
+		host = purple_account_get_string(account, "server", MSN_SERVER);
+	port = purple_account_get_int(account, "port", MSN_PORT);
+
+	session = msn_session_new(account);
+
+	gc->proto_data = session;
+	gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC;
+
+	msn_session_set_login_step(session, MSN_LOGIN_STEP_START);
+
+	/* Hmm, I don't like this. */
+	/* XXX shx: Me neither */
+	username = msn_normalize(account, purple_account_get_username(account));
+
+	if (strcmp(username, purple_account_get_username(account)))
+		purple_account_set_username(account, username);
+
+	if (!msn_session_connect(session, host, port, http_method))
+		purple_connection_error(gc, _("Failed to connect to server."));
+}
+
+static void
+msn_close(PurpleConnection *gc)
+{
+	MsnSession *session;
+
+	session = gc->proto_data;
+
+	g_return_if_fail(session != NULL);
+
+	msn_session_destroy(session);
+
+	gc->proto_data = NULL;
+}
+
+static gboolean
+msn_send_me_im(gpointer data)
+{
+	MsnIMData *imdata = data;
+	serv_got_im(imdata->gc, imdata->who, imdata->msg, imdata->flags, imdata->when);
+	g_free(imdata->msg);
+	g_free(imdata);
+	return FALSE;
+}
+
+static int
+msn_send_im(PurpleConnection *gc, const char *who, const char *message,
+			PurpleMessageFlags flags)
+{
+	PurpleAccount *account;
+	PurpleBuddy *buddy = purple_find_buddy(gc->account, who);
+	MsnMessage *msg;
+	char *msgformat;
+	char *msgtext;
+
+	account = purple_connection_get_account(gc);
+
+	if (buddy) {
+		PurplePresence *p = purple_buddy_get_presence(buddy);
+		if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
+			char *text = purple_markup_strip_html(message);
+			send_to_mobile(gc, who, text);
+			g_free(text);
+			return 1;
+		}
+	}
+
+	msn_import_html(message, &msgformat, &msgtext);
+
+	if (strlen(msgtext) + strlen(msgformat) + strlen(DISPLAY_VERSION) > 1564)
+	{
+		g_free(msgformat);
+		g_free(msgtext);
+
+		return -E2BIG;
+	}
+
+	msg = msn_message_new_plain(msgtext);
+	msn_message_set_attr(msg, "X-MMS-IM-Format", msgformat);
+
+	g_free(msgformat);
+	g_free(msgtext);
+
+	if (g_ascii_strcasecmp(who, purple_account_get_username(account)))
+	{
+		MsnSession *session;
+		MsnSwitchBoard *swboard;
+
+		session = gc->proto_data;
+		swboard = msn_session_get_swboard(session, who, MSN_SB_FLAG_IM);
+
+		msn_switchboard_send_msg(swboard, msg, TRUE);
+	}
+	else
+	{
+		char *body_str, *body_enc, *pre, *post;
+		const char *format;
+		MsnIMData *imdata = g_new0(MsnIMData, 1);
+		/*
+		 * In MSN, you can't send messages to yourself, so
+		 * we'll fake like we received it ;)
+		 */
+		body_str = msn_message_to_string(msg);
+		body_enc = g_markup_escape_text(body_str, -1);
+		g_free(body_str);
+
+		format = msn_message_get_attr(msg, "X-MMS-IM-Format");
+		msn_parse_format(format, &pre, &post);
+		body_str = g_strdup_printf("%s%s%s", pre ? pre :  "",
+								   body_enc ? body_enc : "", post ? post : "");
+		g_free(body_enc);
+		g_free(pre);
+		g_free(post);
+
+		serv_got_typing_stopped(gc, who);
+		imdata->gc = gc;
+		imdata->who = who;
+		imdata->msg = body_str;
+		imdata->flags = flags;
+		imdata->when = time(NULL);
+		g_idle_add(msn_send_me_im, imdata);
+	}
+
+	msn_message_destroy(msg);
+
+	return 1;
+}
+
+static unsigned int
+msn_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
+{
+	PurpleAccount *account;
+	MsnSession *session;
+	MsnSwitchBoard *swboard;
+	MsnMessage *msg;
+
+	account = purple_connection_get_account(gc);
+	session = gc->proto_data;
+
+	/*
+	 * TODO: I feel like this should be "if (state != PURPLE_TYPING)"
+	 *       but this is how it was before, and I don't want to break
+	 *       anything. --KingAnt
+	 */
+	if (state == PURPLE_NOT_TYPING)
+		return 0;
+
+	if (!g_ascii_strcasecmp(who, purple_account_get_username(account)))
+	{
+		/* We'll just fake it, since we're sending to ourself. */
+		serv_got_typing(gc, who, MSN_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
+
+		return MSN_TYPING_SEND_TIMEOUT;
+	}
+
+	swboard = msn_session_find_swboard(session, who);
+
+	if (swboard == NULL || !msn_switchboard_can_send(swboard))
+		return 0;
+
+	swboard->flag |= MSN_SB_FLAG_IM;
+
+	msg = msn_message_new(MSN_MSG_TYPING);
+	msn_message_set_content_type(msg, "text/x-msmsgscontrol");
+	msn_message_set_flag(msg, 'U');
+	msn_message_set_attr(msg, "TypingUser",
+						 purple_account_get_username(account));
+	msn_message_set_bin_data(msg, "\r\n", 2);
+
+	msn_switchboard_send_msg(swboard, msg, FALSE);
+
+	msn_message_destroy(msg);
+
+	return MSN_TYPING_SEND_TIMEOUT;
+}
+
+static void
+msn_set_status(PurpleAccount *account, PurpleStatus *status)
+{
+	PurpleConnection *gc;
+	MsnSession *session;
+
+	gc = purple_account_get_connection(account);
+
+	if (gc != NULL)
+	{
+		session = gc->proto_data;
+		msn_change_status(session);
+	}
+}
+
+static void
+msn_set_idle(PurpleConnection *gc, int idle)
+{
+	MsnSession *session;
+
+	session = gc->proto_data;
+
+	msn_change_status(session);
+}
+
+#if 0
+static void
+fake_userlist_add_buddy(MsnUserList *userlist,
+					   const char *who, int list_id,
+					   const char *group_name)
+{
+	MsnUser *user;
+	static int group_id_c = 1;
+	int group_id;
+
+	group_id = -1;
+
+	if (group_name != NULL)
+	{
+		MsnGroup *group;
+		group = msn_group_new(userlist, group_id_c, group_name);
+		group_id = group_id_c++;
+	}
+
+	user = msn_userlist_find_user(userlist, who);
+
+	if (user == NULL)
+	{
+		user = msn_user_new(userlist, who, NULL);
+		msn_userlist_add_user(userlist, user);
+	}
+	else
+		if (user->list_op & (1 << list_id))
+		{
+			if (list_id == MSN_LIST_FL)
+			{
+				if (group_id >= 0)
+					if (g_list_find(user->group_ids,
+									GINT_TO_POINTER(group_id)))
+						return;
+			}
+			else
+				return;
+		}
+
+	if (group_id >= 0)
+	{
+		user->group_ids = g_list_append(user->group_ids,
+										GINT_TO_POINTER(group_id));
+	}
+
+	user->list_op |= (1 << list_id);
+}
+#endif
+
+static void
+msn_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
+{
+	MsnSession *session;
+	MsnUserList *userlist;
+	const char *who;
+
+	session = gc->proto_data;
+	userlist = session->userlist;
+	who = msn_normalize(gc->account, buddy->name);
+
+	if (!session->logged_in)
+	{
+#if 0
+		fake_userlist_add_buddy(session->sync_userlist, who, MSN_LIST_FL,
+								group ? group->name : NULL);
+#else
+		purple_debug_error("msn", "msn_add_buddy called before connected\n");
+#endif
+
+		return;
+	}
+
+#if 0
+	if (group != NULL && group->name != NULL)
+		purple_debug_info("msn", "msn_add_buddy: %s, %s\n", who, group->name);
+	else
+		purple_debug_info("msn", "msn_add_buddy: %s\n", who);
+#endif
+
+#if 0
+	/* Which is the max? */
+	if (session->fl_users_count >= 150)
+	{
+		purple_debug_info("msn", "Too many buddies\n");
+		/* Buddy list full */
+		/* TODO: purple should be notified of this */
+		return;
+	}
+#endif
+
+	/* XXX - Would group ever be NULL here?  I don't think so...
+	 * shx: Yes it should; MSN handles non-grouped buddies, and this is only
+	 * internal. */
+	msn_userlist_add_buddy(userlist, who, MSN_LIST_FL,
+						   group ? group->name : NULL);
+}
+
+static void
+msn_rem_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
+{
+	MsnSession *session;
+	MsnUserList *userlist;
+
+	session = gc->proto_data;
+	userlist = session->userlist;
+
+	if (!session->logged_in)
+		return;
+
+	/* XXX - Does buddy->name need to be msn_normalize'd here?  --KingAnt */
+	msn_userlist_rem_buddy(userlist, buddy->name, MSN_LIST_FL, group->name);
+}
+
+static void
+msn_add_permit(PurpleConnection *gc, const char *who)
+{
+	MsnSession *session;
+	MsnUserList *userlist;
+	MsnUser *user;
+
+	session = gc->proto_data;
+	userlist = session->userlist;
+	user = msn_userlist_find_user(userlist, who);
+
+	if (!session->logged_in)
+		return;
+
+	if (user != NULL && user->list_op & MSN_LIST_BL_OP)
+		msn_userlist_rem_buddy(userlist, who, MSN_LIST_BL, NULL);
+
+	msn_userlist_add_buddy(userlist, who, MSN_LIST_AL, NULL);
+}
+
+static void
+msn_add_deny(PurpleConnection *gc, const char *who)
+{
+	MsnSession *session;
+	MsnUserList *userlist;
+	MsnUser *user;
+
+	session = gc->proto_data;
+	userlist = session->userlist;
+	user = msn_userlist_find_user(userlist, who);
+
+	if (!session->logged_in)
+		return;
+
+	if (user != NULL && user->list_op & MSN_LIST_AL_OP)
+		msn_userlist_rem_buddy(userlist, who, MSN_LIST_AL, NULL);
+
+	msn_userlist_add_buddy(userlist, who, MSN_LIST_BL, NULL);
+}
+
+static void
+msn_rem_permit(PurpleConnection *gc, const char *who)
+{
+	MsnSession *session;
+	MsnUserList *userlist;
+	MsnUser *user;
+
+	session = gc->proto_data;
+	userlist = session->userlist;
+
+	if (!session->logged_in)
+		return;
+
+	user = msn_userlist_find_user(userlist, who);
+
+	msn_userlist_rem_buddy(userlist, who, MSN_LIST_AL, NULL);
+
+	if (user != NULL && user->list_op & MSN_LIST_RL_OP)
+		msn_userlist_add_buddy(userlist, who, MSN_LIST_BL, NULL);
+}
+
+static void
+msn_rem_deny(PurpleConnection *gc, const char *who)
+{
+	MsnSession *session;
+	MsnUserList *userlist;
+	MsnUser *user;
+
+	session = gc->proto_data;
+	userlist = session->userlist;
+
+	if (!session->logged_in)
+		return;
+
+	user = msn_userlist_find_user(userlist, who);
+
+	msn_userlist_rem_buddy(userlist, who, MSN_LIST_BL, NULL);
+
+	if (user != NULL && user->list_op & MSN_LIST_RL_OP)
+		msn_userlist_add_buddy(userlist, who, MSN_LIST_AL, NULL);
+}
+
+static void
+msn_set_permit_deny(PurpleConnection *gc)
+{
+	PurpleAccount *account;
+	MsnSession *session;
+	MsnCmdProc *cmdproc;
+
+	account = purple_connection_get_account(gc);
+	session = gc->proto_data;
+	cmdproc = session->notification->cmdproc;
+
+	if (account->perm_deny == PURPLE_PRIVACY_ALLOW_ALL ||
+		account->perm_deny == PURPLE_PRIVACY_DENY_USERS)
+	{
+		msn_cmdproc_send(cmdproc, "BLP", "%s", "AL");
+	}
+	else
+	{
+		msn_cmdproc_send(cmdproc, "BLP", "%s", "BL");
+	}
+}
+
+static void
+msn_chat_invite(PurpleConnection *gc, int id, const char *msg,
+				const char *who)
+{
+	MsnSession *session;
+	MsnSwitchBoard *swboard;
+
+	session = gc->proto_data;
+
+	swboard = msn_session_find_swboard_with_id(session, id);
+
+	if (swboard == NULL)
+	{
+		/* if we have no switchboard, everyone else left the chat already */
+		swboard = msn_switchboard_new(session);
+		msn_switchboard_request(swboard);
+		swboard->chat_id = id;
+		swboard->conv = purple_find_chat(gc, id);
+	}
+
+	swboard->flag |= MSN_SB_FLAG_IM;
+
+	msn_switchboard_request_add_user(swboard, who);
+}
+
+static void
+msn_chat_leave(PurpleConnection *gc, int id)
+{
+	MsnSession *session;
+	MsnSwitchBoard *swboard;
+	PurpleConversation *conv;
+
+	session = gc->proto_data;
+
+	swboard = msn_session_find_swboard_with_id(session, id);
+
+	/* if swboard is NULL we were the only person left anyway */
+	if (swboard == NULL)
+		return;
+
+	conv = swboard->conv;
+
+	msn_switchboard_release(swboard, MSN_SB_FLAG_IM);
+
+	/* If other switchboards managed to associate themselves with this
+	 * conv, make sure they know it's gone! */
+	if (conv != NULL)
+	{
+		while ((swboard = msn_session_find_swboard_with_conv(session, conv)) != NULL)
+			swboard->conv = NULL;
+	}
+}
+
+static int
+msn_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags)
+{
+	PurpleAccount *account;
+	MsnSession *session;
+	MsnSwitchBoard *swboard;
+	MsnMessage *msg;
+	char *msgformat;
+	char *msgtext;
+
+	account = purple_connection_get_account(gc);
+	session = gc->proto_data;
+	swboard = msn_session_find_swboard_with_id(session, id);
+
+	if (swboard == NULL)
+		return -EINVAL;
+
+	if (!swboard->ready)
+		return 0;
+
+	swboard->flag |= MSN_SB_FLAG_IM;
+
+	msn_import_html(message, &msgformat, &msgtext);
+
+	if (strlen(msgtext) + strlen(msgformat) + strlen(DISPLAY_VERSION) > 1564)
+	{
+		g_free(msgformat);
+		g_free(msgtext);
+
+		return -E2BIG;
+	}
+
+	msg = msn_message_new_plain(msgtext);
+	msn_message_set_attr(msg, "X-MMS-IM-Format", msgformat);
+	msn_switchboard_send_msg(swboard, msg, FALSE);
+	msn_message_destroy(msg);
+
+	g_free(msgformat);
+	g_free(msgtext);
+
+	serv_got_chat_in(gc, id, purple_account_get_username(account), 0,
+					 message, time(NULL));
+
+	return 0;
+}
+
+static void
+msn_keepalive(PurpleConnection *gc)
+{
+	MsnSession *session;
+
+	session = gc->proto_data;
+
+	if (!session->http_method)
+	{
+		MsnCmdProc *cmdproc;
+
+		cmdproc = session->notification->cmdproc;
+
+		msn_cmdproc_send_quick(cmdproc, "PNG", NULL, NULL);
+	}
+}
+
+static void
+msn_group_buddy(PurpleConnection *gc, const char *who,
+				const char *old_group_name, const char *new_group_name)
+{
+	MsnSession *session;
+	MsnUserList *userlist;
+
+	session = gc->proto_data;
+	userlist = session->userlist;
+
+	msn_userlist_move_buddy(userlist, who, old_group_name, new_group_name);
+}
+
+static void
+msn_rename_group(PurpleConnection *gc, const char *old_name,
+				 PurpleGroup *group, GList *moved_buddies)
+{
+	MsnSession *session;
+	MsnCmdProc *cmdproc;
+	int old_gid;
+	const char *enc_new_group_name;
+
+	session = gc->proto_data;
+	cmdproc = session->notification->cmdproc;
+	enc_new_group_name = purple_url_encode(group->name);
+
+	old_gid = msn_userlist_find_group_id(session->userlist, old_name);
+
+	if (old_gid >= 0)
+	{
+		msn_cmdproc_send(cmdproc, "REG", "%d %s 0", old_gid,
+						 enc_new_group_name);
+	}
+	else
+	{
+		msn_cmdproc_send(cmdproc, "ADG", "%s 0", enc_new_group_name);
+	}
+}
+
+static void
+msn_convo_closed(PurpleConnection *gc, const char *who)
+{
+	MsnSession *session;
+	MsnSwitchBoard *swboard;
+	PurpleConversation *conv;
+
+	session = gc->proto_data;
+
+	swboard = msn_session_find_swboard(session, who);
+
+	/*
+	 * Don't perform an assertion here. If swboard is NULL, then the
+	 * switchboard was either closed by the other party, or the person
+	 * is talking to himself.
+	 */
+	if (swboard == NULL)
+		return;
+
+	conv = swboard->conv;
+
+	/* If we release the switchboard here, it may still have messages
+	   pending ACK which would result in incorrect unsent message errors.
+	   Just let it timeout... This is *so* going to screw with people who
+	   use dumb clients that report "User has closed the conversation window" */
+	/* msn_switchboard_release(swboard, MSN_SB_FLAG_IM); */
+	swboard->conv = NULL;
+
+	/* If other switchboards managed to associate themselves with this
+	 * conv, make sure they know it's gone! */
+	if (conv != NULL)
+	{
+		while ((swboard = msn_session_find_swboard_with_conv(session, conv)) != NULL)
+			swboard->conv = NULL;
+	}
+}
+
+static void
+msn_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img)
+{
+	MsnSession *session;
+	MsnUser *user;
+
+	session = gc->proto_data;
+	user = session->user;
+
+	msn_user_set_buddy_icon(user, img);
+
+	msn_change_status(session);
+}
+
+static void
+msn_remove_group(PurpleConnection *gc, PurpleGroup *group)
+{
+	MsnSession *session;
+	MsnCmdProc *cmdproc;
+	int group_id;
+
+	session = gc->proto_data;
+	cmdproc = session->notification->cmdproc;
+
+	if ((group_id = msn_userlist_find_group_id(session->userlist, group->name)) >= 0)
+	{
+		msn_cmdproc_send(cmdproc, "RMG", "%d", group_id);
+	}
+}
+
+/**
+ * Extract info text from info_data and add it to user_info
+ */
+static gboolean
+msn_tooltip_extract_info_text(PurpleNotifyUserInfo *user_info, MsnGetInfoData *info_data)
+{
+	PurpleBuddy *b;
+
+	b = purple_find_buddy(purple_connection_get_account(info_data->gc),
+						info_data->name);
+
+	if (b)
+	{
+		char *tmp;
+
+		if (b->alias && b->alias[0])
+		{
+			char *aliastext = g_markup_escape_text(b->alias, -1);
+			purple_notify_user_info_add_pair(user_info, _("Alias"), aliastext);
+			g_free(aliastext);
+		}
+
+		if (b->server_alias)
+		{
+			char *nicktext = g_markup_escape_text(b->server_alias, -1);
+			tmp = g_strdup_printf("<font sml=\"msn\">%s</font><br>", nicktext);
+			purple_notify_user_info_add_pair(user_info, _("Nickname"), tmp);
+			g_free(tmp);
+			g_free(nicktext);
+		}
+
+		/* Add the tooltip information */
+		msn_tooltip_text(b, user_info, TRUE);
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+#if PHOTO_SUPPORT
+
+static char *
+msn_get_photo_url(const char *url_text)
+{
+	char *p, *q;
+
+	if ((p = strstr(url_text, " contactparams:photopreauthurl=\"")) != NULL)
+	{
+		p += strlen(" contactparams:photopreauthurl=\"");
+	}
+
+	if (p && (strncmp(p, "http://", 8) == 0) && ((q = strchr(p, '"')) != NULL))
+			return g_strndup(p, q - p);
+
+	return NULL;
+}
+
+static void msn_got_photo(PurpleUtilFetchUrlData *url_data, gpointer data,
+		const gchar *url_text, size_t len, const gchar *error_message);
+
+#endif
+
+#if 0
+static char *msn_info_date_reformat(const char *field, size_t len)
+{
+	char *tmp = g_strndup(field, len);
+	time_t t = purple_str_to_time(tmp, FALSE, NULL, NULL, NULL);
+
+	g_free(tmp);
+	return g_strdup(purple_date_format_short(localtime(&t)));
+}
+#endif
+
+#define MSN_GOT_INFO_GET_FIELD(a, b) \
+	found = purple_markup_extract_info_field(stripped, stripped_len, user_info, \
+			"\n" a ":", 0, "\n", 0, "Undisclosed", b, 0, NULL, NULL); \
+	if (found) \
+		sect_info = TRUE;
+
+#define MSN_GOT_INFO_GET_FIELD_NO_SEARCH(a, b) \
+	found = purple_markup_extract_info_field(stripped, stripped_len, user_info, \
+			"\n" a ":", 0, "\n", 0, "Undisclosed", b, 0, NULL, msn_info_strip_search_link); \
+	if (found) \
+		sect_info = TRUE;
+
+static char *
+msn_info_strip_search_link(const char *field, size_t len)
+{
+	const char *c;
+	if ((c = strstr(field, " (http://spaces.live.com/default.aspx?page=searchresults")) == NULL &&
+		(c = strstr(field, " (http://spaces.msn.com/default.aspx?page=searchresults")) == NULL)
+		return g_strndup(field, len);
+	return g_strndup(field, c - field);
+}
+
+static void
+msn_got_info(PurpleUtilFetchUrlData *url_data, gpointer data,
+		const gchar *url_text, size_t len, const gchar *error_message)
+{
+	MsnGetInfoData *info_data = (MsnGetInfoData *)data;
+	PurpleNotifyUserInfo *user_info;
+	char *stripped, *p, *q, *tmp;
+	char *user_url = NULL;
+	gboolean found;
+	gboolean has_tooltip_text = FALSE;
+	gboolean has_info = FALSE;
+	gboolean sect_info = FALSE;
+	gboolean has_contact_info = FALSE;
+	char *url_buffer;
+	int stripped_len;
+#if PHOTO_SUPPORT
+	char *photo_url_text = NULL;
+	MsnGetInfoStepTwoData *info2_data = NULL;
+#endif
+
+	purple_debug_info("msn", "In msn_got_info\n");
+
+	/* Make sure the connection is still valid */
+	if (g_list_find(purple_connections_get_all(), info_data->gc) == NULL)
+	{
+		purple_debug_warning("msn", "invalid connection. ignoring buddy info.\n");
+		g_free(info_data->name);
+		g_free(info_data);
+		return;
+	}
+
+	user_info = purple_notify_user_info_new();
+	has_tooltip_text = msn_tooltip_extract_info_text(user_info, info_data);
+
+	if (error_message != NULL || url_text == NULL || strcmp(url_text, "") == 0)
+	{
+		tmp = g_strdup_printf("<b>%s</b>", _("Error retrieving profile"));
+		purple_notify_user_info_add_pair(user_info, NULL, tmp);
+		g_free(tmp);
+
+		purple_notify_userinfo(info_data->gc, info_data->name, user_info, NULL, NULL);
+		purple_notify_user_info_destroy(user_info);
+
+		g_free(info_data->name);
+		g_free(info_data);
+		return;
+	}
+
+	url_buffer = g_strdup(url_text);
+
+	/* If they have a homepage link, MSN masks it such that we need to
+	 * fetch the url out before purple_markup_strip_html() nukes it */
+	/* I don't think this works with the new spaces profiles - Stu 3/2/06 */
+	if ((p = strstr(url_text,
+			"Take a look at my </font><A class=viewDesc title=\"")) != NULL)
+	{
+		p += 50;
+
+		if ((q = strchr(p, '"')) != NULL)
+			user_url = g_strndup(p, q - p);
+	}
+
+	/*
+	 * purple_markup_strip_html() doesn't strip out character entities like &nbsp;
+	 * and &#183;
+	 */
+	while ((p = strstr(url_buffer, "&nbsp;")) != NULL)
+	{
+		*p = ' '; /* Turn &nbsp;'s into ordinary blanks */
+		p += 1;
+		memmove(p, p + 5, strlen(p + 5));
+		url_buffer[strlen(url_buffer) - 5] = '\0';
+	}
+
+	while ((p = strstr(url_buffer, "&#183;")) != NULL)
+	{
+		memmove(p, p + 6, strlen(p + 6));
+		url_buffer[strlen(url_buffer) - 6] = '\0';
+	}
+
+	/* Nuke the nasty \r's that just get in the way */
+	purple_str_strip_char(url_buffer, '\r');
+
+	/* MSN always puts in &#39; for apostrophes...replace them */
+	while ((p = strstr(url_buffer, "&#39;")) != NULL)
+	{
+		*p = '\'';
+		memmove(p + 1, p + 5, strlen(p + 5));
+		url_buffer[strlen(url_buffer) - 4] = '\0';
+	}
+
+	/* Nuke the html, it's easier than trying to parse the horrid stuff */
+	stripped = purple_markup_strip_html(url_buffer);
+	stripped_len = strlen(stripped);
+
+	purple_debug_misc("msn", "stripped = %p\n", stripped);
+	purple_debug_misc("msn", "url_buffer = %p\n", url_buffer);
+
+	/* General section header */
+	if (has_tooltip_text)
+		purple_notify_user_info_add_section_break(user_info);
+
+	purple_notify_user_info_add_section_header(user_info, _("General"));
+
+	/* Extract their Name and put it in */
+	MSN_GOT_INFO_GET_FIELD("Name", _("Name"));
+
+	/* General */
+	MSN_GOT_INFO_GET_FIELD("Nickname", _("Nickname"));
+	MSN_GOT_INFO_GET_FIELD_NO_SEARCH("Age", _("Age"));
+	MSN_GOT_INFO_GET_FIELD_NO_SEARCH("Gender", _("Gender"));
+	MSN_GOT_INFO_GET_FIELD_NO_SEARCH("Occupation", _("Occupation"));
+	MSN_GOT_INFO_GET_FIELD_NO_SEARCH("Location", _("Location"));
+
+	/* Extract their Interests and put it in */
+	found = purple_markup_extract_info_field(stripped, stripped_len, user_info,
+			"\nInterests\t", 0, " (/default.aspx?page=searchresults", 0,
+			"Undisclosed", _("Hobbies and Interests") /* _("Interests") */,
+			0, NULL, NULL);
+
+	if (found)
+		sect_info = TRUE;
+
+	MSN_GOT_INFO_GET_FIELD("More about me", _("A Little About Me"));
+
+	if (sect_info)
+	{
+		has_info = TRUE;
+		sect_info = FALSE;
+	}
+    else
+    {
+		/* Remove the section header */
+		purple_notify_user_info_remove_last_item(user_info);
+		if (has_tooltip_text)
+			purple_notify_user_info_remove_last_item(user_info);
+	}
+
+	/* Social */
+	purple_notify_user_info_add_section_break(user_info);
+	purple_notify_user_info_add_section_header(user_info, _("Social"));
+
+	MSN_GOT_INFO_GET_FIELD("Marital status", _("Marital Status"));
+	MSN_GOT_INFO_GET_FIELD("Interested in", _("Interests"));
+	MSN_GOT_INFO_GET_FIELD("Pets", _("Pets"));
+	MSN_GOT_INFO_GET_FIELD("Hometown", _("Hometown"));
+	MSN_GOT_INFO_GET_FIELD("Places lived", _("Places Lived"));
+	MSN_GOT_INFO_GET_FIELD("Fashion", _("Fashion"));
+	MSN_GOT_INFO_GET_FIELD("Humor", _("Humor"));
+	MSN_GOT_INFO_GET_FIELD("Music", _("Music"));
+	MSN_GOT_INFO_GET_FIELD("Favorite quote", _("Favorite Quote"));
+
+	if (sect_info)
+	{
+		has_info = TRUE;
+		sect_info = FALSE;
+	}
+    else
+    {
+		/* Remove the section header */
+		purple_notify_user_info_remove_last_item(user_info);
+		purple_notify_user_info_remove_last_item(user_info);
+	}
+
+	/* Contact Info */
+	/* Personal */
+	purple_notify_user_info_add_section_break(user_info);
+	purple_notify_user_info_add_section_header(user_info, _("Contact Info"));
+	purple_notify_user_info_add_section_header(user_info, _("Personal"));
+
+	MSN_GOT_INFO_GET_FIELD("Name", _("Name"));
+	MSN_GOT_INFO_GET_FIELD("Significant other", _("Significant Other"));
+	MSN_GOT_INFO_GET_FIELD("Home phone", _("Home Phone"));
+	MSN_GOT_INFO_GET_FIELD("Home phone 2", _("Home Phone 2"));
+	MSN_GOT_INFO_GET_FIELD("Home address", _("Home Address"));
+	MSN_GOT_INFO_GET_FIELD("Personal Mobile", _("Personal Mobile"));
+	MSN_GOT_INFO_GET_FIELD("Home fax", _("Home Fax"));
+	MSN_GOT_INFO_GET_FIELD("Personal e-mail", _("Personal E-Mail"));
+	MSN_GOT_INFO_GET_FIELD("Personal IM", _("Personal IM"));
+	MSN_GOT_INFO_GET_FIELD("Birthday", _("Birthday"));
+	MSN_GOT_INFO_GET_FIELD("Anniversary", _("Anniversary"));
+	MSN_GOT_INFO_GET_FIELD("Notes", _("Notes"));
+
+	if (sect_info)
+	{
+		has_info = TRUE;
+		sect_info = FALSE;
+		has_contact_info = TRUE;
+	}
+    else
+    {
+		/* Remove the section header */
+		purple_notify_user_info_remove_last_item(user_info);
+	}
+
+	/* Business */
+	purple_notify_user_info_add_section_header(user_info, _("Work"));
+	MSN_GOT_INFO_GET_FIELD("Name", _("Name"));
+	MSN_GOT_INFO_GET_FIELD("Job title", _("Job Title"));
+	MSN_GOT_INFO_GET_FIELD("Company", _("Company"));
+	MSN_GOT_INFO_GET_FIELD("Department", _("Department"));
+	MSN_GOT_INFO_GET_FIELD("Profession", _("Profession"));
+	MSN_GOT_INFO_GET_FIELD("Work phone 1", _("Work Phone"));
+	MSN_GOT_INFO_GET_FIELD("Work phone 2", _("Work Phone 2"));
+	MSN_GOT_INFO_GET_FIELD("Work address", _("Work Address"));
+	MSN_GOT_INFO_GET_FIELD("Work mobile", _("Work Mobile"));
+	MSN_GOT_INFO_GET_FIELD("Work pager", _("Work Pager"));
+	MSN_GOT_INFO_GET_FIELD("Work fax", _("Work Fax"));
+	MSN_GOT_INFO_GET_FIELD("Work e-mail", _("Work E-Mail"));
+	MSN_GOT_INFO_GET_FIELD("Work IM", _("Work IM"));
+	MSN_GOT_INFO_GET_FIELD("Start date", _("Start Date"));
+	MSN_GOT_INFO_GET_FIELD("Notes", _("Notes"));
+
+	if (sect_info)
+	{
+		has_info = TRUE;
+		sect_info = FALSE;
+		has_contact_info = TRUE;
+	}
+    else
+    {
+		/* Remove the section header */
+		purple_notify_user_info_remove_last_item(user_info);
+	}
+
+	if (!has_contact_info)
+	{
+		/* Remove the Contact Info section header */
+		purple_notify_user_info_remove_last_item(user_info);
+	}
+
+#if 0 /* these probably don't show up any more */
+	/*
+	 * The fields, 'A Little About Me', 'Favorite Things', 'Hobbies
+	 * and Interests', 'Favorite Quote', and 'My Homepage' may or may
+	 * not appear, in any combination. However, they do appear in
+	 * certain order, so we can successively search to pin down the
+	 * distinct values.
+	 */
+
+	/* Check if they have A Little About Me */
+	found = purple_markup_extract_info_field(stripped, stripped_len, s,
+			" A Little About Me \n\n", 0, "Favorite Things", '\n', NULL,
+			_("A Little About Me"), 0, NULL, NULL);
+
+	if (!found)
+	{
+		found = purple_markup_extract_info_field(stripped, stripped_len, s,
+				" A Little About Me \n\n", 0, "Hobbies and Interests", '\n',
+				NULL, _("A Little About Me"), 0, NULL, NULL);
+	}
+
+	if (!found)
+	{
+		found = purple_markup_extract_info_field(stripped, stripped_len, s,
+				" A Little About Me \n\n", 0, "Favorite Quote", '\n', NULL,
+				_("A Little About Me"), 0, NULL, NULL);
+	}
+
+	if (!found)
+	{
+		found = purple_markup_extract_info_field(stripped, stripped_len, s,
+				" A Little About Me \n\n", 0, "My Homepage \n\nTake a look",
+				'\n',
+				NULL, _("A Little About Me"), 0, NULL, NULL);
+	}
+
+	if (!found)
+	{
+		purple_markup_extract_info_field(stripped, stripped_len, s,
+				" A Little About Me \n\n", 0, "last updated", '\n', NULL,
+				_("A Little About Me"), 0, NULL, NULL);
+	}
+
+	if (found)
+		has_info = TRUE;
+
+	/* Check if they have Favorite Things */
+	found = purple_markup_extract_info_field(stripped, stripped_len, s,
+			" Favorite Things \n\n", 0, "Hobbies and Interests", '\n', NULL,
+			_("Favorite Things"), 0, NULL, NULL);
+
+	if (!found)
+	{
+		found = purple_markup_extract_info_field(stripped, stripped_len, s,
+				" Favorite Things \n\n", 0, "Favorite Quote", '\n', NULL,
+				_("Favorite Things"), 0, NULL, NULL);
+	}
+
+	if (!found)
+	{
+		found = purple_markup_extract_info_field(stripped, stripped_len, s,
+				" Favorite Things \n\n", 0, "My Homepage \n\nTake a look", '\n',
+				NULL, _("Favorite Things"), 0, NULL, NULL);
+	}
+
+	if (!found)
+	{
+		purple_markup_extract_info_field(stripped, stripped_len, s,
+				" Favorite Things \n\n", 0, "last updated", '\n', NULL,
+				_("Favorite Things"), 0, NULL, NULL);
+	}
+
+	if (found)
+		has_info = TRUE;
+
+	/* Check if they have Hobbies and Interests */
+	found = purple_markup_extract_info_field(stripped, stripped_len, s,
+			" Hobbies and Interests \n\n", 0, "Favorite Quote", '\n', NULL,
+			_("Hobbies and Interests"), 0, NULL, NULL);
+
+	if (!found)
+	{
+		found = purple_markup_extract_info_field(stripped, stripped_len, s,
+				" Hobbies and Interests \n\n", 0, "My Homepage \n\nTake a look",
+				'\n', NULL, _("Hobbies and Interests"), 0, NULL, NULL);
+	}
+
+	if (!found)
+	{
+		purple_markup_extract_info_field(stripped, stripped_len, s,
+				" Hobbies and Interests \n\n", 0, "last updated", '\n', NULL,
+				_("Hobbies and Interests"), 0, NULL, NULL);
+	}
+
+	if (found)
+		has_info = TRUE;
+
+	/* Check if they have Favorite Quote */
+	found = purple_markup_extract_info_field(stripped, stripped_len, s,
+			"Favorite Quote \n\n", 0, "My Homepage \n\nTake a look", '\n', NULL,
+			_("Favorite Quote"), 0, NULL, NULL);
+
+	if (!found)
+	{
+		purple_markup_extract_info_field(stripped, stripped_len, s,
+				"Favorite Quote \n\n", 0, "last updated", '\n', NULL,
+				_("Favorite Quote"), 0, NULL, NULL);
+	}
+
+	if (found)
+		has_info = TRUE;
+
+	/* Extract the last updated date and put it in */
+	found = purple_markup_extract_info_field(stripped, stripped_len, s,
+			" last updated:", 1, "\n", 0, NULL, _("Last Updated"), 0,
+			NULL, msn_info_date_reformat);
+
+	if (found)
+		has_info = TRUE;
+#endif
+
+	/* If we were able to fetch a homepage url earlier, stick it in there */
+	if (user_url != NULL)
+	{
+		tmp = g_strdup_printf("<a href=\"%s\">%s</a>", user_url, user_url);
+		purple_notify_user_info_add_pair(user_info, _("Homepage"), tmp);
+		g_free(tmp);
+		g_free(user_url);
+
+		has_info = TRUE;
+	}
+
+	if (!has_info)
+	{
+		/* MSN doesn't actually distinguish between "unknown member" and
+		 * a known member with an empty profile. Try to explain this fact.
+		 * Note that if we have a nonempty tooltip_text, we know the user
+		 * exists.
+		 */
+		/* This doesn't work with the new spaces profiles - Stu 3/2/06
+		char *p = strstr(url_buffer, "Unknown Member </TITLE>");
+		 * This might not work for long either ... */
+		/* Nope, it failed some time before 5/2/07 :(
+		char *p = strstr(url_buffer, "form id=\"SpacesSearch\" name=\"SpacesSearch\"");
+		 * Let's see how long this one holds out for ... */
+		char *p = strstr(url_buffer, "<form id=\"profile_form\" name=\"profile_form\" action=\"http&#58;&#47;&#47;spaces.live.com&#47;profile.aspx&#63;cid&#61;0\"");
+		PurpleBuddy *b = purple_find_buddy
+				(purple_connection_get_account(info_data->gc), info_data->name);
+		purple_notify_user_info_add_pair(user_info, _("Error retrieving profile"),
+									   ((p && b) ? _("The user has not created a public profile.") :
+										(p ? _("MSN reported not being able to find the user's profile. "
+											   "This either means that the user does not exist, "
+											   "or that the user exists "
+											   "but has not created a public profile.") :
+										 _("Could not find "	/* This should never happen */
+										   "any information in the user's profile. "
+										   "The user most likely does not exist."))));
+	}
+
+	/* put a link to the actual profile URL */
+	tmp = g_strdup_printf("<a href=\"%s%s\">%s%s</a>",
+					PROFILE_URL, info_data->name, PROFILE_URL, info_data->name);
+	purple_notify_user_info_add_pair(user_info, _("Profile URL"), tmp);
+	g_free(tmp);
+
+#if PHOTO_SUPPORT
+	/* Find the URL to the photo; must be before the marshalling [Bug 994207] */
+	photo_url_text = msn_get_photo_url(url_text);
+
+	/* Marshall the existing state */
+	info2_data = g_malloc0(sizeof(MsnGetInfoStepTwoData));
+	info2_data->info_data = info_data;
+	info2_data->stripped = stripped;
+	info2_data->url_buffer = url_buffer;
+	info2_data->user_info = user_info;
+	info2_data->photo_url_text = photo_url_text;
+
+	/* Try to put the photo in there too, if there's one */
+	if (photo_url_text)
+	{
+		purple_util_fetch_url(photo_url_text, FALSE, NULL, FALSE, msn_got_photo,
+					   info2_data);
+	}
+	else
+	{
+		/* Emulate a callback */
+		/* TODO: Huh? */
+		msn_got_photo(NULL, info2_data, NULL, 0, NULL);
+	}
+}
+
+static void
+msn_got_photo(PurpleUtilFetchUrlData *url_data, gpointer user_data,
+		const gchar *url_text, size_t len, const gchar *error_message)
+{
+	MsnGetInfoStepTwoData *info2_data = (MsnGetInfoStepTwoData *)user_data;
+	int id = -1;
+
+	/* Unmarshall the saved state */
+	MsnGetInfoData *info_data = info2_data->info_data;
+	char *stripped = info2_data->stripped;
+	char *url_buffer = info2_data->url_buffer;
+	PurpleNotifyUserInfo *user_info = info2_data->user_info;
+	char *photo_url_text = info2_data->photo_url_text;
+
+	/* Make sure the connection is still valid if we got here by fetching a photo url */
+	if (url_text && (error_message != NULL ||
+					 g_list_find(purple_connections_get_all(), info_data->gc) == NULL))
+	{
+		purple_debug_warning("msn", "invalid connection. ignoring buddy photo info.\n");
+		g_free(stripped);
+		g_free(url_buffer);
+		g_free(user_info);
+		g_free(info_data->name);
+		g_free(info_data);
+		g_free(photo_url_text);
+		g_free(info2_data);
+
+		return;
+	}
+
+	/* Try to put the photo in there too, if there's one and is readable */
+	if (user_data && url_text && len != 0)
+	{
+		if (strstr(url_text, "400 Bad Request")
+			|| strstr(url_text, "403 Forbidden")
+			|| strstr(url_text, "404 Not Found"))
+		{
+
+			purple_debug_info("msn", "Error getting %s: %s\n",
+					photo_url_text, url_text);
+		}
+		else
+		{
+			char buf[1024];
+			purple_debug_info("msn", "%s is %d bytes\n", photo_url_text, len);
+			id = purple_imgstore_add_with_id(g_memdup(url_text, len), len, NULL);
+			g_snprintf(buf, sizeof(buf), "<img id=\"%d\"><br>", id);
+			purple_notify_user_info_prepend_pair(user_info, NULL, buf);
+		}
+	}
+
+	/* We continue here from msn_got_info, as if nothing has happened */
+#endif
+	purple_notify_userinfo(info_data->gc, info_data->name, user_info, NULL, NULL);
+
+	g_free(stripped);
+	g_free(url_buffer);
+	purple_notify_user_info_destroy(user_info);
+	g_free(info_data->name);
+	g_free(info_data);
+#if PHOTO_SUPPORT
+	g_free(photo_url_text);
+	g_free(info2_data);
+	if (id != -1)
+		purple_imgstore_unref_by_id(id);
+#endif
+}
+
+static void
+msn_get_info(PurpleConnection *gc, const char *name)
+{
+	MsnGetInfoData *data;
+	char *url;
+
+	data       = g_new0(MsnGetInfoData, 1);
+	data->gc   = gc;
+	data->name = g_strdup(name);
+
+	url = g_strdup_printf("%s%s", PROFILE_URL, name);
+
+	purple_util_fetch_url(url, FALSE,
+				   "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)",
+				   TRUE, msn_got_info, data);
+
+	g_free(url);
+}
+
+static gboolean msn_load(PurplePlugin *plugin)
+{
+	msn_notification_init();
+	msn_switchboard_init();
+	msn_sync_init();
+
+	return TRUE;
+}
+
+static gboolean msn_unload(PurplePlugin *plugin)
+{
+	msn_notification_end();
+	msn_switchboard_end();
+	msn_sync_end();
+
+	return TRUE;
+}
+
+static PurpleAccount *find_acct(const char *prpl, const char *acct_id)
+{
+	PurpleAccount *acct = NULL;
+
+	/* If we have a specific acct, use it */
+	if (acct_id) {
+		acct = purple_accounts_find(acct_id, prpl);
+		if (acct && !purple_account_is_connected(acct))
+			acct = NULL;
+	} else { /* Otherwise find an active account for the protocol */
+		GList *l = purple_accounts_get_all();
+		while (l) {
+			if (!strcmp(prpl, purple_account_get_protocol_id(l->data))
+					&& purple_account_is_connected(l->data)) {
+				acct = l->data;
+				break;
+			}
+			l = l->next;
+		}
+	}
+
+	return acct;
+}
+
+static gboolean msn_uri_handler(const char *proto, const char *cmd, GHashTable *params)
+{
+	char *acct_id = g_hash_table_lookup(params, "account");
+	PurpleAccount *acct;
+
+	if (g_ascii_strcasecmp(proto, "msnim"))
+		return FALSE;
+
+	acct = find_acct("prpl-msn", acct_id);
+
+	if (!acct)
+		return FALSE;
+
+	/* msnim:chat?contact=user at domain.tld */
+	if (!g_ascii_strcasecmp(cmd, "Chat")) {
+		char *sname = g_hash_table_lookup(params, "contact");
+		if (sname) {
+			PurpleConversation *conv = purple_find_conversation_with_account(
+				PURPLE_CONV_TYPE_IM, sname, acct);
+			if (conv == NULL)
+				conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, sname);
+			purple_conversation_present(conv);
+		}
+		/*else
+			**If pidgindialogs_im() was in the core, we could use it here.
+			 * It is all purple_request_* based, but I'm not sure it really belongs in the core
+			pidgindialogs_im();*/
+
+		return TRUE;
+	}
+	/* msnim:add?contact=user at domain.tld */
+	else if (!g_ascii_strcasecmp(cmd, "Add")) {
+		char *name = g_hash_table_lookup(params, "contact");
+		purple_blist_request_add_buddy(acct, name, NULL, NULL);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+static PurplePluginProtocolInfo prpl_info =
+{
+	OPT_PROTO_MAIL_CHECK,
+	NULL,					/* user_splits */
+	NULL,					/* protocol_options */
+	{"png", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_SEND},	/* icon_spec */
+	msn_list_icon,			/* list_icon */
+	NULL,				/* list_emblems */
+	msn_status_text,		/* status_text */
+	msn_tooltip_text,		/* tooltip_text */
+	msn_status_types,		/* away_states */
+	msn_blist_node_menu,		/* blist_node_menu */
+	NULL,					/* chat_info */
+	NULL,					/* chat_info_defaults */
+	msn_login,			/* login */
+	msn_close,			/* close */
+	msn_send_im,			/* send_im */
+	NULL,					/* set_info */
+	msn_send_typing,		/* send_typing */
+	msn_get_info,			/* get_info */
+	msn_set_status,			/* set_away */
+	msn_set_idle,			/* set_idle */
+	NULL,					/* change_passwd */
+	msn_add_buddy,			/* add_buddy */
+	NULL,					/* add_buddies */
+	msn_rem_buddy,			/* remove_buddy */
+	NULL,					/* remove_buddies */
+	msn_add_permit,			/* add_permit */
+	msn_add_deny,			/* add_deny */
+	msn_rem_permit,			/* rem_permit */
+	msn_rem_deny,			/* rem_deny */
+	msn_set_permit_deny,		/* set_permit_deny */
+	NULL,					/* join_chat */
+	NULL,					/* reject chat invite */
+	NULL,					/* get_chat_name */
+	msn_chat_invite,		/* chat_invite */
+	msn_chat_leave,			/* chat_leave */
+	NULL,					/* chat_whisper */
+	msn_chat_send,			/* chat_send */
+	msn_keepalive,			/* keepalive */
+	NULL,					/* register_user */
+	NULL,					/* get_cb_info */
+	NULL,					/* get_cb_away */
+	NULL,					/* alias_buddy */
+	msn_group_buddy,		/* group_buddy */
+	msn_rename_group,		/* rename_group */
+	NULL,					/* buddy_free */
+	msn_convo_closed,		/* convo_closed */
+	msn_normalize,			/* normalize */
+	msn_set_buddy_icon,		/* set_buddy_icon */
+	msn_remove_group,		/* 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 */
+	msn_can_receive_file,	/* can_receive_file */
+	msn_send_file,			/* send_file */
+	msn_new_xfer,			/* new_xfer */
+	msn_offline_message,			/* offline_message */
+	NULL,					/* whiteboard_prpl_ops */
+	NULL,					/* send_raw */
+	NULL,					/* roomlist_room_serialize */
+	NULL,					/* unregister_user */
+	msn_send_attention,                     /* send_attention */
+	msn_attention_types,                    /* attention_types */
+
+	/* padding */
+	NULL
+};
+
+static PurplePluginInfo info =
+{
+	PURPLE_PLUGIN_MAGIC,
+	PURPLE_MAJOR_VERSION,
+	PURPLE_MINOR_VERSION,
+	PURPLE_PLUGIN_PROTOCOL,                             /**< type           */
+	NULL,                                             /**< ui_requirement */
+	0,                                                /**< flags          */
+	NULL,                                             /**< dependencies   */
+	PURPLE_PRIORITY_DEFAULT,                            /**< priority       */
+
+	"prpl-msn",                                       /**< id             */
+	"MSN",                                            /**< name           */
+	DISPLAY_VERSION,                                  /**< version        */
+	                                                  /**  summary        */
+	N_("MSN Protocol Plugin"),
+	                                                  /**  description    */
+	N_("MSN Protocol Plugin"),
+	"Christian Hammond <chipx86 at gnupdate.org>",       /**< author         */
+	PURPLE_WEBSITE,                                     /**< homepage       */
+
+	msn_load,                                         /**< load           */
+	msn_unload,                                       /**< unload         */
+	NULL,                                             /**< destroy        */
+
+	NULL,                                             /**< ui_info        */
+	&prpl_info,                                       /**< extra_info     */
+	NULL,                                             /**< prefs_info     */
+	msn_actions,
+
+	/* padding */
+	NULL,
+	NULL,
+	NULL,
+	NULL
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+	PurpleAccountOption *option;
+
+	option = purple_account_option_string_new(_("Server"), "server",
+											MSN_SERVER);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+
+	option = purple_account_option_int_new(_("Port"), "port", 1863);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+
+	option = purple_account_option_bool_new(_("Use HTTP Method"),
+										  "http_method", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+
+	option = purple_account_option_string_new(_("HTTP Method Server"),
+										  "http_method_server", MSN_HTTPCONN_SERVER);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+
+	option = purple_account_option_bool_new(_("Show custom smileys"),
+										  "custom_smileys", TRUE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+
+	purple_cmd_register("nudge", "", PURPLE_CMD_P_PRPL,
+	                  PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY,
+	                 "prpl-msn", msn_cmd_nudge,
+	                  _("nudge: nudge a user to get their attention"), NULL);
+
+	purple_prefs_remove("/plugins/prpl/msn");
+
+	purple_signal_connect(purple_get_core(), "uri-handler", plugin,
+		PURPLE_CALLBACK(msn_uri_handler), NULL);
+}
+
+PURPLE_INIT_PLUGIN(msn, init_plugin, info);
============================================================
--- libpurple/protocols/msnp9/msn.h	b22e20e606eb32b0398e620861065293c19a5b1c
+++ libpurple/protocols/msnp9/msn.h	b22e20e606eb32b0398e620861065293c19a5b1c
@@ -0,0 +1,134 @@
+/**
+ * @file msn.h The MSN protocol plugin
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_H_
+#define _MSN_H_
+
+/* #define MSN_DEBUG_MSG 1 */
+/* #define MSN_DEBUG_SLPMSG 1 */
+/* #define MSN_DEBUG_HTTP 1 */
+
+/* #define MSN_DEBUG_SLP 1 */
+/* #define MSN_DEBUG_SLP_VERBOSE 1 */
+/* #define MSN_DEBUG_SLP_FILES 1 */
+
+/* #define MSN_DEBUG_NS 1 */
+/* #define MSN_DEBUG_SB 1 */
+
+#include "internal.h"
+
+#include "account.h"
+#include "accountopt.h"
+#include "blist.h"
+#include "connection.h"
+#include "conversation.h"
+#include "debug.h"
+#include "cipher.h"
+#include "notify.h"
+#include "privacy.h"
+#include "proxy.h"
+#include "prpl.h"
+#include "request.h"
+#include "servconn.h"
+#include "sslconn.h"
+#include "util.h"
+
+#include "ft.h"
+
+#define MSN_BUF_LEN 8192
+
+#define USEROPT_MSNSERVER 3
+#define MSN_SERVER "messenger.hotmail.com"
+#define MSN_HTTPCONN_SERVER "gateway.messenger.hotmail.com"
+#define USEROPT_MSNPORT 4
+#define MSN_PORT 1863
+
+#define MSN_TYPING_RECV_TIMEOUT 6
+#define MSN_TYPING_SEND_TIMEOUT	4
+
+#define HOTMAIL_URL "http://www.hotmail.com/cgi-bin/folders"
+#define PASSPORT_URL "http://lc1.law13.hotmail.passport.com/cgi-bin/dologin?login="
+#define PROFILE_URL "http://spaces.live.com/profile.aspx?mem="
+
+#define USEROPT_HOTMAIL 0
+
+#define BUDDY_ALIAS_MAXLEN 387
+
+#define MSN_FT_GUID "{5D3E02AB-6190-11d3-BBBB-00C04F795683}"
+
+#define MSN_CLIENTINFO \
+	"Client-Name: Purple/" VERSION "\r\n" \
+	"Chat-Logging: Y\r\n"
+
+/* Index into attention_types */
+#define MSN_NUDGE 0
+
+typedef enum
+{
+	MSN_LIST_FL_OP = 0x01,
+	MSN_LIST_AL_OP = 0x02,
+	MSN_LIST_BL_OP = 0x04,
+	MSN_LIST_RL_OP = 0x08
+
+} MsnListOp;
+
+typedef enum
+{
+	MSN_CLIENT_CAP_WIN_MOBILE = 0x00001,
+	MSN_CLIENT_CAP_UNKNOWN_1  = 0x00002,
+	MSN_CLIENT_CAP_INK_GIF    = 0x00004,
+	MSN_CLIENT_CAP_INK_ISF    = 0x00008,
+	MSN_CLIENT_CAP_VIDEO_CHAT = 0x00010,
+	MSN_CLIENT_CAP_BASE       = 0x00020,
+	MSN_CLIENT_CAP_MSNMOBILE  = 0x00040,
+	MSN_CLIENT_CAP_MSNDIRECT  = 0x00080,
+	MSN_CLIENT_CAP_WEBMSGR    = 0x00100,
+	MSN_CLIENT_CAP_DIRECTIM   = 0x04000,
+	MSN_CLIENT_CAP_WINKS      = 0x08000,
+	MSN_CLIENT_CAP_SEARCH     = 0x10000
+
+} MsnClientCaps;
+
+typedef enum
+{
+	MSN_CLIENT_VER_5_0 = 0x00,
+	MSN_CLIENT_VER_6_0 = 0x10,	/* MSNC1 */
+	MSN_CLIENT_VER_6_1 = 0x20,	/* MSNC2 */
+	MSN_CLIENT_VER_6_2 = 0x30,	/* MSNC3 */
+	MSN_CLIENT_VER_7_0 = 0x40,	/* MSNC4 */
+	MSN_CLIENT_VER_7_5 = 0x50	/* MSNC5 */
+
+} MsnClientVerId;
+
+#define MSN_CLIENT_ID_VERSION      MSN_CLIENT_VER_7_0
+#define MSN_CLIENT_ID_RESERVED_1   0x00
+#define MSN_CLIENT_ID_RESERVED_2   0x00
+#define MSN_CLIENT_ID_CAPABILITIES MSN_CLIENT_CAP_BASE
+
+#define MSN_CLIENT_ID \
+	((MSN_CLIENT_ID_VERSION    << 24) | \
+	 (MSN_CLIENT_ID_RESERVED_1 << 16) | \
+	 (MSN_CLIENT_ID_RESERVED_2 <<  8) | \
+	 (MSN_CLIENT_ID_CAPABILITIES))
+
+#endif /* _MSN_H_ */
============================================================
--- libpurple/protocols/msnp9/nexus.c	595146178ae1b75fd0e9b6c25c0d0ec32a9df5be
+++ libpurple/protocols/msnp9/nexus.c	595146178ae1b75fd0e9b6c25c0d0ec32a9df5be
@@ -0,0 +1,511 @@
+/**
+ * @file nexus.c MSN Nexus functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "nexus.h"
+#include "notification.h"
+
+/**************************************************************************
+ * Main
+ **************************************************************************/
+
+MsnNexus *
+msn_nexus_new(MsnSession *session)
+{
+	MsnNexus *nexus;
+
+	nexus = g_new0(MsnNexus, 1);
+	nexus->session = session;
+	nexus->challenge_data = g_hash_table_new_full(g_str_hash,
+		g_str_equal, g_free, g_free);
+
+	return nexus;
+}
+
+void
+msn_nexus_destroy(MsnNexus *nexus)
+{
+	if (nexus->gsc)
+		purple_ssl_close(nexus->gsc);
+
+	g_free(nexus->login_host);
+
+	g_free(nexus->login_path);
+
+	if (nexus->challenge_data != NULL)
+		g_hash_table_destroy(nexus->challenge_data);
+
+	if (nexus->input_handler > 0)
+		purple_input_remove(nexus->input_handler);
+	g_free(nexus->write_buf);
+	g_free(nexus->read_buf);
+
+	g_free(nexus);
+}
+
+/**************************************************************************
+ * Util
+ **************************************************************************/
+
+static gssize
+msn_ssl_read(MsnNexus *nexus)
+{
+	gssize len;
+	char temp_buf[4096];
+
+	if ((len = purple_ssl_read(nexus->gsc, temp_buf,
+			sizeof(temp_buf))) > 0)
+	{
+		nexus->read_buf = g_realloc(nexus->read_buf,
+			nexus->read_len + len + 1);
+		strncpy(nexus->read_buf + nexus->read_len, temp_buf, len);
+		nexus->read_len += len;
+		nexus->read_buf[nexus->read_len] = '\0';
+	}
+
+	return len;
+}
+
+static void
+nexus_write_cb(gpointer data, gint source, PurpleInputCondition cond)
+{
+	MsnNexus *nexus = data;
+	int len, total_len;
+
+	total_len = strlen(nexus->write_buf);
+
+	len = purple_ssl_write(nexus->gsc,
+		nexus->write_buf + nexus->written_len,
+		total_len - nexus->written_len);
+
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len <= 0) {
+		purple_input_remove(nexus->input_handler);
+		nexus->input_handler = 0;
+		/* TODO: notify of the error */
+		return;
+	}
+	nexus->written_len += len;
+
+	if (nexus->written_len < total_len)
+		return;
+
+	purple_input_remove(nexus->input_handler);
+	nexus->input_handler = 0;
+
+	g_free(nexus->write_buf);
+	nexus->write_buf = NULL;
+	nexus->written_len = 0;
+
+	nexus->written_cb(nexus, source, 0);
+}
+
+/**************************************************************************
+ * Login
+ **************************************************************************/
+
+static void
+login_connect_cb(gpointer data, PurpleSslConnection *gsc,
+				 PurpleInputCondition cond);
+
+static void
+login_error_cb(PurpleSslConnection *gsc, PurpleSslErrorType error, void *data)
+{
+	MsnNexus *nexus;
+	MsnSession *session;
+
+	nexus = data;
+	g_return_if_fail(nexus != NULL);
+
+	nexus->gsc = NULL;
+
+	session = nexus->session;
+	g_return_if_fail(session != NULL);
+
+	msn_session_set_error(session, MSN_ERROR_AUTH, _("Unable to connect"));
+	/* the above line will result in nexus being destroyed, so we don't want
+	 * to destroy it here, or we'd crash */
+}
+
+static void
+nexus_login_written_cb(gpointer data, gint source, PurpleInputCondition cond)
+{
+	MsnNexus *nexus = data;
+	MsnSession *session;
+	int len;
+
+	session = nexus->session;
+	g_return_if_fail(session != NULL);
+
+	if (nexus->input_handler == 0)
+		/* TODO: Use purple_ssl_input_add()? */
+		nexus->input_handler = purple_input_add(nexus->gsc->fd,
+			PURPLE_INPUT_READ, nexus_login_written_cb, nexus);
+
+
+	len = msn_ssl_read(nexus);
+
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len < 0) {
+		purple_input_remove(nexus->input_handler);
+		nexus->input_handler = 0;
+		g_free(nexus->read_buf);
+		nexus->read_buf = NULL;
+		nexus->read_len = 0;
+		/* TODO: error handling */
+		return;
+	}
+
+	if (g_strstr_len(nexus->read_buf, nexus->read_len,
+			"\r\n\r\n") == NULL)
+		return;
+
+	purple_input_remove(nexus->input_handler);
+	nexus->input_handler = 0;
+
+	purple_ssl_close(nexus->gsc);
+	nexus->gsc = NULL;
+
+	purple_debug_misc("msn", "ssl buffer: {%s}\n", nexus->read_buf);
+
+	if (strstr(nexus->read_buf, "HTTP/1.1 302") != NULL)
+	{
+		/* Redirect. */
+		char *location, *c;
+
+		location = strstr(nexus->read_buf, "Location: ");
+		if (location == NULL)
+		{
+			g_free(nexus->read_buf);
+			nexus->read_buf = NULL;
+			nexus->read_len = 0;
+
+			return;
+		}
+		location = strchr(location, ' ') + 1;
+
+		if ((c = strchr(location, '\r')) != NULL)
+			*c = '\0';
+
+		/* Skip the http:// */
+		if ((c = strchr(location, '/')) != NULL)
+			location = c + 2;
+
+		if ((c = strchr(location, '/')) != NULL)
+		{
+			g_free(nexus->login_path);
+			nexus->login_path = g_strdup(c);
+
+			*c = '\0';
+		}
+
+		g_free(nexus->login_host);
+		nexus->login_host = g_strdup(location);
+
+		nexus->gsc = purple_ssl_connect(session->account,
+				nexus->login_host, PURPLE_SSL_DEFAULT_PORT,
+				login_connect_cb, login_error_cb, nexus);
+	}
+	else if (strstr(nexus->read_buf, "HTTP/1.1 401 Unauthorized") != NULL)
+	{
+		const char *error;
+
+		if ((error = strstr(nexus->read_buf, "WWW-Authenticate")) != NULL)
+		{
+			if ((error = strstr(error, "cbtxt=")) != NULL)
+			{
+				const char *c;
+				char *temp;
+
+				error += strlen("cbtxt=");
+
+				if ((c = strchr(error, '\n')) == NULL)
+					c = error + strlen(error);
+
+				temp = g_strndup(error, c - error);
+				error = purple_url_decode(temp);
+				g_free(temp);
+				if ((temp = strstr(error, " Do one of the following or try again:")) != NULL)
+					*temp = '\0';
+			}
+		}
+
+		msn_session_set_error(session, MSN_ERROR_AUTH, error);
+	}
+	else if (strstr(nexus->read_buf, "HTTP/1.1 503 Service Unavailable"))
+	{
+		msn_session_set_error(session, MSN_ERROR_SERV_UNAVAILABLE, NULL);
+	}
+	else if (strstr(nexus->read_buf, "HTTP/1.1 200 OK"))
+	{
+		char *base, *c;
+		char *login_params;
+
+#if 0
+		/* All your base are belong to us. */
+		base = buffer;
+
+		/* For great cookie! */
+		while ((base = strstr(base, "Set-Cookie: ")) != NULL)
+		{
+			base += strlen("Set-Cookie: ");
+
+			c = strchr(base, ';');
+
+			session->login_cookies =
+				g_list_append(session->login_cookies,
+							  g_strndup(base, c - base));
+		}
+#endif
+
+		base  = strstr(nexus->read_buf, "Authentication-Info: ");
+
+		g_return_if_fail(base != NULL);
+
+		base  = strstr(base, "from-PP='");
+		base += strlen("from-PP='");
+		c     = strchr(base, '\'');
+
+		login_params = g_strndup(base, c - base);
+
+		msn_got_login_params(session, login_params);
+
+		g_free(login_params);
+
+		msn_nexus_destroy(nexus);
+		session->nexus = NULL;
+		return;
+	}
+
+	g_free(nexus->read_buf);
+	nexus->read_buf = NULL;
+	nexus->read_len = 0;
+
+}
+
+/* this guards against missing hash entries */
+static char *
+nexus_challenge_data_lookup(GHashTable *challenge_data, const char *key)
+{
+	char *entry;
+
+	return (entry = (char *)g_hash_table_lookup(challenge_data, key)) ?
+		entry : "(null)";
+}
+
+void
+login_connect_cb(gpointer data, PurpleSslConnection *gsc,
+				 PurpleInputCondition cond)
+{
+	MsnNexus *nexus;
+	MsnSession *session;
+	char *username, *password;
+	char *request_str, *head, *tail;
+	char *buffer = NULL;
+	guint32 ctint;
+
+	nexus = data;
+	g_return_if_fail(nexus != NULL);
+
+	session = nexus->session;
+	g_return_if_fail(session != NULL);
+
+	msn_session_set_login_step(session, MSN_LOGIN_STEP_GET_COOKIE);
+
+	username =
+		g_strdup(purple_url_encode(purple_account_get_username(session->account)));
+
+	password =
+		g_strndup(purple_url_encode(purple_connection_get_password(session->account->gc)), 16);
+
+	ctint = strtoul((char *)g_hash_table_lookup(nexus->challenge_data, "ct"), NULL, 10) + 200;
+
+	head = g_strdup_printf(
+		"GET %s HTTP/1.1\r\n"
+		"Authorization: Passport1.4 OrgVerb=GET,OrgURL=%s,sign-in=%s",
+		nexus->login_path,
+		(char *)g_hash_table_lookup(nexus->challenge_data, "ru"),
+		username);
+
+	tail = g_strdup_printf(
+		"lc=%s,id=%s,tw=%s,fs=%s,ru=%s,ct=%" G_GUINT32_FORMAT ",kpp=%s,kv=%s,ver=%s,tpf=%s\r\n"
+		"User-Agent: MSMSGS\r\n"
+		"Host: %s\r\n"
+		"Connection: Keep-Alive\r\n"
+		"Cache-Control: no-cache\r\n",
+		nexus_challenge_data_lookup(nexus->challenge_data, "lc"),
+		nexus_challenge_data_lookup(nexus->challenge_data, "id"),
+		nexus_challenge_data_lookup(nexus->challenge_data, "tw"),
+		nexus_challenge_data_lookup(nexus->challenge_data, "fs"),
+		nexus_challenge_data_lookup(nexus->challenge_data, "ru"),
+		ctint,
+		nexus_challenge_data_lookup(nexus->challenge_data, "kpp"),
+		nexus_challenge_data_lookup(nexus->challenge_data, "kv"),
+		nexus_challenge_data_lookup(nexus->challenge_data, "ver"),
+		nexus_challenge_data_lookup(nexus->challenge_data, "tpf"),
+		nexus->login_host);
+
+	buffer = g_strdup_printf("%s,pwd=XXXXXXXX,%s\r\n", head, tail);
+	request_str = g_strdup_printf("%s,pwd=%s,%s\r\n", head, password, tail);
+
+	purple_debug_misc("msn", "Sending: {%s}\n", buffer);
+
+	g_free(buffer);
+	g_free(head);
+	g_free(tail);
+	g_free(username);
+	g_free(password);
+
+	nexus->write_buf = request_str;
+	nexus->written_len = 0;
+
+	nexus->read_len = 0;
+
+	nexus->written_cb = nexus_login_written_cb;
+
+	nexus->input_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE,
+		nexus_write_cb, nexus);
+
+	nexus_write_cb(nexus, gsc->fd, PURPLE_INPUT_WRITE);
+
+	return;
+
+
+}
+
+static void
+nexus_connect_written_cb(gpointer data, gint source, PurpleInputCondition cond)
+{
+	MsnNexus *nexus = data;
+	int len;
+	char *da_login;
+	char *base, *c;
+
+	if (nexus->input_handler == 0)
+		/* TODO: Use purple_ssl_input_add()? */
+		nexus->input_handler = purple_input_add(nexus->gsc->fd,
+			PURPLE_INPUT_READ, nexus_connect_written_cb, nexus);
+
+	/* Get the PassportURLs line. */
+	len = msn_ssl_read(nexus);
+
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len < 0) {
+		purple_input_remove(nexus->input_handler);
+		nexus->input_handler = 0;
+		g_free(nexus->read_buf);
+		nexus->read_buf = NULL;
+		nexus->read_len = 0;
+		/* TODO: error handling */
+		return;
+	}
+
+	if (g_strstr_len(nexus->read_buf, nexus->read_len,
+			"\r\n\r\n") == NULL)
+		return;
+
+	purple_input_remove(nexus->input_handler);
+	nexus->input_handler = 0;
+
+	base = strstr(nexus->read_buf, "PassportURLs");
+
+	if (base == NULL)
+	{
+		g_free(nexus->read_buf);
+		nexus->read_buf = NULL;
+		nexus->read_len = 0;
+		return;
+	}
+
+	if ((da_login = strstr(base, "DALogin=")) != NULL)
+	{
+		/* skip over "DALogin=" */
+		da_login += 8;
+
+		if ((c = strchr(da_login, ',')) != NULL)
+			*c = '\0';
+
+		if ((c = strchr(da_login, '/')) != NULL)
+		{
+			nexus->login_path = g_strdup(c);
+			*c = '\0';
+		}
+
+		nexus->login_host = g_strdup(da_login);
+	}
+
+	g_free(nexus->read_buf);
+	nexus->read_buf = NULL;
+	nexus->read_len = 0;
+
+	purple_ssl_close(nexus->gsc);
+
+	/* Now begin the connection to the login server. */
+	nexus->gsc = purple_ssl_connect(nexus->session->account,
+			nexus->login_host, PURPLE_SSL_DEFAULT_PORT,
+			login_connect_cb, login_error_cb, nexus);
+}
+
+
+/**************************************************************************
+ * Connect
+ **************************************************************************/
+
+static void
+nexus_connect_cb(gpointer data, PurpleSslConnection *gsc,
+				 PurpleInputCondition cond)
+{
+	MsnNexus *nexus;
+	MsnSession *session;
+
+	nexus = data;
+	g_return_if_fail(nexus != NULL);
+
+	session = nexus->session;
+	g_return_if_fail(session != NULL);
+
+	msn_session_set_login_step(session, MSN_LOGIN_STEP_AUTH);
+
+	nexus->write_buf = g_strdup("GET /rdr/pprdr.asp\r\n\r\n");
+	nexus->written_len = 0;
+
+	nexus->read_len = 0;
+
+	nexus->written_cb = nexus_connect_written_cb;
+
+	nexus->input_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE,
+		nexus_write_cb, nexus);
+
+	nexus_write_cb(nexus, gsc->fd, PURPLE_INPUT_WRITE);
+}
+
+void
+msn_nexus_connect(MsnNexus *nexus)
+{
+	nexus->gsc = purple_ssl_connect(nexus->session->account,
+			"nexus.passport.com", PURPLE_SSL_DEFAULT_PORT,
+			nexus_connect_cb, login_error_cb, nexus);
+}
============================================================
--- libpurple/protocols/msnp9/nexus.h	31c25a806fbb3fb7a66700dc4ee388756facb352
+++ libpurple/protocols/msnp9/nexus.h	31c25a806fbb3fb7a66700dc4ee388756facb352
@@ -0,0 +1,52 @@
+/**
+ * @file nexus.h MSN Nexus functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_NEXUS_H_
+#define _MSN_NEXUS_H_
+
+typedef struct _MsnNexus MsnNexus;
+
+struct _MsnNexus
+{
+	MsnSession *session;
+
+	char *login_host;
+	char *login_path;
+	GHashTable *challenge_data;
+	PurpleSslConnection *gsc;
+
+	guint input_handler;
+
+	char *write_buf;
+	gsize written_len;
+	PurpleInputFunction written_cb;
+
+	char *read_buf;
+	gsize read_len;
+};
+
+void msn_nexus_connect(MsnNexus *nexus);
+MsnNexus *msn_nexus_new(MsnSession *session);
+void msn_nexus_destroy(MsnNexus *nexus);
+
+#endif /* _MSN_NEXUS_H_ */
============================================================
--- libpurple/protocols/msnp9/notification.c	cbedda79fceb1d07ca3c9a8fa4f5461cae25e2c0
+++ libpurple/protocols/msnp9/notification.c	cbedda79fceb1d07ca3c9a8fa4f5461cae25e2c0
@@ -0,0 +1,1464 @@
+/**
+ * @file notification.c Notification server functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "notification.h"
+#include "state.h"
+#include "error.h"
+#include "msn-utils.h"
+#include "page.h"
+
+#include "userlist.h"
+#include "sync.h"
+#include "slplink.h"
+
+static MsnTable *cbs_table;
+
+/**************************************************************************
+ * Main
+ **************************************************************************/
+
+static void
+destroy_cb(MsnServConn *servconn)
+{
+	MsnNotification *notification;
+
+	notification = servconn->cmdproc->data;
+	g_return_if_fail(notification != NULL);
+
+	msn_notification_destroy(notification);
+}
+
+MsnNotification *
+msn_notification_new(MsnSession *session)
+{
+	MsnNotification *notification;
+	MsnServConn *servconn;
+
+	g_return_val_if_fail(session != NULL, NULL);
+
+	notification = g_new0(MsnNotification, 1);
+
+	notification->session = session;
+	notification->servconn = servconn = msn_servconn_new(session, MSN_SERVCONN_NS);
+	msn_servconn_set_destroy_cb(servconn, destroy_cb);
+
+	notification->cmdproc = servconn->cmdproc;
+	notification->cmdproc->data = notification;
+	notification->cmdproc->cbs_table = cbs_table;
+
+	return notification;
+}
+
+void
+msn_notification_destroy(MsnNotification *notification)
+{
+	notification->cmdproc->data = NULL;
+
+	msn_servconn_set_destroy_cb(notification->servconn, NULL);
+
+	msn_servconn_destroy(notification->servconn);
+
+	g_free(notification);
+}
+
+/**************************************************************************
+ * Connect
+ **************************************************************************/
+
+static void
+connect_cb(MsnServConn *servconn)
+{
+	MsnCmdProc *cmdproc;
+	MsnSession *session;
+	PurpleAccount *account;
+	char **a, **c, *vers;
+	int i;
+
+	g_return_if_fail(servconn != NULL);
+
+	cmdproc = servconn->cmdproc;
+	session = servconn->session;
+	account = session->account;
+
+	/* Allocate an array for CVR0, NULL, and all the versions */
+	a = c = g_new0(char *, session->protocol_ver - 8 + 3);
+
+	for (i = session->protocol_ver; i >= 8; i--)
+		*c++ = g_strdup_printf("MSNP%d", i);
+
+	*c++ = g_strdup("CVR0");
+
+	vers = g_strjoinv(" ", a);
+
+	if (session->login_step == MSN_LOGIN_STEP_START)
+		msn_session_set_login_step(session, MSN_LOGIN_STEP_HANDSHAKE);
+	else
+		msn_session_set_login_step(session, MSN_LOGIN_STEP_HANDSHAKE2);
+
+	msn_cmdproc_send(cmdproc, "VER", "%s", vers);
+
+	g_strfreev(a);
+	g_free(vers);
+}
+
+gboolean
+msn_notification_connect(MsnNotification *notification, const char *host, int port)
+{
+	MsnServConn *servconn;
+
+	g_return_val_if_fail(notification != NULL, FALSE);
+
+	servconn = notification->servconn;
+
+	msn_servconn_set_connect_cb(servconn, connect_cb);
+	notification->in_use = msn_servconn_connect(servconn, host, port);
+
+	return notification->in_use;
+}
+
+void
+msn_notification_disconnect(MsnNotification *notification)
+{
+	g_return_if_fail(notification != NULL);
+	g_return_if_fail(notification->in_use);
+
+	msn_servconn_disconnect(notification->servconn);
+
+	notification->in_use = FALSE;
+}
+
+/**************************************************************************
+ * Util
+ **************************************************************************/
+
+static void
+group_error_helper(MsnSession *session, const char *msg, int group_id, int error)
+{
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	char *reason = NULL;
+	char *title = NULL;
+
+	account = session->account;
+	gc = purple_account_get_connection(account);
+
+	if (error == 224)
+	{
+		if (group_id == 0)
+		{
+			return;
+		}
+		else
+		{
+			const char *group_name;
+			group_name =
+				msn_userlist_find_group_name(session->userlist,
+											 group_id);
+			reason = g_strdup_printf(_("%s is not a valid group."),
+									 group_name);
+		}
+	}
+	else
+	{
+		reason = g_strdup(_("Unknown error."));
+	}
+
+	title = g_strdup_printf(_("%s on %s (%s)"), msg,
+						  purple_account_get_username(account),
+						  purple_account_get_protocol_name(account));
+	purple_notify_error(gc, NULL, title, reason);
+	g_free(title);
+	g_free(reason);
+}
+
+/**************************************************************************
+ * Login
+ **************************************************************************/
+
+void
+msn_got_login_params(MsnSession *session, const char *login_params)
+{
+	MsnCmdProc *cmdproc;
+
+	cmdproc = session->notification->cmdproc;
+
+	msn_session_set_login_step(session, MSN_LOGIN_STEP_AUTH_END);
+
+	msn_cmdproc_send(cmdproc, "USR", "TWN S %s", login_params);
+}
+
+static void
+cvr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	PurpleAccount *account;
+
+	account = cmdproc->session->account;
+
+	msn_cmdproc_send(cmdproc, "USR", "TWN I %s",
+					 purple_account_get_username(account));
+}
+
+static void
+usr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	PurpleAccount *account;
+	PurpleConnection *gc;
+
+	session = cmdproc->session;
+	account = session->account;
+	gc = purple_account_get_connection(account);
+
+	if (!g_ascii_strcasecmp(cmd->params[1], "OK"))
+	{
+		/* OK */
+		const char *friendly = purple_url_decode(cmd->params[3]);
+
+		purple_connection_set_display_name(gc, friendly);
+
+		msn_session_set_login_step(session, MSN_LOGIN_STEP_SYN);
+
+		msn_cmdproc_send(cmdproc, "SYN", "%s", "0");
+	}
+	else if (!g_ascii_strcasecmp(cmd->params[1], "TWN"))
+	{
+		/* Passport authentication */
+		char **elems, **cur, **tokens;
+
+		session->nexus = msn_nexus_new(session);
+
+		/* Parse the challenge data. */
+
+		elems = g_strsplit(cmd->params[3], ",", 0);
+
+		for (cur = elems; *cur != NULL; cur++)
+		{
+				tokens = g_strsplit(*cur, "=", 2);
+				g_hash_table_insert(session->nexus->challenge_data, tokens[0], tokens[1]);
+				/* Don't free each of the tokens, only the array. */
+				g_free(tokens);
+		}
+
+		g_strfreev(elems);
+
+		msn_session_set_login_step(session, MSN_LOGIN_STEP_AUTH_START);
+
+		msn_nexus_connect(session->nexus);
+	}
+}
+
+static void
+usr_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error)
+{
+	MsnErrorType msnerr = 0;
+
+	switch (error)
+	{
+		case 500:
+		case 601:
+		case 910:
+		case 921:
+			msnerr = MSN_ERROR_SERV_UNAVAILABLE;
+			break;
+		case 911:
+			msnerr = MSN_ERROR_AUTH;
+			break;
+		default:
+			return;
+			break;
+	}
+
+	msn_session_set_error(cmdproc->session, msnerr, NULL);
+}
+
+static void
+ver_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	PurpleAccount *account;
+	gboolean protocol_supported = FALSE;
+	char proto_str[8];
+	size_t i;
+
+	session = cmdproc->session;
+	account = session->account;
+
+	g_snprintf(proto_str, sizeof(proto_str), "MSNP%d", session->protocol_ver);
+
+	for (i = 1; i < cmd->param_count; i++)
+	{
+		if (!strcmp(cmd->params[i], proto_str))
+		{
+			protocol_supported = TRUE;
+			break;
+		}
+	}
+
+	if (!protocol_supported)
+	{
+		msn_session_set_error(session, MSN_ERROR_UNSUPPORTED_PROTOCOL,
+							  NULL);
+		return;
+	}
+
+	msn_cmdproc_send(cmdproc, "CVR",
+					 "0x0409 winnt 5.1 i386 MSNMSGR 6.0.0602 MSMSGS %s",
+					 purple_account_get_username(account));
+}
+
+/**************************************************************************
+ * Log out
+ **************************************************************************/
+
+static void
+out_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	if (!g_ascii_strcasecmp(cmd->params[0], "OTH"))
+		msn_session_set_error(cmdproc->session, MSN_ERROR_SIGN_OTHER,
+							  NULL);
+	else if (!g_ascii_strcasecmp(cmd->params[0], "SSD"))
+		msn_session_set_error(cmdproc->session, MSN_ERROR_SERV_DOWN, NULL);
+}
+
+void
+msn_notification_close(MsnNotification *notification)
+{
+	g_return_if_fail(notification != NULL);
+
+	if (!notification->in_use)
+		return;
+
+	msn_cmdproc_send_quick(notification->cmdproc, "OUT", NULL, NULL);
+
+	msn_notification_disconnect(notification);
+}
+
+/**************************************************************************
+ * Messages
+ **************************************************************************/
+
+static void
+msg_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload,
+			 size_t len)
+{
+	MsnMessage *msg;
+
+	msg = msn_message_new_from_cmd(cmdproc->session, cmd);
+
+	msn_message_parse_payload(msg, payload, len);
+#ifdef MSN_DEBUG_NS
+	msn_message_show_readable(msg, "Notification", TRUE);
+#endif
+
+	msn_cmdproc_process_msg(cmdproc, msg);
+
+	msn_message_destroy(msg);
+}
+
+static void
+msg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	/* NOTE: cmd is not always cmdproc->last_cmd, sometimes cmd is a queued
+	 * command and we are processing it */
+
+	if (cmd->payload == NULL)
+	{
+		cmdproc->last_cmd->payload_cb  = msg_cmd_post;
+		cmdproc->servconn->payload_len = atoi(cmd->params[2]);
+	}
+	else
+	{
+		g_return_if_fail(cmd->payload_cb != NULL);
+
+		cmd->payload_cb(cmdproc, cmd, cmd->payload, cmd->payload_len);
+	}
+}
+
+/**************************************************************************
+ * Challenges
+ **************************************************************************/
+
+static void
+chl_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnTransaction *trans;
+	char buf[33];
+	const char *challenge_resp;
+	PurpleCipher *cipher;
+	PurpleCipherContext *context;
+	guchar digest[16];
+	int i;
+
+	cipher = purple_ciphers_find_cipher("md5");
+	context = purple_cipher_context_new(cipher, NULL);
+
+	purple_cipher_context_append(context, (const guchar *)cmd->params[1],
+							   strlen(cmd->params[1]));
+
+	challenge_resp = "VT6PX?UQTM4WM%YR";
+
+	purple_cipher_context_append(context, (const guchar *)challenge_resp,
+							   strlen(challenge_resp));
+	purple_cipher_context_digest(context, sizeof(digest), digest, NULL);
+	purple_cipher_context_destroy(context);
+
+	for (i = 0; i < 16; i++)
+		g_snprintf(buf + (i*2), 3, "%02x", digest[i]);
+
+	trans = msn_transaction_new(cmdproc, "QRY", "%s 32", "PROD0038W!61ZTF9");
+
+	msn_transaction_set_payload(trans, buf, 32);
+
+	msn_cmdproc_send_trans(cmdproc, trans);
+}
+
+/**************************************************************************
+ * Buddy Lists
+ **************************************************************************/
+
+static void
+add_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	MsnUser *user;
+	const char *list;
+	const char *passport;
+	const char *friendly;
+	MsnListId list_id;
+	int group_id;
+
+	list     = cmd->params[1];
+	passport = cmd->params[3];
+	friendly = purple_url_decode(cmd->params[4]);
+
+	session = cmdproc->session;
+
+	user = msn_userlist_find_user(session->userlist, passport);
+
+	if (user == NULL)
+	{
+		user = msn_user_new(session->userlist, passport, friendly);
+		msn_userlist_add_user(session->userlist, user);
+	}
+	else
+		msn_user_set_friendly_name(user, friendly);
+
+	list_id = msn_get_list_id(list);
+
+	if (cmd->param_count >= 6)
+		group_id = atoi(cmd->params[5]);
+	else
+		group_id = -1;
+
+	msn_got_add_user(session, user, list_id, group_id);
+	msn_user_update(user);
+}
+
+static void
+add_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error)
+{
+	MsnSession *session;
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	const char *list, *passport;
+	char *reason = NULL;
+	char *msg = NULL;
+	char **params;
+
+	session = cmdproc->session;
+	account = session->account;
+	gc = purple_account_get_connection(account);
+	params = g_strsplit(trans->params, " ", 0);
+
+	list     = params[0];
+	passport = params[1];
+
+	if (!strcmp(list, "FL"))
+		msg = g_strdup_printf(_("Unable to add user on %s (%s)"),
+							  purple_account_get_username(account),
+							  purple_account_get_protocol_name(account));
+	else if (!strcmp(list, "BL"))
+		msg = g_strdup_printf(_("Unable to block user on %s (%s)"),
+							  purple_account_get_username(account),
+							  purple_account_get_protocol_name(account));
+	else if (!strcmp(list, "AL"))
+		msg = g_strdup_printf(_("Unable to permit user on %s (%s)"),
+							  purple_account_get_username(account),
+							  purple_account_get_protocol_name(account));
+
+	if (!strcmp(list, "FL"))
+	{
+		if (error == 210)
+		{
+			reason = g_strdup_printf(_("%s could not be added because "
+									   "your buddy list is full."), passport);
+		}
+	}
+
+	if (reason == NULL)
+	{
+		if (error == 208)
+		{
+			reason = g_strdup_printf(_("%s is not a valid passport account."),
+									 passport);
+		}
+		else if (error == 500)
+		{
+			reason = g_strdup(_("Service Temporarily Unavailable."));
+		}
+		else
+		{
+			reason = g_strdup(_("Unknown error."));
+		}
+	}
+
+	if (msg != NULL)
+	{
+		purple_notify_error(gc, NULL, msg, reason);
+		g_free(msg);
+	}
+
+	if (!strcmp(list, "FL"))
+	{
+		PurpleBuddy *buddy;
+
+		buddy = purple_find_buddy(account, passport);
+
+		if (buddy != NULL)
+			purple_blist_remove_buddy(buddy);
+	}
+
+	g_free(reason);
+
+	g_strfreev(params);
+}
+
+static void
+adg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	gint group_id;
+	const char *group_name;
+
+	session = cmdproc->session;
+
+	group_id = atoi(cmd->params[3]);
+
+	group_name = purple_url_decode(cmd->params[2]);
+
+	msn_group_new(session->userlist, group_id, group_name);
+
+	/* There is a user that must me moved to this group */
+	if (cmd->trans->data)
+	{
+		/* msn_userlist_move_buddy(); */
+		MsnUserList *userlist = cmdproc->session->userlist;
+		MsnMoveBuddy *data = cmd->trans->data;
+
+		if (data->old_group_name != NULL)
+		{
+			msn_userlist_rem_buddy(userlist, data->who, MSN_LIST_FL, data->old_group_name);
+			g_free(data->old_group_name);
+		}
+
+		msn_userlist_add_buddy(userlist, data->who, MSN_LIST_FL, group_name);
+		g_free(data->who);
+
+	}
+}
+
+static void
+qng_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	static int count = 0;
+	const char *passport;
+	PurpleAccount *account;
+
+	session = cmdproc->session;
+	account = session->account;
+
+	if (session->passport_info.file == NULL)
+		return;
+
+	passport = purple_normalize(account, purple_account_get_username(account));
+
+	if ((strstr(passport, "@hotmail.") != NULL) ||
+		(strstr(passport, "@msn.com") != NULL))
+		return;
+
+	if (count++ < 26)
+		return;
+
+	count = 0;
+	msn_cmdproc_send(cmdproc, "URL", "%s", "INBOX");
+}
+
+
+static void
+fln_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSlpLink *slplink;
+	MsnUser *user;
+
+	user = msn_userlist_find_user(cmdproc->session->userlist, cmd->params[0]);
+
+	user->status = "offline";
+	msn_user_update(user);
+
+	slplink = msn_session_find_slplink(cmdproc->session, cmd->params[0]);
+
+	if (slplink != NULL)
+		msn_slplink_destroy(slplink);
+
+}
+
+static void
+iln_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	MsnUser *user;
+	MsnObject *msnobj;
+	const char *state, *passport, *friendly;
+
+	session = cmdproc->session;
+	account = session->account;
+	gc = purple_account_get_connection(account);
+
+	state    = cmd->params[1];
+	passport = cmd->params[2];
+	friendly = purple_url_decode(cmd->params[3]);
+
+	user = msn_userlist_find_user(session->userlist, passport);
+
+	serv_got_alias(gc, passport, friendly);
+
+	msn_user_set_friendly_name(user, friendly);
+
+	if (session->protocol_ver >= 9 && cmd->param_count == 6)
+	{
+		msnobj = msn_object_new_from_string(purple_url_decode(cmd->params[5]));
+		msn_user_set_object(user, msnobj);
+	}
+
+	msn_user_set_state(user, state);
+	msn_user_update(user);
+}
+
+static void
+ipg_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len)
+{
+#if 0
+	purple_debug_misc("msn", "Incoming Page: {%s}\n", payload);
+#endif
+}
+
+static void
+ipg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	cmdproc->servconn->payload_len = atoi(cmd->params[0]);
+	cmdproc->last_cmd->payload_cb = ipg_cmd_post;
+}
+
+static void
+nln_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	MsnUser *user;
+	MsnObject *msnobj;
+	int clientid;
+	const char *state, *passport, *friendly, *old_friendly;
+
+	session = cmdproc->session;
+	account = session->account;
+	gc = purple_account_get_connection(account);
+
+	state    = cmd->params[0];
+	passport = cmd->params[1];
+	friendly = purple_url_decode(cmd->params[2]);
+
+	user = msn_userlist_find_user(session->userlist, passport);
+
+	old_friendly = msn_user_get_friendly_name(user);
+	if (!old_friendly || (old_friendly && (!friendly || strcmp(old_friendly, friendly))))
+	{
+		serv_got_alias(gc, passport, friendly);
+		msn_user_set_friendly_name(user, friendly);
+	}
+
+	if (session->protocol_ver >= 9)
+	{
+		if (cmd->param_count == 5)
+		{
+			msnobj =
+				msn_object_new_from_string(purple_url_decode(cmd->params[4]));
+			msn_user_set_object(user, msnobj);
+		}
+		else
+		{
+			msn_user_set_object(user, NULL);
+		}
+	}
+
+	clientid = atoi(cmd->params[3]);
+	user->mobile = (clientid & MSN_CLIENT_CAP_MSNMOBILE);
+
+	msn_user_set_state(user, state);
+	msn_user_update(user);
+}
+
+#if 0
+static void
+chg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	char *state = cmd->params[1];
+	int state_id = 0;
+
+	if (!strcmp(state, "NLN"))
+		state_id = MSN_ONLINE;
+	else if (!strcmp(state, "BSY"))
+		state_id = MSN_BUSY;
+	else if (!strcmp(state, "IDL"))
+		state_id = MSN_IDLE;
+	else if (!strcmp(state, "BRB"))
+		state_id = MSN_BRB;
+	else if (!strcmp(state, "AWY"))
+		state_id = MSN_AWAY;
+	else if (!strcmp(state, "PHN"))
+		state_id = MSN_PHONE;
+	else if (!strcmp(state, "LUN"))
+		state_id = MSN_LUNCH;
+	else if (!strcmp(state, "HDN"))
+		state_id = MSN_HIDDEN;
+
+	cmdproc->session->state = state_id;
+}
+#endif
+
+
+static void
+not_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len)
+{
+#if 0
+	MSN_SET_PARAMS("NOT %d\r\n%s", cmdproc->servconn->payload, payload);
+	purple_debug_misc("msn", "Notification: {%s}\n", payload);
+#endif
+}
+
+static void
+not_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	cmdproc->servconn->payload_len = atoi(cmd->params[0]);
+	cmdproc->last_cmd->payload_cb = not_cmd_post;
+}
+
+static void
+rea_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	/* TODO: This might be for us too */
+
+	MsnSession *session;
+	PurpleConnection *gc;
+	const char *friendly;
+
+	session = cmdproc->session;
+	gc = session->account->gc;
+	friendly = purple_url_decode(cmd->params[3]);
+
+	purple_connection_set_display_name(gc, friendly);
+}
+
+static void
+prp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session = cmdproc->session;
+	const char *type, *value;
+
+	g_return_if_fail(cmd->param_count >= 3);
+
+	type  = cmd->params[2];
+
+	if (cmd->param_count == 4)
+	{
+		value = cmd->params[3];
+		if (!strcmp(type, "PHH"))
+			msn_user_set_home_phone(session->user, purple_url_decode(value));
+		else if (!strcmp(type, "PHW"))
+			msn_user_set_work_phone(session->user, purple_url_decode(value));
+		else if (!strcmp(type, "PHM"))
+			msn_user_set_mobile_phone(session->user, purple_url_decode(value));
+	}
+	else
+	{
+		if (!strcmp(type, "PHH"))
+			msn_user_set_home_phone(session->user, NULL);
+		else if (!strcmp(type, "PHW"))
+			msn_user_set_work_phone(session->user, NULL);
+		else if (!strcmp(type, "PHM"))
+			msn_user_set_mobile_phone(session->user, NULL);
+	}
+}
+
+static void
+reg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	int group_id;
+	const char *group_name;
+
+	session = cmdproc->session;
+	group_id = atoi(cmd->params[2]);
+	group_name = purple_url_decode(cmd->params[3]);
+
+	msn_userlist_rename_group_id(session->userlist, group_id, group_name);
+}
+
+static void
+reg_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error)
+{
+	int group_id;
+	char **params;
+
+	params = g_strsplit(trans->params, " ", 0);
+
+	group_id = atoi(params[0]);
+
+	group_error_helper(cmdproc->session, _("Unable to rename group"), group_id, error);
+
+	g_strfreev(params);
+}
+
+static void
+rem_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	MsnUser *user;
+	const char *list;
+	const char *passport;
+	MsnListId list_id;
+	int group_id;
+
+	session = cmdproc->session;
+	list = cmd->params[1];
+	passport = cmd->params[3];
+	user = msn_userlist_find_user(session->userlist, passport);
+
+	g_return_if_fail(user != NULL);
+
+	list_id = msn_get_list_id(list);
+
+	if (cmd->param_count == 5)
+		group_id = atoi(cmd->params[4]);
+	else
+		group_id = -1;
+
+	msn_got_rem_user(session, user, list_id, group_id);
+	msn_user_update(user);
+}
+
+static void
+rmg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	int group_id;
+
+	session = cmdproc->session;
+	group_id = atoi(cmd->params[2]);
+
+	msn_userlist_remove_group_id(session->userlist, group_id);
+}
+
+static void
+rmg_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error)
+{
+	int group_id;
+	char **params;
+
+	params = g_strsplit(trans->params, " ", 0);
+
+	group_id = atoi(params[0]);
+
+	group_error_helper(cmdproc->session, _("Unable to delete group"), group_id, error);
+
+	g_strfreev(params);
+}
+
+static void
+syn_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	MsnSync *sync;
+	int total_users;
+
+	session = cmdproc->session;
+
+	if (cmd->param_count == 2)
+	{
+		/*
+		 * This can happen if we sent a SYN with an up-to-date
+		 * buddy list revision, but we send 0 to get a full list.
+		 * So, error out.
+		 */
+
+		msn_session_set_error(cmdproc->session, MSN_ERROR_BAD_BLIST, NULL);
+		return;
+	}
+
+	total_users  = atoi(cmd->params[2]);
+
+	sync = msn_sync_new(session);
+	sync->total_users = total_users;
+	sync->old_cbs_table = cmdproc->cbs_table;
+
+	session->sync = sync;
+	cmdproc->cbs_table = sync->cbs_table;
+}
+
+/**************************************************************************
+ * Misc commands
+ **************************************************************************/
+
+static void
+url_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	PurpleAccount *account;
+	const char *rru;
+	const char *url;
+	PurpleCipher *cipher;
+	PurpleCipherContext *context;
+	guchar digest[16];
+	FILE *fd;
+	char *buf;
+	char buf2[3];
+	char sendbuf[64];
+	int i;
+
+	session = cmdproc->session;
+	account = session->account;
+
+	rru = cmd->params[1];
+	url = cmd->params[2];
+
+	buf = g_strdup_printf("%s%lu%s",
+			   session->passport_info.mspauth ? session->passport_info.mspauth : "BOGUS",
+			   time(NULL) - session->passport_info.sl,
+			   purple_connection_get_password(account->gc));
+
+	cipher = purple_ciphers_find_cipher("md5");
+	context = purple_cipher_context_new(cipher, NULL);
+
+	purple_cipher_context_append(context, (const guchar *)buf, strlen(buf));
+	purple_cipher_context_digest(context, sizeof(digest), digest, NULL);
+	purple_cipher_context_destroy(context);
+
+	g_free(buf);
+
+	memset(sendbuf, 0, sizeof(sendbuf));
+
+	for (i = 0; i < 16; i++)
+	{
+		g_snprintf(buf2, sizeof(buf2), "%02x", digest[i]);
+		strcat(sendbuf, buf2);
+	}
+
+	if (session->passport_info.file != NULL)
+	{
+		g_unlink(session->passport_info.file);
+		g_free(session->passport_info.file);
+	}
+
+	if ((fd = purple_mkstemp(&session->passport_info.file, FALSE)) == NULL)
+	{
+		purple_debug_error("msn",
+						 "Error opening temp passport file: %s\n",
+						 strerror(errno));
+	}
+	else
+	{
+#ifdef _WIN32
+		fputs("<!-- saved from url=(0013)about:internet -->\n", fd);
+#endif
+		fputs("<html>\n"
+			  "<head>\n"
+			  "<noscript>\n"
+			  "<meta http-equiv=\"Refresh\" content=\"0; "
+			  "url=http://www.hotmail.com\">\n"
+			  "</noscript>\n"
+			  "</head>\n\n",
+			  fd);
+
+		fprintf(fd, "<body onload=\"document.pform.submit(); \">\n");
+		fprintf(fd, "<form name=\"pform\" action=\"%s\" method=\"POST\">\n\n",
+				url);
+		fprintf(fd, "<input type=\"hidden\" name=\"mode\" value=\"ttl\">\n");
+		fprintf(fd, "<input type=\"hidden\" name=\"login\" value=\"%s\">\n",
+				purple_account_get_username(account));
+		fprintf(fd, "<input type=\"hidden\" name=\"username\" value=\"%s\">\n",
+				purple_account_get_username(account));
+		if (session->passport_info.sid != NULL)
+			fprintf(fd, "<input type=\"hidden\" name=\"sid\" value=\"%s\">\n",
+					session->passport_info.sid);
+		if (session->passport_info.kv != NULL)
+			fprintf(fd, "<input type=\"hidden\" name=\"kv\" value=\"%s\">\n",
+					session->passport_info.kv);
+		fprintf(fd, "<input type=\"hidden\" name=\"id\" value=\"2\">\n");
+		fprintf(fd, "<input type=\"hidden\" name=\"sl\" value=\"%ld\">\n",
+				time(NULL) - session->passport_info.sl);
+		fprintf(fd, "<input type=\"hidden\" name=\"rru\" value=\"%s\">\n",
+				rru);
+		if (session->passport_info.mspauth != NULL)
+			fprintf(fd, "<input type=\"hidden\" name=\"auth\" value=\"%s\">\n",
+					session->passport_info.mspauth);
+		fprintf(fd, "<input type=\"hidden\" name=\"creds\" value=\"%s\">\n",
+				sendbuf); /* TODO Digest me (huh? -- ChipX86) */
+		fprintf(fd, "<input type=\"hidden\" name=\"svc\" value=\"mail\">\n");
+		fprintf(fd, "<input type=\"hidden\" name=\"js\" value=\"yes\">\n");
+		fprintf(fd, "</form></body>\n");
+		fprintf(fd, "</html>\n");
+
+		if (fclose(fd))
+		{
+			purple_debug_error("msn",
+							 "Error closing temp passport file: %s\n",
+							 strerror(errno));
+
+			g_unlink(session->passport_info.file);
+			g_free(session->passport_info.file);
+			session->passport_info.file = NULL;
+		}
+#ifdef _WIN32
+		else
+		{
+			/*
+			 * Renaming file with .html extension, so that the
+			 * win32 open_url will work.
+			 */
+			char *tmp;
+
+			if ((tmp =
+				g_strdup_printf("%s.html",
+					session->passport_info.file)) != NULL)
+			{
+				if (g_rename(session->passport_info.file,
+							tmp) == 0)
+				{
+					g_free(session->passport_info.file);
+					session->passport_info.file = tmp;
+				}
+				else
+					g_free(tmp);
+			}
+		}
+#endif
+	}
+}
+/**************************************************************************
+ * Switchboards
+ **************************************************************************/
+
+static void
+rng_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	MsnSwitchBoard *swboard;
+	const char *session_id;
+	char *host;
+	int port;
+
+	session = cmdproc->session;
+	session_id = cmd->params[0];
+
+	msn_parse_socket(cmd->params[1], &host, &port);
+
+	if (session->http_method)
+		port = 80;
+
+	swboard = msn_switchboard_new(session);
+
+	msn_switchboard_set_invited(swboard, TRUE);
+	msn_switchboard_set_session_id(swboard, cmd->params[0]);
+	msn_switchboard_set_auth_key(swboard, cmd->params[3]);
+	swboard->im_user = g_strdup(cmd->params[4]);
+	/* msn_switchboard_add_user(swboard, cmd->params[4]); */
+
+	if (!msn_switchboard_connect(swboard, host, port))
+		msn_switchboard_destroy(swboard);
+
+	g_free(host);
+}
+
+static void
+xfr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	char *host;
+	int port;
+
+	if (strcmp(cmd->params[1], "SB") && strcmp(cmd->params[1], "NS"))
+	{
+		/* Maybe we can have a generic bad command error. */
+		purple_debug_error("msn", "Bad XFR command (%s)\n", cmd->params[1]);
+		return;
+	}
+
+	msn_parse_socket(cmd->params[2], &host, &port);
+
+	if (!strcmp(cmd->params[1], "SB"))
+	{
+		purple_debug_error("msn", "This shouldn't be handled here.\n");
+	}
+	else if (!strcmp(cmd->params[1], "NS"))
+	{
+		MsnSession *session;
+
+		session = cmdproc->session;
+
+		msn_session_set_login_step(session, MSN_LOGIN_STEP_TRANSFER);
+
+		msn_notification_connect(session->notification, host, port);
+	}
+
+	g_free(host);
+}
+
+/**************************************************************************
+ * Message Types
+ **************************************************************************/
+
+static void
+profile_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	MsnSession *session;
+	const char *value;
+
+	session = cmdproc->session;
+
+	if (strcmp(msg->remote_user, "Hotmail"))
+		/* This isn't an official message. */
+		return;
+
+	if ((value = msn_message_get_attr(msg, "kv")) != NULL)
+	{
+		g_free(session->passport_info.kv);
+		session->passport_info.kv = g_strdup(value);
+	}
+
+	if ((value = msn_message_get_attr(msg, "sid")) != NULL)
+	{
+		g_free(session->passport_info.sid);
+		session->passport_info.sid = g_strdup(value);
+	}
+
+	if ((value = msn_message_get_attr(msg, "MSPAuth")) != NULL)
+	{
+		g_free(session->passport_info.mspauth);
+		session->passport_info.mspauth = g_strdup(value);
+	}
+
+	if ((value = msn_message_get_attr(msg, "ClientIP")) != NULL)
+	{
+		g_free(session->passport_info.client_ip);
+		session->passport_info.client_ip = g_strdup(value);
+	}
+
+	if ((value = msn_message_get_attr(msg, "ClientPort")) != NULL)
+		session->passport_info.client_port = ntohs(atoi(value));
+
+	if ((value = msn_message_get_attr(msg, "LoginTime")) != NULL)
+		session->passport_info.sl = atol(value);
+}
+
+static void
+initial_email_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	MsnSession *session;
+	PurpleConnection *gc;
+	GHashTable *table;
+	const char *unread;
+
+	session = cmdproc->session;
+	gc = session->account->gc;
+
+	if (strcmp(msg->remote_user, "Hotmail"))
+		/* This isn't an official message. */
+		return;
+
+	if (session->passport_info.file == NULL)
+	{
+		MsnTransaction *trans;
+		trans = msn_transaction_new(cmdproc, "URL", "%s", "INBOX");
+		msn_transaction_queue_cmd(trans, msg->cmd);
+
+		msn_cmdproc_send_trans(cmdproc, trans);
+
+		return;
+	}
+
+	if (!purple_account_get_check_mail(session->account))
+		return;
+
+	table = msn_message_get_hashtable_from_body(msg);
+
+	unread = g_hash_table_lookup(table, "Inbox-Unread");
+
+	if (unread != NULL)
+	{
+		int count = atoi(unread);
+
+		if (count > 0)
+		{
+			const char *passport;
+			const char *url;
+
+			passport = msn_user_get_passport(session->user);
+			url = session->passport_info.file;
+
+			purple_notify_emails(gc, atoi(unread), FALSE, NULL, NULL,
+							   &passport, &url, NULL, NULL);
+		}
+	}
+
+	g_hash_table_destroy(table);
+}
+
+static void
+email_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	MsnSession *session;
+	PurpleConnection *gc;
+	GHashTable *table;
+	char *from, *subject, *tmp;
+
+	session = cmdproc->session;
+	gc = session->account->gc;
+
+	if (strcmp(msg->remote_user, "Hotmail"))
+		/* This isn't an official message. */
+		return;
+
+	if (session->passport_info.file == NULL)
+	{
+		MsnTransaction *trans;
+		trans = msn_transaction_new(cmdproc, "URL", "%s", "INBOX");
+		msn_transaction_queue_cmd(trans, msg->cmd);
+
+		msn_cmdproc_send_trans(cmdproc, trans);
+
+		return;
+	}
+
+	if (!purple_account_get_check_mail(session->account))
+		return;
+
+	table = msn_message_get_hashtable_from_body(msg);
+
+	from = subject = NULL;
+
+	tmp = g_hash_table_lookup(table, "From");
+	if (tmp != NULL)
+		from = purple_mime_decode_field(tmp);
+
+	tmp = g_hash_table_lookup(table, "Subject");
+	if (tmp != NULL)
+		subject = purple_mime_decode_field(tmp);
+
+	purple_notify_email(gc,
+					  (subject != NULL ? subject : ""),
+					  (from != NULL ?  from : ""),
+					  msn_user_get_passport(session->user),
+					  session->passport_info.file, NULL, NULL);
+
+	g_free(from);
+	g_free(subject);
+
+	g_hash_table_destroy(table);
+}
+
+static void
+system_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	GHashTable *table;
+	const char *type_s;
+
+	if (strcmp(msg->remote_user, "Hotmail"))
+		/* This isn't an official message. */
+		return;
+
+	table = msn_message_get_hashtable_from_body(msg);
+
+	if ((type_s = g_hash_table_lookup(table, "Type")) != NULL)
+	{
+		int type = atoi(type_s);
+		char buf[MSN_BUF_LEN];
+		int minutes;
+
+		switch (type)
+		{
+			case 1:
+				minutes = atoi(g_hash_table_lookup(table, "Arg1"));
+				g_snprintf(buf, sizeof(buf), dngettext(PACKAGE, 
+							"The MSN server will shut down for maintenance "
+							"in %d minute. You will automatically be "
+							"signed out at that time.  Please finish any "
+							"conversations in progress.\n\nAfter the "
+							"maintenance has been completed, you will be "
+							"able to successfully sign in.",
+							"The MSN server will shut down for maintenance "
+							"in %d minutes. You will automatically be "
+							"signed out at that time.  Please finish any "
+							"conversations in progress.\n\nAfter the "
+							"maintenance has been completed, you will be "
+							"able to successfully sign in.", minutes),
+						minutes);
+			default:
+				break;
+		}
+
+		if (*buf != '\0')
+			purple_notify_info(cmdproc->session->account->gc, NULL, buf, NULL);
+	}
+
+	g_hash_table_destroy(table);
+}
+
+void
+msn_notification_add_buddy(MsnNotification *notification, const char *list,
+						   const char *who, const char *store_name,
+						   int group_id)
+{
+	MsnCmdProc *cmdproc;
+	cmdproc = notification->servconn->cmdproc;
+
+	if (group_id < 0 && !strcmp(list, "FL"))
+		group_id = 0;
+
+	if (group_id >= 0)
+	{
+		msn_cmdproc_send(cmdproc, "ADD", "%s %s %s %d",
+						 list, who, store_name, group_id);
+	}
+	else
+	{
+		msn_cmdproc_send(cmdproc, "ADD", "%s %s %s", list, who, store_name);
+	}
+}
+
+void
+msn_notification_rem_buddy(MsnNotification *notification, const char *list,
+						   const char *who, int group_id)
+{
+	MsnCmdProc *cmdproc;
+	cmdproc = notification->servconn->cmdproc;
+
+	if (group_id >= 0)
+	{
+		msn_cmdproc_send(cmdproc, "REM", "%s %s %d", list, who, group_id);
+	}
+	else
+	{
+		msn_cmdproc_send(cmdproc, "REM", "%s %s", list, who);
+	}
+}
+
+/**************************************************************************
+ * Init
+ **************************************************************************/
+
+void
+msn_notification_init(void)
+{
+	/* TODO: check prp, blp */
+
+	cbs_table = msn_table_new();
+
+	/* Synchronous */
+	msn_table_add_cmd(cbs_table, "CHG", "CHG", NULL);
+	msn_table_add_cmd(cbs_table, "CHG", "ILN", iln_cmd);
+	msn_table_add_cmd(cbs_table, "ADD", "ADD", add_cmd);
+	msn_table_add_cmd(cbs_table, "ADD", "ILN", iln_cmd);
+	msn_table_add_cmd(cbs_table, "REM", "REM", rem_cmd);
+	msn_table_add_cmd(cbs_table, "USR", "USR", usr_cmd);
+	msn_table_add_cmd(cbs_table, "USR", "XFR", xfr_cmd);
+	msn_table_add_cmd(cbs_table, "SYN", "SYN", syn_cmd);
+	msn_table_add_cmd(cbs_table, "CVR", "CVR", cvr_cmd);
+	msn_table_add_cmd(cbs_table, "VER", "VER", ver_cmd);
+	msn_table_add_cmd(cbs_table, "REA", "REA", rea_cmd);
+	msn_table_add_cmd(cbs_table, "PRP", "PRP", prp_cmd);
+	/* msn_table_add_cmd(cbs_table, "BLP", "BLP", blp_cmd); */
+	msn_table_add_cmd(cbs_table, "BLP", "BLP", NULL);
+	msn_table_add_cmd(cbs_table, "REG", "REG", reg_cmd);
+	msn_table_add_cmd(cbs_table, "ADG", "ADG", adg_cmd);
+	msn_table_add_cmd(cbs_table, "RMG", "RMG", rmg_cmd);
+	msn_table_add_cmd(cbs_table, "XFR", "XFR", xfr_cmd);
+
+	/* Asynchronous */
+	msn_table_add_cmd(cbs_table, NULL, "IPG", ipg_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "MSG", msg_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "NOT", not_cmd);
+
+	msn_table_add_cmd(cbs_table, NULL, "CHL", chl_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "REM", rem_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "ADD", add_cmd);
+
+	msn_table_add_cmd(cbs_table, NULL, "QRY", NULL);
+	msn_table_add_cmd(cbs_table, NULL, "QNG", qng_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "FLN", fln_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "NLN", nln_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "ILN", iln_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "OUT", out_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "RNG", rng_cmd);
+
+	msn_table_add_cmd(cbs_table, NULL, "URL", url_cmd);
+
+	msn_table_add_cmd(cbs_table, "fallback", "XFR", xfr_cmd);
+
+	msn_table_add_error(cbs_table, "ADD", add_error);
+	msn_table_add_error(cbs_table, "REG", reg_error);
+	msn_table_add_error(cbs_table, "RMG", rmg_error);
+	/* msn_table_add_error(cbs_table, "REA", rea_error); */
+	msn_table_add_error(cbs_table, "USR", usr_error);
+
+	msn_table_add_msg_type(cbs_table,
+						   "text/x-msmsgsprofile",
+						   profile_msg);
+	msn_table_add_msg_type(cbs_table,
+						   "text/x-msmsgsinitialemailnotification",
+						   initial_email_msg);
+	msn_table_add_msg_type(cbs_table,
+						   "text/x-msmsgsemailnotification",
+						   email_msg);
+	msn_table_add_msg_type(cbs_table,
+						   "application/x-msmsgssystemmessage",
+						   system_msg);
+}
+
+void
+msn_notification_end(void)
+{
+	msn_table_destroy(cbs_table);
+}
============================================================
--- libpurple/protocols/msnp9/notification.h	681e80141cc08b532276ee779c47cf0a1a770fc3
+++ libpurple/protocols/msnp9/notification.h	681e80141cc08b532276ee779c47cf0a1a770fc3
@@ -0,0 +1,70 @@
+/**
+ * @file notification.h Notification server functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_NOTIFICATION_H_
+#define _MSN_NOTIFICATION_H_
+
+typedef struct _MsnNotification MsnNotification;
+
+#include "session.h"
+#include "servconn.h"
+#include "cmdproc.h"
+
+struct _MsnNotification
+{
+	MsnSession *session;
+	MsnCmdProc *cmdproc;
+	MsnServConn *servconn;
+
+	gboolean in_use;
+};
+
+#include "state.h"
+
+void msn_notification_end(void);
+void msn_notification_init(void);
+
+void msn_notification_add_buddy(MsnNotification *notification,
+								const char *list, const char *who,
+								const char *store_name, int group_id);
+void msn_notification_rem_buddy(MsnNotification *notification,
+								const char *list, const char *who,
+								int group_id);
+MsnNotification *msn_notification_new(MsnSession *session);
+void msn_notification_destroy(MsnNotification *notification);
+gboolean msn_notification_connect(MsnNotification *notification,
+							  const char *host, int port);
+void msn_notification_disconnect(MsnNotification *notification);
+
+/**
+ * Closes a notification.
+ *
+ * It's first closed, and then disconnected.
+ * 
+ * @param notification The notification object to close.
+ */
+void msn_notification_close(MsnNotification *notification);
+
+void msn_got_login_params(MsnSession *session, const char *login_params);
+
+#endif /* _MSN_NOTIFICATION_H_ */
============================================================
--- libpurple/protocols/msnp9/object.c	38449587c1dcf0341a9781cab58011c3221914d9
+++ libpurple/protocols/msnp9/object.c	38449587c1dcf0341a9781cab58011c3221914d9
@@ -0,0 +1,345 @@
+/**
+ * @file object.c MSNObject API
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "object.h"
+#include "debug.h"
+
+#define GET_STRING_TAG(field, id) \
+	if ((tag = strstr(str, id "=\"")) != NULL) \
+	{ \
+		tag += strlen(id "=\""); \
+		c = strchr(tag, '"'); \
+		if (c != NULL) \
+		{ \
+			if (obj->field != NULL) \
+				g_free(obj->field); \
+			obj->field = g_strndup(tag, c - tag); \
+		} \
+	}
+
+#define GET_INT_TAG(field, id) \
+	if ((tag = strstr(str, id "=\"")) != NULL) \
+	{ \
+		char buf[16]; \
+		size_t offset; \
+		tag += strlen(id "=\""); \
+		c = strchr(tag, '"'); \
+		if (c != NULL) \
+		{ \
+			memset(buf, 0, sizeof(buf)); \
+			offset = c - tag; \
+			if (offset >= sizeof(buf)) \
+				offset = sizeof(buf) - 1; \
+			strncpy(buf, tag, offset); \
+			obj->field = atoi(buf); \
+		} \
+	}
+
+static GList *local_objs;
+
+MsnObject *
+msn_object_new(void)
+{
+	MsnObject *obj;
+
+	obj = g_new0(MsnObject, 1);
+
+	msn_object_set_type(obj, MSN_OBJECT_UNKNOWN);
+	msn_object_set_friendly(obj, "AAA=");
+
+	return obj;
+}
+
+MsnObject *
+msn_object_new_from_string(const char *str)
+{
+	MsnObject *obj;
+	char *tag, *c;
+
+	g_return_val_if_fail(str != NULL, NULL);
+
+	if (strncmp(str, "<msnobj ", 8))
+		return NULL;
+
+	obj = msn_object_new();
+
+	GET_STRING_TAG(creator,  "Creator");
+	GET_INT_TAG(size,        "Size");
+	GET_INT_TAG(type,        "Type");
+	GET_STRING_TAG(location, "Location");
+	GET_STRING_TAG(friendly, "Friendly");
+	GET_STRING_TAG(sha1d,    "SHA1D");
+	GET_STRING_TAG(sha1c,    "SHA1C");
+
+	/* If we are missing any of the required elements then discard the object */
+	/* SHA1C is not always sent anymore */
+	if (obj->creator == NULL || obj->size == 0 || obj->type == 0
+			|| obj->location == NULL || obj->friendly == NULL
+			|| obj->sha1d == NULL /*|| obj->sha1c == NULL*/) {
+		purple_debug_error("msn", "Discarding invalid msnobj: '%s'\n", str);
+		msn_object_destroy(obj);
+		obj = NULL;
+	}
+
+	return obj;
+}
+
+void
+msn_object_destroy(MsnObject *obj)
+{
+	g_return_if_fail(obj != NULL);
+
+	g_free(obj->creator);
+	g_free(obj->location);
+	g_free(obj->friendly);
+	g_free(obj->sha1d);
+	g_free(obj->sha1c);
+
+	purple_imgstore_unref(obj->img);
+
+	if (obj->local)
+		local_objs = g_list_remove(local_objs, obj);
+
+	g_free(obj);
+}
+
+char *
+msn_object_to_string(const MsnObject *obj)
+{
+	char *str;
+	const char *sha1c;
+
+	g_return_val_if_fail(obj != NULL, NULL);
+
+	sha1c = msn_object_get_sha1c(obj);
+
+	str = g_strdup_printf("<msnobj Creator=\"%s\" Size=\"%d\" Type=\"%d\" "
+						  "Location=\"%s\" Friendly=\"%s\" SHA1D=\"%s\""
+						  "%s%s%s/>",
+						  msn_object_get_creator(obj),
+						  msn_object_get_size(obj),
+						  msn_object_get_type(obj),
+						  msn_object_get_location(obj),
+						  msn_object_get_friendly(obj),
+						  msn_object_get_sha1d(obj),
+						  sha1c ? " SHA1C=\"" : "",
+						  sha1c ? sha1c : "",
+						  sha1c ? "\"" : "");
+
+	return str;
+}
+
+void
+msn_object_set_creator(MsnObject *obj, const char *creator)
+{
+	g_return_if_fail(obj != NULL);
+
+	if (obj->creator != NULL)
+		g_free(obj->creator);
+
+	obj->creator = (creator == NULL ? NULL : g_strdup(creator));
+}
+
+void
+msn_object_set_size(MsnObject *obj, int size)
+{
+	g_return_if_fail(obj != NULL);
+
+	obj->size = size;
+}
+
+void
+msn_object_set_type(MsnObject *obj, MsnObjectType type)
+{
+	g_return_if_fail(obj != NULL);
+
+	obj->type = type;
+}
+
+void
+msn_object_set_location(MsnObject *obj, const char *location)
+{
+	g_return_if_fail(obj != NULL);
+
+	if (obj->location != NULL)
+		g_free(obj->location);
+
+	obj->location = (location == NULL ? NULL : g_strdup(location));
+}
+
+void
+msn_object_set_friendly(MsnObject *obj, const char *friendly)
+{
+	g_return_if_fail(obj != NULL);
+
+	if (obj->friendly != NULL)
+		g_free(obj->friendly);
+
+	obj->friendly = (friendly == NULL ? NULL : g_strdup(friendly));
+}
+
+void
+msn_object_set_sha1d(MsnObject *obj, const char *sha1d)
+{
+	g_return_if_fail(obj != NULL);
+
+	if (obj->sha1d != NULL)
+		g_free(obj->sha1d);
+
+	obj->sha1d = (sha1d == NULL ? NULL : g_strdup(sha1d));
+}
+
+void
+msn_object_set_sha1c(MsnObject *obj, const char *sha1c)
+{
+	g_return_if_fail(obj != NULL);
+
+	if (obj->sha1c != NULL)
+		g_free(obj->sha1c);
+
+	obj->sha1c = (sha1c == NULL ? NULL : g_strdup(sha1c));
+}
+
+const char *
+msn_object_get_creator(const MsnObject *obj)
+{
+	g_return_val_if_fail(obj != NULL, NULL);
+
+	return obj->creator;
+}
+
+int
+msn_object_get_size(const MsnObject *obj)
+{
+	g_return_val_if_fail(obj != NULL, 0);
+
+	return obj->size;
+}
+
+MsnObjectType
+msn_object_get_type(const MsnObject *obj)
+{
+	g_return_val_if_fail(obj != NULL, MSN_OBJECT_UNKNOWN);
+
+	return obj->type;
+}
+
+const char *
+msn_object_get_location(const MsnObject *obj)
+{
+	g_return_val_if_fail(obj != NULL, NULL);
+
+	return obj->location;
+}
+
+const char *
+msn_object_get_friendly(const MsnObject *obj)
+{
+	g_return_val_if_fail(obj != NULL, NULL);
+
+	return obj->friendly;
+}
+
+const char *
+msn_object_get_sha1d(const MsnObject *obj)
+{
+	g_return_val_if_fail(obj != NULL, NULL);
+
+	return obj->sha1d;
+}
+
+const char *
+msn_object_get_sha1c(const MsnObject *obj)
+{
+	g_return_val_if_fail(obj != NULL, NULL);
+
+	return obj->sha1c;
+}
+
+const char *
+msn_object_get_sha1(const MsnObject *obj)
+{
+	g_return_val_if_fail(obj != NULL, NULL);
+
+	if(obj->sha1c != NULL) {
+		return obj->sha1c;
+	} else {
+		return obj->sha1d;
+	}
+}
+
+static MsnObject *
+msn_object_find_local(const char *sha1)
+{
+	GList *l;
+
+	g_return_val_if_fail(sha1 != NULL, NULL);
+
+	for (l = local_objs; l != NULL; l = l->next)
+	{
+		MsnObject *local_obj = l->data;
+
+		if (!strcmp(msn_object_get_sha1(local_obj), sha1))
+			return local_obj;
+	}
+
+	return NULL;
+
+}
+
+void
+msn_object_set_local(MsnObject *obj)
+{
+	g_return_if_fail(obj != NULL);
+
+	obj->local = TRUE;
+
+	local_objs = g_list_append(local_objs, obj);
+}
+
+void
+msn_object_set_image(MsnObject *obj, PurpleStoredImage *img)
+{
+	g_return_if_fail(obj != NULL);
+	g_return_if_fail(img != NULL);
+
+	/* obj->local = TRUE; */
+
+	purple_imgstore_unref(obj->img);
+	obj->img = purple_imgstore_ref(img);
+}
+
+PurpleStoredImage *
+msn_object_get_image(const MsnObject *obj)
+{
+	MsnObject *local_obj;
+
+	g_return_val_if_fail(obj != NULL, NULL);
+
+	local_obj = msn_object_find_local(msn_object_get_sha1(obj));
+
+	if (local_obj != NULL)
+		return local_obj->img;
+
+	return NULL;
+}
============================================================
--- libpurple/protocols/msnp9/object.h	ecf11b7801feaf3d57036a77ebf3f219b1718339
+++ libpurple/protocols/msnp9/object.h	ecf11b7801feaf3d57036a77ebf3f219b1718339
@@ -0,0 +1,229 @@
+/**
+ * @file object.h MSNObject API
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_OBJECT_H_
+#define _MSN_OBJECT_H_
+
+#include "imgstore.h"
+
+#include "internal.h"
+
+typedef enum
+{
+	MSN_OBJECT_UNKNOWN    = -1, /**< Unknown object        */
+	MSN_OBJECT_RESERVED1  =  1, /**< Reserved              */
+	MSN_OBJECT_EMOTICON   =  2, /**< Custom Emoticon       */
+	MSN_OBJECT_USERTILE   =  3, /**< UserTile (buddy icon) */
+	MSN_OBJECT_RESERVED2  =  4, /**< Reserved              */
+	MSN_OBJECT_BACKGROUND =  5  /**< Background            */
+
+} MsnObjectType;
+
+typedef struct
+{
+	gboolean local;
+
+	char *creator;
+	int size;
+	MsnObjectType type;
+	PurpleStoredImage *img;
+	char *location;
+	char *friendly;
+	char *sha1d;
+	char *sha1c;
+
+} MsnObject;
+
+/**
+ * Creates a MsnObject structure.
+ *
+ * @return A new MsnObject structure.
+ */
+MsnObject *msn_object_new(void);
+
+/**
+ * Creates a MsnObject structure from a string.
+ *
+ * @param str The string.
+ *
+ * @return The new MsnObject structure.
+ */
+MsnObject *msn_object_new_from_string(const char *str);
+
+/**
+ * Destroys an MsnObject structure.
+ *
+ * @param obj The object structure.
+ */
+void msn_object_destroy(MsnObject *obj);
+
+/**
+ * Outputs a string representation of an MsnObject.
+ *
+ * @param obj The object.
+ *
+ * @return The string representation. This must be freed.
+ */
+char *msn_object_to_string(const MsnObject *obj);
+
+/**
+ * Sets the creator field in a MsnObject.
+ *
+ * @param creator The creator value.
+ */
+void msn_object_set_creator(MsnObject *obj, const char *creator);
+
+/**
+ * Sets the size field in a MsnObject.
+ *
+ * @param size The size value.
+ */
+void msn_object_set_size(MsnObject *obj, int size);
+
+/**
+ * Sets the type field in a MsnObject.
+ *
+ * @param type The type value.
+ */
+void msn_object_set_type(MsnObject *obj, MsnObjectType type);
+
+/**
+ * Sets the location field in a MsnObject.
+ *
+ * @param location The location value.
+ */
+void msn_object_set_location(MsnObject *obj, const char *location);
+
+/**
+ * Sets the friendly name field in a MsnObject.
+ *
+ * @param friendly The friendly name value.
+ */
+void msn_object_set_friendly(MsnObject *obj, const char *friendly);
+
+/**
+ * Sets the SHA1D field in a MsnObject.
+ *
+ * @param sha1d The sha1d value.
+ */
+void msn_object_set_sha1d(MsnObject *obj, const char *sha1d);
+
+/**
+ * Sets the SHA1C field in a MsnObject.
+ *
+ * @param sha1c The sha1c value.
+ */
+void msn_object_set_sha1c(MsnObject *obj, const char *sha1c);
+
+/**
+ * Associates an image with a MsnObject.
+ *
+ * @param obj The object.
+ * @param img The image to associate.
+ */
+void msn_object_set_image(MsnObject *obj, PurpleStoredImage *img);
+
+/**
+ * Returns a MsnObject's creator value.
+ *
+ * @param obj The object.
+ *
+ * @return The creator value.
+ */
+const char *msn_object_get_creator(const MsnObject *obj);
+
+/**
+ * Returns a MsnObject's size value.
+ *
+ * @param obj The object.
+ *
+ * @return The size value.
+ */
+int msn_object_get_size(const MsnObject *obj);
+
+/**
+ * Returns a MsnObject's type.
+ *
+ * @param obj The object.
+ *
+ * @return The object type.
+ */
+MsnObjectType msn_object_get_type(const MsnObject *obj);
+
+/**
+ * Returns a MsnObject's location value.
+ *
+ * @param obj The object.
+ *
+ * @return The location value.
+ */
+const char *msn_object_get_location(const MsnObject *obj);
+
+/**
+ * Returns a MsnObject's friendly name value.
+ *
+ * @param obj The object.
+ *
+ * @return The friendly name value.
+ */
+const char *msn_object_get_friendly(const MsnObject *obj);
+
+/**
+ * Returns a MsnObject's SHA1D value.
+ *
+ * @param obj The object.
+ *
+ * @return The SHA1D value.
+ */
+const char *msn_object_get_sha1d(const MsnObject *obj);
+
+/**
+ * Returns a MsnObject's SHA1C value.
+ *
+ * @param obj The object.
+ *
+ * @return The SHA1C value.
+ */
+const char *msn_object_get_sha1c(const MsnObject *obj);
+
+/**
+ * Returns a MsnObject's SHA1C value if it exists, otherwise SHA1D.
+ *
+ * @param obj The object.
+ *
+ * @return The SHA1C value.
+ */
+const char *msn_object_get_sha1(const MsnObject *obj);
+
+/**
+ * Returns the image associated with the MsnObject.
+ *
+ * @param obj The object.
+ *
+ * @return The associated image.
+ */
+PurpleStoredImage *msn_object_get_image(const MsnObject *obj);
+
+void msn_object_set_local(MsnObject *obj);
+
+#endif /* _MSN_OBJECT_H_ */
============================================================
--- libpurple/protocols/msnp9/page.c	74dfbb4800b451c0627a953fe23526e39f47fc30
+++ libpurple/protocols/msnp9/page.c	74dfbb4800b451c0627a953fe23526e39f47fc30
@@ -0,0 +1,89 @@
+/**
+ * @file page.c Paging functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "page.h"
+
+MsnPage *
+msn_page_new(void)
+{
+	MsnPage *page;
+
+	page = g_new0(MsnPage, 1);
+
+	return page;
+}
+
+void
+msn_page_destroy(MsnPage *page)
+{
+	g_return_if_fail(page != NULL);
+
+	if (page->body != NULL)
+		g_free(page->body);
+
+	if (page->from_location != NULL)
+		g_free(page->from_location);
+
+	if (page->from_phone != NULL)
+		g_free(page->from_phone);
+
+	g_free(page);
+}
+
+char *
+msn_page_gen_payload(const MsnPage *page, size_t *ret_size)
+{
+	char *str;
+
+	g_return_val_if_fail(page != NULL, NULL);
+
+	str =
+		g_strdup_printf("<TEXT xml:space=\"preserve\" enc=\"utf-8\">%s</TEXT>",
+						msn_page_get_body(page));
+
+	if (ret_size != NULL)
+		*ret_size = strlen(str);
+
+	return str;
+}
+
+void
+msn_page_set_body(MsnPage *page, const char *body)
+{
+	g_return_if_fail(page != NULL);
+	g_return_if_fail(body != NULL);
+
+	if (page->body != NULL)
+		g_free(page->body);
+
+	page->body = g_strdup(body);
+}
+
+const char *
+msn_page_get_body(const MsnPage *page)
+{
+	g_return_val_if_fail(page != NULL, NULL);
+
+	return page->body;
+}
============================================================
--- libpurple/protocols/msnp9/page.h	08a76b6e899cd5ad2c19d35b6d5aca9275aab6ca
+++ libpurple/protocols/msnp9/page.h	08a76b6e899cd5ad2c19d35b6d5aca9275aab6ca
@@ -0,0 +1,81 @@
+/**
+ * @file page.h Paging functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_PAGE_H_
+#define _MSN_PAGE_H_
+
+typedef struct _MsnPage MsnPage;
+
+#include "session.h"
+
+/**
+ * A page.
+ */
+struct _MsnPage
+{
+	char *from_location;
+	char *from_phone;
+
+	char *body;
+};
+
+/**
+ * Creates a new, empty page.
+ *
+ * @return A new page.
+ */
+MsnPage *msn_page_new(void);
+
+/**
+ * Destroys a page.
+ */
+void msn_page_destroy(MsnPage *page);
+
+/**
+ * Generates the payload data of a page.
+ *
+ * @param page     The page.
+ * @param ret_size The returned size of the payload.
+ *
+ * @return The payload data of a page.
+ */
+char *msn_page_gen_payload(const MsnPage *page, size_t *ret_size);
+
+/**
+ * Sets the body of a page.
+ *
+ * @param page  The page.
+ * @param body The body of the page.
+ */
+void msn_page_set_body(MsnPage *page, const char *body);
+
+/**
+ * Returns the body of the page.
+ *
+ * @param page The page.
+ *
+ * @return The body of the page.
+ */
+const char *msn_page_get_body(const MsnPage *page);
+
+#endif /* _MSN_PAGE_H_ */
============================================================
--- libpurple/protocols/msnp9/servconn.c	334451299225389dac3793441cdee0a3c464a93f
+++ libpurple/protocols/msnp9/servconn.c	334451299225389dac3793441cdee0a3c464a93f
@@ -0,0 +1,550 @@
+/**
+ * @file servconn.c Server connection functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "servconn.h"
+#include "error.h"
+
+static void read_cb(gpointer data, gint source, PurpleInputCondition cond);
+
+/**************************************************************************
+ * Main
+ **************************************************************************/
+
+MsnServConn *
+msn_servconn_new(MsnSession *session, MsnServConnType type)
+{
+	MsnServConn *servconn;
+
+	g_return_val_if_fail(session != NULL, NULL);
+
+	servconn = g_new0(MsnServConn, 1);
+
+	servconn->type = type;
+
+	servconn->session = session;
+	servconn->cmdproc = msn_cmdproc_new(session);
+	servconn->cmdproc->servconn = servconn;
+
+	servconn->httpconn = msn_httpconn_new(servconn);
+
+	servconn->num = session->servconns_count++;
+
+	servconn->tx_buf = purple_circ_buffer_new(MSN_BUF_LEN);
+	servconn->tx_handler = 0;
+
+	return servconn;
+}
+
+void
+msn_servconn_destroy(MsnServConn *servconn)
+{
+	g_return_if_fail(servconn != NULL);
+
+	if (servconn->processing)
+	{
+		servconn->wasted = TRUE;
+		return;
+	}
+
+	if (servconn->connected)
+		msn_servconn_disconnect(servconn);
+
+	if (servconn->destroy_cb)
+		servconn->destroy_cb(servconn);
+
+	if (servconn->httpconn != NULL)
+		msn_httpconn_destroy(servconn->httpconn);
+
+	g_free(servconn->host);
+
+	purple_circ_buffer_destroy(servconn->tx_buf);
+	if (servconn->tx_handler > 0)
+		purple_input_remove(servconn->tx_handler);
+
+	msn_cmdproc_destroy(servconn->cmdproc);
+	g_free(servconn);
+}
+
+void
+msn_servconn_set_connect_cb(MsnServConn *servconn,
+							void (*connect_cb)(MsnServConn *))
+{
+	g_return_if_fail(servconn != NULL);
+	servconn->connect_cb = connect_cb;
+}
+
+void
+msn_servconn_set_disconnect_cb(MsnServConn *servconn,
+							   void (*disconnect_cb)(MsnServConn *))
+{
+	g_return_if_fail(servconn != NULL);
+
+	servconn->disconnect_cb = disconnect_cb;
+}
+
+void
+msn_servconn_set_destroy_cb(MsnServConn *servconn,
+							void (*destroy_cb)(MsnServConn *))
+{
+	g_return_if_fail(servconn != NULL);
+
+	servconn->destroy_cb = destroy_cb;
+}
+
+/**************************************************************************
+ * Utility
+ **************************************************************************/
+
+void
+msn_servconn_got_error(MsnServConn *servconn, MsnServConnError error)
+{
+	char *tmp;
+	const char *reason;
+
+	const char *names[] = { "Notification", "Switchboard" };
+	const char *name;
+
+	name = names[servconn->type];
+
+	switch (error)
+	{
+		case MSN_SERVCONN_ERROR_CONNECT:
+			reason = _("Unable to connect"); break;
+		case MSN_SERVCONN_ERROR_WRITE:
+			reason = _("Writing error"); break;
+		case MSN_SERVCONN_ERROR_READ:
+			reason = _("Reading error"); break;
+		default:
+			reason = _("Unknown error"); break;
+	}
+
+	purple_debug_error("msn", "Connection error from %s server (%s): %s\n",
+					 name, servconn->host, reason);
+	tmp = g_strdup_printf(_("Connection error from %s server:\n%s"),
+						  name, reason);
+
+	if (servconn->type == MSN_SERVCONN_NS)
+	{
+		msn_session_set_error(servconn->session, MSN_ERROR_SERVCONN, tmp);
+	}
+	else if (servconn->type == MSN_SERVCONN_SB)
+	{
+		MsnSwitchBoard *swboard;
+		swboard = servconn->cmdproc->data;
+		if (swboard != NULL)
+			swboard->error = MSN_SB_ERROR_CONNECTION;
+	}
+
+	msn_servconn_disconnect(servconn);
+
+	g_free(tmp);
+}
+
+/**************************************************************************
+ * Connect
+ **************************************************************************/
+
+static void
+connect_cb(gpointer data, gint source, const gchar *error_message)
+{
+	MsnServConn *servconn;
+
+	servconn = data;
+	servconn->connect_data = NULL;
+	servconn->processing = FALSE;
+
+	if (servconn->wasted)
+	{
+		if (source >= 0)
+			close(source);
+		msn_servconn_destroy(servconn);
+		return;
+	}
+
+	servconn->fd = source;
+
+	if (source >= 0)
+	{
+		servconn->connected = TRUE;
+
+		/* Someone wants to know we connected. */
+		servconn->connect_cb(servconn);
+		servconn->inpa = purple_input_add(servconn->fd, PURPLE_INPUT_READ,
+			read_cb, data);
+	}
+	else
+	{
+		purple_debug_error("msn", "Connection error: %s\n", error_message);
+		msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_CONNECT);
+	}
+}
+
+gboolean
+msn_servconn_connect(MsnServConn *servconn, const char *host, int port)
+{
+	MsnSession *session;
+
+	g_return_val_if_fail(servconn != NULL, FALSE);
+	g_return_val_if_fail(host     != NULL, FALSE);
+	g_return_val_if_fail(port      > 0,    FALSE);
+
+	session = servconn->session;
+
+	if (servconn->connected)
+		msn_servconn_disconnect(servconn);
+
+	g_free(servconn->host);
+	servconn->host = g_strdup(host);
+
+	if (session->http_method)
+	{
+		/* HTTP Connection. */
+
+		if (!servconn->httpconn->connected)
+			if (!msn_httpconn_connect(servconn->httpconn, host, port))
+				return FALSE;
+
+		servconn->connected = TRUE;
+		servconn->httpconn->virgin = TRUE;
+
+		/* Someone wants to know we connected. */
+		servconn->connect_cb(servconn);
+
+		return TRUE;
+	}
+
+	servconn->connect_data = purple_proxy_connect(NULL, session->account,
+			host, port, connect_cb, servconn);
+
+	if (servconn->connect_data != NULL)
+	{
+		servconn->processing = TRUE;
+		return TRUE;
+	}
+	else
+		return FALSE;
+}
+
+void
+msn_servconn_disconnect(MsnServConn *servconn)
+{
+	g_return_if_fail(servconn != NULL);
+
+	if (!servconn->connected)
+	{
+		/* We could not connect. */
+		if (servconn->disconnect_cb != NULL)
+			servconn->disconnect_cb(servconn);
+
+		return;
+	}
+
+	if (servconn->session->http_method)
+	{
+		/* Fake disconnection. */
+		if (servconn->disconnect_cb != NULL)
+			servconn->disconnect_cb(servconn);
+
+		return;
+	}
+
+	if (servconn->connect_data != NULL)
+	{
+		purple_proxy_connect_cancel(servconn->connect_data);
+		servconn->connect_data = NULL;
+	}
+
+	if (servconn->inpa > 0)
+	{
+		purple_input_remove(servconn->inpa);
+		servconn->inpa = 0;
+	}
+
+	close(servconn->fd);
+
+	servconn->rx_buf = NULL;
+	servconn->rx_len = 0;
+	servconn->payload_len = 0;
+
+	servconn->connected = FALSE;
+
+	if (servconn->disconnect_cb != NULL)
+		servconn->disconnect_cb(servconn);
+}
+
+static void
+servconn_write_cb(gpointer data, gint source, PurpleInputCondition cond)
+{
+	MsnServConn *servconn = data;
+	int ret, writelen;
+
+	writelen = purple_circ_buffer_get_max_read(servconn->tx_buf);
+
+	if (writelen == 0) {
+		purple_input_remove(servconn->tx_handler);
+		servconn->tx_handler = 0;
+		return;
+	}
+
+	ret = write(servconn->fd, servconn->tx_buf->outptr, writelen);
+
+	if (ret < 0 && errno == EAGAIN)
+		return;
+	else if (ret <= 0) {
+		msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_WRITE);
+		return;
+	}
+
+	purple_circ_buffer_mark_read(servconn->tx_buf, ret);
+}
+
+ssize_t
+msn_servconn_write(MsnServConn *servconn, const char *buf, size_t len)
+{
+	ssize_t ret = 0;
+
+	g_return_val_if_fail(servconn != NULL, 0);
+
+	if (!servconn->session->http_method)
+	{
+		if (servconn->tx_handler == 0) {
+			switch (servconn->type)
+			{
+				case MSN_SERVCONN_NS:
+				case MSN_SERVCONN_SB:
+					ret = write(servconn->fd, buf, len);
+					break;
+#if 0
+				case MSN_SERVCONN_DC:
+					ret = write(servconn->fd, &buf, sizeof(len));
+					ret = write(servconn->fd, buf, len);
+					break;
+#endif
+				default:
+					ret = write(servconn->fd, buf, len);
+					break;
+			}
+		} else {
+			ret = -1;
+			errno = EAGAIN;
+		}
+
+		if (ret < 0 && errno == EAGAIN)
+			ret = 0;
+		if (ret >= 0 && ret < len) {
+			if (servconn->tx_handler == 0)
+				servconn->tx_handler = purple_input_add(
+					servconn->fd, PURPLE_INPUT_WRITE,
+					servconn_write_cb, servconn);
+			purple_circ_buffer_append(servconn->tx_buf, buf + ret,
+				len - ret);
+		}
+	}
+	else
+	{
+		ret = msn_httpconn_write(servconn->httpconn, buf, len);
+	}
+
+	if (ret == -1)
+	{
+		msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_WRITE);
+	}
+
+	return ret;
+}
+
+static void
+read_cb(gpointer data, gint source, PurpleInputCondition cond)
+{
+	MsnServConn *servconn;
+	MsnSession *session;
+	char buf[MSN_BUF_LEN];
+	char *cur, *end, *old_rx_buf;
+	int len, cur_len;
+
+	servconn = data;
+	session = servconn->session;
+
+	len = read(servconn->fd, buf, sizeof(buf) - 1);
+
+	if (len < 0 && errno == EAGAIN)
+		return;
+	else if (len <= 0)
+	{
+		purple_debug_error("msn", "servconn read error, len: %d error: %s\n", len, strerror(errno));
+		msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_READ);
+
+		return;
+	}
+
+	buf[len] = '\0';
+
+	servconn->rx_buf = g_realloc(servconn->rx_buf, len + servconn->rx_len + 1);
+	memcpy(servconn->rx_buf + servconn->rx_len, buf, len + 1);
+	servconn->rx_len += len;
+
+	end = old_rx_buf = servconn->rx_buf;
+
+	servconn->processing = TRUE;
+
+	do
+	{
+		cur = end;
+
+		if (servconn->payload_len)
+		{
+			if (servconn->payload_len > servconn->rx_len)
+				/* The payload is still not complete. */
+				break;
+
+			cur_len = servconn->payload_len;
+			end += cur_len;
+		}
+		else
+		{
+			end = strstr(cur, "\r\n");
+
+			if (end == NULL)
+				/* The command is still not complete. */
+				break;
+
+			*end = '\0';
+			end += 2;
+			cur_len = end - cur;
+		}
+
+		servconn->rx_len -= cur_len;
+
+		if (servconn->payload_len)
+		{
+			msn_cmdproc_process_payload(servconn->cmdproc, cur, cur_len);
+			servconn->payload_len = 0;
+		}
+		else
+		{
+			msn_cmdproc_process_cmd_text(servconn->cmdproc, cur);
+		}
+	} while (servconn->connected && !servconn->wasted && servconn->rx_len > 0);
+
+	if (servconn->connected && !servconn->wasted)
+	{
+		if (servconn->rx_len > 0)
+			servconn->rx_buf = g_memdup(cur, servconn->rx_len);
+		else
+			servconn->rx_buf = NULL;
+	}
+
+	servconn->processing = FALSE;
+
+	if (servconn->wasted)
+		msn_servconn_destroy(servconn);
+
+	g_free(old_rx_buf);
+}
+
+#if 0
+static int
+create_listener(int port)
+{
+	int fd;
+	const int on = 1;
+
+#if 0
+	struct addrinfo hints;
+	struct addrinfo *c, *res;
+	char port_str[5];
+
+	snprintf(port_str, sizeof(port_str), "%d", port);
+
+	memset(&hints, 0, sizeof(hints));
+
+	hints.ai_flags = AI_PASSIVE;
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+
+	if (getaddrinfo(NULL, port_str, &hints, &res) != 0)
+	{
+		purple_debug_error("msn", "Could not get address info: %s.\n",
+						 port_str);
+		return -1;
+	}
+
+	for (c = res; c != NULL; c = c->ai_next)
+	{
+		fd = socket(c->ai_family, c->ai_socktype, c->ai_protocol);
+
+		if (fd < 0)
+			continue;
+
+		setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+		if (bind(fd, c->ai_addr, c->ai_addrlen) == 0)
+			break;
+
+		close(fd);
+	}
+
+	if (c == NULL)
+	{
+		purple_debug_error("msn", "Could not find socket: %s.\n", port_str);
+		return -1;
+	}
+
+	freeaddrinfo(res);
+#else
+	struct sockaddr_in sockin;
+
+	fd = socket(AF_INET, SOCK_STREAM, 0);
+
+	if (fd < 0)
+		return -1;
+
+	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) != 0)
+	{
+		close(fd);
+		return -1;
+	}
+
+	memset(&sockin, 0, sizeof(struct sockaddr_in));
+	sockin.sin_family = AF_INET;
+	sockin.sin_port = htons(port);
+
+	if (bind(fd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0)
+	{
+		close(fd);
+		return -1;
+	}
+#endif
+
+	if (listen (fd, 4) != 0)
+	{
+		close (fd);
+		return -1;
+	}
+
+	fcntl(fd, F_SETFL, O_NONBLOCK);
+
+	return fd;
+}
+#endif
============================================================
--- libpurple/protocols/msnp9/servconn.h	6aa2668e7125c02c17891e406bc5dc8685b4c7a8
+++ libpurple/protocols/msnp9/servconn.h	6aa2668e7125c02c17891e406bc5dc8685b4c7a8
@@ -0,0 +1,171 @@
+/**
+ * @file servconn.h Server connection functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_SERVCONN_H_
+#define _MSN_SERVCONN_H_
+
+typedef struct _MsnServConn MsnServConn;
+
+#include "session.h"
+#include "cmdproc.h"
+
+#include "proxy.h"
+#include "httpconn.h"
+
+/**
+ * Connection error types.
+ */
+typedef enum
+{
+	MSN_SERVCONN_ERROR_NONE,
+	MSN_SERVCONN_ERROR_CONNECT,
+	MSN_SERVCONN_ERROR_WRITE,
+	MSN_SERVCONN_ERROR_READ,
+
+} MsnServConnError;
+
+/**
+ * Connection types.
+ */
+typedef enum
+{
+	MSN_SERVCONN_NS,
+	MSN_SERVCONN_SB
+
+} MsnServConnType;
+
+/**
+ * A Connection.
+ */
+struct _MsnServConn
+{
+	MsnServConnType type; /**< The type of this connection. */
+	MsnSession *session;  /**< The MSN session of this connection. */
+	MsnCmdProc *cmdproc;  /**< The command processor of this connection. */
+
+	PurpleProxyConnectData *connect_data;
+
+	gboolean connected;   /**< A flag that states if it's connected. */
+	gboolean processing;  /**< A flag that states if something is working
+							with this connection. */
+	gboolean wasted;      /**< A flag that states if it should be destroyed. */
+
+	char *host; /**< The host this connection is connected or should be
+				  connected to. */
+	int num; /**< A number id of this connection. */
+
+	MsnHttpConn *httpconn; /**< The HTTP connection this connection should use. */
+
+	int fd; /**< The connection's file descriptor. */
+	int inpa; /**< The connection's input handler. */
+
+	char *rx_buf; /**< The receive buffer. */
+	int rx_len; /**< The receive buffer lenght. */
+
+	size_t payload_len; /**< The length of the payload.
+						  It's only set when we've received a command that
+						  has a payload. */
+
+	PurpleCircBuffer *tx_buf;
+	guint tx_handler;
+
+	void (*connect_cb)(MsnServConn *); /**< The callback to call when connecting. */
+	void (*disconnect_cb)(MsnServConn *); /**< The callback to call when disconnecting. */
+	void (*destroy_cb)(MsnServConn *); /**< The callback to call when destroying. */
+};
+
+/**
+ * Creates a new connection object.
+ *
+ * @param session The session.
+ * @param type The type of the connection.
+ */
+MsnServConn *msn_servconn_new(MsnSession *session, MsnServConnType type);
+
+/**
+ * Destroys a connection object.
+ *
+ * @param servconn The connection.
+ */
+void msn_servconn_destroy(MsnServConn *servconn);
+
+/**
+ * Connects to a host.
+ *
+ * @param servconn The connection.
+ * @param host The host.
+ * @param port The port.
+ */
+gboolean msn_servconn_connect(MsnServConn *servconn, const char *host, int port);
+
+/**
+ * Disconnects.
+ *
+ * @param servconn The connection.
+ */
+void msn_servconn_disconnect(MsnServConn *servconn);
+
+/**
+ * Sets the connect callback.
+ *
+ * @param servconn The servconn.
+ * @param connect_cb The connect callback.
+ */
+void msn_servconn_set_connect_cb(MsnServConn *servconn,
+								 void (*connect_cb)(MsnServConn *));
+/**
+ * Sets the disconnect callback.
+ *
+ * @param servconn The servconn.
+ * @param disconnect_cb The disconnect callback.
+ */
+void msn_servconn_set_disconnect_cb(MsnServConn *servconn,
+									void (*disconnect_cb)(MsnServConn *));
+/**
+ * Sets the destroy callback.
+ *
+ * @param servconn The servconn that's being destroyed.
+ * @param destroy_cb The destroy callback.
+ */
+void msn_servconn_set_destroy_cb(MsnServConn *servconn,
+								 void (*destroy_cb)(MsnServConn *));
+
+/**
+ * Writes a chunck of data to the servconn.
+ *
+ * @param servconn The servconn.
+ * @param buf The data to write.
+ * @param size The size of the data.
+ */
+ssize_t msn_servconn_write(MsnServConn *servconn, const char *buf,
+						  size_t size);
+
+/**
+ * Function to call whenever an error related to a switchboard occurs.
+ *
+ * @param servconn The servconn.
+ * @param error The error that happened.
+ */
+void msn_servconn_got_error(MsnServConn *servconn, MsnServConnError error);
+
+#endif /* _MSN_SERVCONN_H_ */
============================================================
--- libpurple/protocols/msnp9/session.c	c00810400b189db9e2a3ffa25f1f430d41561bd1
+++ libpurple/protocols/msnp9/session.c	c00810400b189db9e2a3ffa25f1f430d41561bd1
@@ -0,0 +1,421 @@
+/**
+ * @file session.c MSN session functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "session.h"
+#include "notification.h"
+
+#include "dialog.h"
+
+MsnSession *
+msn_session_new(PurpleAccount *account)
+{
+	MsnSession *session;
+
+	g_return_val_if_fail(account != NULL, NULL);
+
+	session = g_new0(MsnSession, 1);
+
+	session->account = account;
+	session->notification = msn_notification_new(session);
+	session->userlist = msn_userlist_new(session);
+
+	session->user = msn_user_new(session->userlist,
+								 purple_account_get_username(account), NULL);
+
+	session->protocol_ver = 9;
+	session->conv_seq = 1;
+
+	return session;
+}
+
+void
+msn_session_destroy(MsnSession *session)
+{
+	g_return_if_fail(session != NULL);
+
+	session->destroying = TRUE;
+
+	if (session->connected)
+		msn_session_disconnect(session);
+
+	if (session->notification != NULL)
+		msn_notification_destroy(session->notification);
+
+	while (session->switches != NULL)
+		msn_switchboard_destroy(session->switches->data);
+
+	while (session->slplinks != NULL)
+		msn_slplink_destroy(session->slplinks->data);
+
+	msn_userlist_destroy(session->userlist);
+
+	g_free(session->passport_info.kv);
+	g_free(session->passport_info.sid);
+	g_free(session->passport_info.mspauth);
+	g_free(session->passport_info.client_ip);
+
+	if (session->passport_info.file != NULL)
+	{
+		g_unlink(session->passport_info.file);
+		g_free(session->passport_info.file);
+	}
+
+	if (session->sync != NULL)
+		msn_sync_destroy(session->sync);
+
+	if (session->nexus != NULL)
+		msn_nexus_destroy(session->nexus);
+
+	if (session->user != NULL)
+		msn_user_destroy(session->user);
+
+	g_free(session);
+}
+
+gboolean
+msn_session_connect(MsnSession *session, const char *host, int port,
+					gboolean http_method)
+{
+	g_return_val_if_fail(session != NULL, FALSE);
+	g_return_val_if_fail(!session->connected, TRUE);
+
+	session->connected = TRUE;
+	session->http_method = http_method;
+
+	if (session->notification == NULL)
+	{
+		purple_debug_error("msn", "This shouldn't happen\n");
+		g_return_val_if_reached(FALSE);
+	}
+
+	if (msn_notification_connect(session->notification, host, port))
+	{
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+void
+msn_session_disconnect(MsnSession *session)
+{
+	g_return_if_fail(session != NULL);
+	g_return_if_fail(session->connected);
+
+	session->connected = FALSE;
+
+	while (session->switches != NULL)
+		msn_switchboard_close(session->switches->data);
+
+	if (session->notification != NULL)
+		msn_notification_close(session->notification);
+}
+
+/* TODO: This must go away when conversation is redesigned */
+MsnSwitchBoard *
+msn_session_find_swboard(MsnSession *session, const char *username)
+{
+	GList *l;
+
+	g_return_val_if_fail(session  != NULL, NULL);
+	g_return_val_if_fail(username != NULL, NULL);
+
+	for (l = session->switches; l != NULL; l = l->next)
+	{
+		MsnSwitchBoard *swboard;
+
+		swboard = l->data;
+
+		if ((swboard->im_user != NULL) && !strcmp(username, swboard->im_user))
+			return swboard;
+	}
+
+	return NULL;
+}
+
+MsnSwitchBoard *
+msn_session_find_swboard_with_conv(MsnSession *session, PurpleConversation *conv)
+{
+	GList *l;
+
+	g_return_val_if_fail(session  != NULL, NULL);
+	g_return_val_if_fail(conv != NULL, NULL);
+
+	for (l = session->switches; l != NULL; l = l->next)
+	{
+		MsnSwitchBoard *swboard;
+
+		swboard = l->data;
+
+		if (swboard->conv == conv)
+			return swboard;
+	}
+
+	return NULL;
+}
+
+MsnSwitchBoard *
+msn_session_find_swboard_with_id(const MsnSession *session, int chat_id)
+{
+	GList *l;
+
+	g_return_val_if_fail(session != NULL, NULL);
+	g_return_val_if_fail(chat_id >= 0,    NULL);
+
+	for (l = session->switches; l != NULL; l = l->next)
+	{
+		MsnSwitchBoard *swboard;
+
+		swboard = l->data;
+
+		if (swboard->chat_id == chat_id)
+			return swboard;
+	}
+
+	return NULL;
+}
+
+MsnSwitchBoard *
+msn_session_get_swboard(MsnSession *session, const char *username,
+						MsnSBFlag flag)
+{
+	MsnSwitchBoard *swboard;
+
+	g_return_val_if_fail(session != NULL, NULL);
+
+	swboard = msn_session_find_swboard(session, username);
+
+	if (swboard == NULL)
+	{
+		swboard = msn_switchboard_new(session);
+		swboard->im_user = g_strdup(username);
+		msn_switchboard_request(swboard);
+		msn_switchboard_request_add_user(swboard, username);
+	}
+
+	swboard->flag |= flag;
+
+	return swboard;
+}
+
+static void
+msn_session_sync_users(MsnSession *session)
+{
+	PurpleBlistNode *gnode, *cnode, *bnode;
+	PurpleConnection *gc = purple_account_get_connection(session->account);
+
+	g_return_if_fail(gc != NULL);
+
+	/* The core used to use msn_add_buddy to add all buddies before
+	 * being logged in. This no longer happens, so we manually iterate
+	 * over the whole buddy list to identify sync issues. */
+
+	for (gnode = purple_blist_get_root(); gnode; gnode = gnode->next) {
+		PurpleGroup *group = (PurpleGroup *)gnode;
+		const char *group_name = group->name;
+		if(!PURPLE_BLIST_NODE_IS_GROUP(gnode))
+			continue;
+		for(cnode = gnode->child; cnode; cnode = cnode->next) {
+			if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
+				continue;
+			for(bnode = cnode->child; bnode; bnode = bnode->next) {
+				PurpleBuddy *b;
+				if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
+					continue;
+				b = (PurpleBuddy *)bnode;
+				if(purple_buddy_get_account(b) == purple_connection_get_account(gc)) {
+					MsnUser *remote_user;
+					gboolean found = FALSE;
+
+					remote_user = msn_userlist_find_user(session->userlist, purple_buddy_get_name(b));
+
+					if ((remote_user != NULL) && (remote_user->list_op & MSN_LIST_FL_OP))
+					{
+						int group_id;
+						GList *l;
+
+						group_id = msn_userlist_find_group_id(remote_user->userlist,
+								group_name);
+
+						for (l = remote_user->group_ids; l != NULL; l = l->next)
+						{
+							if (group_id == GPOINTER_TO_INT(l->data))
+							{
+								found = TRUE;
+								break;
+							}
+						}
+
+					}
+
+					if (!found)
+					{
+						/* The user was not on the server list or not in that group
+						 * on the server list */
+						msn_show_sync_issue(session, purple_buddy_get_name(b), group_name);
+					}
+				}
+			}
+		}
+	}
+}
+
+void
+msn_session_set_error(MsnSession *session, MsnErrorType error,
+					  const char *info)
+{
+	PurpleConnection *gc;
+	char *msg;
+
+	gc = purple_account_get_connection(session->account);
+
+	switch (error)
+	{
+		case MSN_ERROR_SERVCONN:
+			msg = g_strdup(info);
+			break;
+		case MSN_ERROR_UNSUPPORTED_PROTOCOL:
+			msg = g_strdup(_("Our protocol is not supported by the "
+							 "server."));
+			break;
+		case MSN_ERROR_HTTP_MALFORMED:
+			msg = g_strdup(_("Error parsing HTTP."));
+			break;
+		case MSN_ERROR_SIGN_OTHER:
+			gc->wants_to_die = TRUE;
+			msg = g_strdup(_("You have signed on from another location."));
+			break;
+		case MSN_ERROR_SERV_UNAVAILABLE:
+			msg = g_strdup(_("The MSN servers are temporarily "
+							 "unavailable. Please wait and try "
+							 "again."));
+			break;
+		case MSN_ERROR_SERV_DOWN:
+			msg = g_strdup(_("The MSN servers are going down "
+							 "temporarily."));
+			break;
+		case MSN_ERROR_AUTH:
+			gc->wants_to_die = TRUE;
+			msg = g_strdup_printf(_("Unable to authenticate: %s"),
+								  (info == NULL ) ?
+								  _("Unknown error") : info);
+			break;
+		case MSN_ERROR_BAD_BLIST:
+			msg = g_strdup(_("Your MSN buddy list is temporarily "
+							 "unavailable. Please wait and try "
+							 "again."));
+			break;
+		default:
+			msg = g_strdup(_("Unknown error."));
+			break;
+	}
+
+	msn_session_disconnect(session);
+
+	purple_connection_error(gc, msg);
+
+	g_free(msg);
+}
+
+static const char *
+get_login_step_text(MsnSession *session)
+{
+	const char *steps_text[] = {
+		_("Connecting"),
+		_("Handshaking"),
+		_("Transferring"),
+		_("Handshaking"),
+		_("Starting authentication"),
+		_("Getting cookie"),
+		_("Authenticating"),
+		_("Sending cookie"),
+		_("Retrieving buddy list")
+	};
+
+	return steps_text[session->login_step];
+}
+
+void
+msn_session_set_login_step(MsnSession *session, MsnLoginStep step)
+{
+	PurpleConnection *gc;
+
+	/* Prevent the connection progress going backwards, eg. if we get
+	 * transferred several times during login */
+	if (session->login_step > step)
+		return;
+
+	/* If we're already logged in, we're probably here because of a
+	 * mid-session XFR from the notification server, so we don't want to
+	 * popup the connection progress dialog */
+	if (session->logged_in)
+		return;
+
+	gc = session->account->gc;
+
+	session->login_step = step;
+
+	purple_connection_update_progress(gc, get_login_step_text(session), step,
+									MSN_LOGIN_STEPS);
+}
+
+void
+msn_session_finish_login(MsnSession *session)
+{
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	PurpleStoredImage *img;
+	const char *passport;
+
+	if (session->logged_in)
+		return;
+
+	account = session->account;
+	gc = purple_account_get_connection(account);
+
+	img = purple_buddy_icons_find_account_icon(session->account);
+	msn_user_set_buddy_icon(session->user, img);
+	purple_imgstore_unref(img);
+
+	session->logged_in = TRUE;
+
+	msn_change_status(session);
+
+	purple_connection_set_state(gc, PURPLE_CONNECTED);
+
+	/* Sync users */
+	msn_session_sync_users(session);
+	/* It seems that some accounts that haven't accessed hotmail for a while
+	 * and @msn.com accounts don't automatically get the initial email
+	 * notification so we always request it on login
+	 */
+
+	passport = purple_normalize(account, purple_account_get_username(account));
+
+	if ((strstr(passport, "@hotmail.") != NULL) ||
+		(strstr(passport, "@msn.com") != NULL))
+	{
+		msn_cmdproc_send(session->notification->cmdproc, "URL", "%s", "INBOX");
+	}
+}
============================================================
--- libpurple/protocols/msnp9/session.h	d5bec9bc0a92062231a3af894739157d9a8766c1
+++ libpurple/protocols/msnp9/session.h	d5bec9bc0a92062231a3af894739157d9a8766c1
@@ -0,0 +1,227 @@
+/**
+ * @file session.h MSN session functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_SESSION_H_
+#define _MSN_SESSION_H_
+
+typedef struct _MsnSession MsnSession;
+
+#include "sslconn.h"
+
+#include "user.h"
+#include "slpcall.h"
+
+#include "notification.h"
+#include "switchboard.h"
+#include "group.h"
+
+#include "cmdproc.h"
+#include "nexus.h"
+#include "httpconn.h"
+
+#include "userlist.h"
+#include "sync.h"
+
+/**
+ * Types of errors.
+ */
+typedef enum
+{
+	MSN_ERROR_SERVCONN,
+	MSN_ERROR_UNSUPPORTED_PROTOCOL,
+	MSN_ERROR_HTTP_MALFORMED,
+	MSN_ERROR_AUTH,
+	MSN_ERROR_BAD_BLIST,
+	MSN_ERROR_SIGN_OTHER,
+	MSN_ERROR_SERV_DOWN,
+	MSN_ERROR_SERV_UNAVAILABLE
+
+} MsnErrorType;
+
+/**
+ * Login steps.
+ */
+typedef enum
+{
+	MSN_LOGIN_STEP_START,
+	MSN_LOGIN_STEP_HANDSHAKE,
+	MSN_LOGIN_STEP_TRANSFER,
+	MSN_LOGIN_STEP_HANDSHAKE2,
+	MSN_LOGIN_STEP_AUTH_START,
+	MSN_LOGIN_STEP_AUTH,
+	MSN_LOGIN_STEP_GET_COOKIE,
+	MSN_LOGIN_STEP_AUTH_END,
+	MSN_LOGIN_STEP_SYN,
+	MSN_LOGIN_STEP_END
+
+} MsnLoginStep;
+
+#define MSN_LOGIN_STEPS MSN_LOGIN_STEP_END
+
+struct _MsnSession
+{
+	PurpleAccount *account;
+	MsnUser *user;
+
+	guint protocol_ver;
+
+	MsnLoginStep login_step; /**< The current step in the login process. */
+
+	gboolean connected;
+	gboolean logged_in; /**< A temporal flag to ignore local buddy list adds. */
+	gboolean destroying; /**< A flag that states if the session is being destroyed. */
+	gboolean http_method;
+
+	MsnNotification *notification;
+	MsnNexus *nexus;
+	MsnSync *sync;
+
+	MsnUserList *userlist;
+
+	int servconns_count; /**< The count of server connections. */
+	GList *switches; /**< The list of all the switchboards. */
+	GList *directconns; /**< The list of all the directconnections. */
+	GList *slplinks; /**< The list of all the slplinks. */
+
+	int conv_seq; /**< The current conversation sequence number. */
+
+	struct
+	{
+		char *kv;
+		char *sid;
+		char *mspauth;
+		unsigned long sl;
+		char *file;
+		char *client_ip;
+		int client_port;
+
+	} passport_info;
+};
+
+/**
+ * Creates an MSN session.
+ *
+ * @param account The account.
+ *
+ * @return The new MSN session.
+ */
+MsnSession *msn_session_new(PurpleAccount *account);
+
+/**
+ * Destroys an MSN session.
+ *
+ * @param session The MSN session to destroy.
+ */
+void msn_session_destroy(MsnSession *session);
+
+/**
+ * Connects to and initiates an MSN session.
+ *
+ * @param session     The MSN session.
+ * @param host        The dispatch server host.
+ * @param port        The dispatch server port.
+ * @param http_method Whether to use or not http_method.
+ *
+ * @return @c TRUE on success, @c FALSE on failure.
+ */
+gboolean msn_session_connect(MsnSession *session,
+							 const char *host, int port,
+							 gboolean http_method);
+
+/**
+ * Disconnects from an MSN session.
+ *
+ * @param session The MSN session.
+ */
+void msn_session_disconnect(MsnSession *session);
+
+ /**
+ * Finds a switchboard with the given username.
+ *
+ * @param session The MSN session.
+ * @param username The username to search for.
+ *
+ * @return The switchboard, if found.
+ */
+MsnSwitchBoard *msn_session_find_swboard(MsnSession *session,
+										 const char *username);
+
+ /**
+ * Finds a switchboard with the given conversation.
+ *
+ * @param session The MSN session.
+ * @param conv    The conversation to search for.
+ *
+ * @return The switchboard, if found.
+ */
+MsnSwitchBoard *msn_session_find_swboard_with_conv(MsnSession *session,
+												   PurpleConversation *conv);
+/**
+ * Finds a switchboard with the given chat ID.
+ *
+ * @param session The MSN session.
+ * @param chat_id The chat ID to search for.
+ *
+ * @return The switchboard, if found.
+ */
+MsnSwitchBoard *msn_session_find_swboard_with_id(const MsnSession *session,
+												 int chat_id);
+
+/**
+ * Returns a switchboard to communicate with certain username.
+ *
+ * @param session The MSN session.
+ * @param username The username to search for.
+ * @param flag The flag of the switchboard
+ *
+ * @return The switchboard.
+ */
+MsnSwitchBoard *msn_session_get_swboard(MsnSession *session,
+										const char *username, MsnSBFlag flag);
+
+/**
+ * Sets an error for the MSN session.
+ *
+ * @param session The MSN session.
+ * @param error The error.
+ * @param info Extra information.
+ */
+void msn_session_set_error(MsnSession *session, MsnErrorType error,
+						   const char *info);
+
+/**
+ * Sets the current step in the login proccess.
+ *
+ * @param session The MSN session.
+ * @param step The current step.
+ */
+void msn_session_set_login_step(MsnSession *session, MsnLoginStep step);
+
+/**
+ * Finish the login proccess.
+ *
+ * @param session The MSN session.
+ */
+void msn_session_finish_login(MsnSession *session);
+
+#endif /* _MSN_SESSION_H_ */
============================================================
--- libpurple/protocols/msnp9/slp.c	4c5fa0bb627bb1a0d5c81ea293cca2f605cdae90
+++ libpurple/protocols/msnp9/slp.c	4c5fa0bb627bb1a0d5c81ea293cca2f605cdae90
@@ -0,0 +1,1109 @@
+/**
+ * @file msnslp.c MSNSLP support
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "slp.h"
+#include "slpcall.h"
+#include "slpmsg.h"
+#include "slpsession.h"
+
+#include "object.h"
+#include "user.h"
+#include "switchboard.h"
+
+/* ms to delay between sending buddy icon requests to the server. */
+#define BUDDY_ICON_DELAY 20000
+
+static void send_ok(MsnSlpCall *slpcall, const char *branch,
+					const char *type, const char *content);
+
+static void send_decline(MsnSlpCall *slpcall, const char *branch,
+						 const char *type, const char *content);
+
+void msn_request_user_display(MsnUser *user);
+
+/**************************************************************************
+ * Util
+ **************************************************************************/
+
+static char *
+get_token(const char *str, const char *start, const char *end)
+{
+	const char *c, *c2;
+
+	if ((c = strstr(str, start)) == NULL)
+		return NULL;
+
+	c += strlen(start);
+
+	if (end != NULL)
+	{
+		if ((c2 = strstr(c, end)) == NULL)
+			return NULL;
+
+		return g_strndup(c, c2 - c);
+	}
+	else
+	{
+		/* This has to be changed */
+		return g_strdup(c);
+	}
+
+}
+
+/**************************************************************************
+ * Xfer
+ **************************************************************************/
+
+static void
+msn_xfer_init(PurpleXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+	/* MsnSlpLink *slplink; */
+	char *content;
+
+	purple_debug_info("msn", "xfer_init\n");
+
+	slpcall = xfer->data;
+
+	/* Send Ok */
+	content = g_strdup_printf("SessionID: %lu\r\n\r\n",
+							  slpcall->session_id);
+
+	send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
+			content);
+
+	g_free(content);
+	msn_slplink_unleash(slpcall->slplink);
+}
+
+void
+msn_xfer_cancel(PurpleXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+	char *content;
+
+	g_return_if_fail(xfer != NULL);
+	g_return_if_fail(xfer->data != NULL);
+
+	slpcall = xfer->data;
+
+	if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL)
+	{
+		if (slpcall->started)
+		{
+			msn_slp_call_close(slpcall);
+		}
+		else
+		{
+			content = g_strdup_printf("SessionID: %lu\r\n\r\n",
+									slpcall->session_id);
+
+			send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
+						content);
+
+			g_free(content);
+			msn_slplink_unleash(slpcall->slplink);
+
+			msn_slp_call_destroy(slpcall);
+		}
+	}
+}
+
+void
+msn_xfer_progress_cb(MsnSlpCall *slpcall, gsize total_length, gsize len, gsize offset)
+{
+	PurpleXfer *xfer;
+
+	xfer = slpcall->xfer;
+
+	xfer->bytes_sent = (offset + len);
+	xfer->bytes_remaining = total_length - (offset + len);
+
+	purple_xfer_update_progress(xfer);
+}
+
+void
+msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session)
+{
+	if ((purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_DONE) &&
+		(purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_REMOTE) &&
+		(purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_LOCAL))
+	{
+		purple_xfer_cancel_remote(slpcall->xfer);
+	}
+}
+
+void
+msn_xfer_completed_cb(MsnSlpCall *slpcall, const guchar *body,
+					  gsize size)
+{
+	PurpleXfer *xfer = slpcall->xfer;
+	purple_xfer_set_completed(xfer, TRUE);
+	purple_xfer_end(xfer);
+}
+
+/**************************************************************************
+ * SLP Control
+ **************************************************************************/
+
+#if 0
+static void
+got_transresp(MsnSlpCall *slpcall, const char *nonce,
+			  const char *ips_str, int port)
+{
+	MsnDirectConn *directconn;
+	char **ip_addrs, **c;
+
+	directconn = msn_directconn_new(slpcall->slplink);
+
+	directconn->initial_call = slpcall;
+
+	/* msn_directconn_parse_nonce(directconn, nonce); */
+	directconn->nonce = g_strdup(nonce);
+
+	ip_addrs = g_strsplit(ips_str, " ", -1);
+
+	for (c = ip_addrs; *c != NULL; c++)
+	{
+		purple_debug_info("msn", "ip_addr = %s\n", *c);
+		if (msn_directconn_connect(directconn, *c, port))
+			break;
+	}
+
+	g_strfreev(ip_addrs);
+}
+#endif
+
+static void
+send_ok(MsnSlpCall *slpcall, const char *branch,
+		const char *type, const char *content)
+{
+	MsnSlpLink *slplink;
+	MsnSlpMessage *slpmsg;
+
+	slplink = slpcall->slplink;
+
+	/* 200 OK */
+	slpmsg = msn_slpmsg_sip_new(slpcall, 1,
+								"MSNSLP/1.0 200 OK",
+								branch, type, content);
+
+#ifdef MSN_DEBUG_SLP
+	slpmsg->info = "SLP 200 OK";
+	slpmsg->text_body = TRUE;
+#endif
+
+	msn_slplink_queue_slpmsg(slplink, slpmsg);
+
+	msn_slp_call_session_init(slpcall);
+}
+
+static void
+send_decline(MsnSlpCall *slpcall, const char *branch,
+			 const char *type, const char *content)
+{
+	MsnSlpLink *slplink;
+	MsnSlpMessage *slpmsg;
+
+	slplink = slpcall->slplink;
+
+	/* 603 Decline */
+	slpmsg = msn_slpmsg_sip_new(slpcall, 1,
+								"MSNSLP/1.0 603 Decline",
+								branch, type, content);
+
+#ifdef MSN_DEBUG_SLP
+	slpmsg->info = "SLP 603 Decline";
+	slpmsg->text_body = TRUE;
+#endif
+
+	msn_slplink_queue_slpmsg(slplink, slpmsg);
+}
+
+#define MAX_FILE_NAME_LEN 0x226
+
+static void
+got_sessionreq(MsnSlpCall *slpcall, const char *branch,
+			   const char *euf_guid, const char *context)
+{
+	if (!strcmp(euf_guid, "A4268EEC-FEC5-49E5-95C3-F126696BDBF6"))
+	{
+		/* Emoticon or UserDisplay */
+		char *content;
+		gsize len;
+		MsnSlpSession *slpsession;
+		MsnSlpLink *slplink;
+		MsnSlpMessage *slpmsg;
+		MsnObject *obj;
+		char *msnobj_data;
+		PurpleStoredImage *img;
+		int type;
+
+		/* Send Ok */
+		content = g_strdup_printf("SessionID: %lu\r\n\r\n",
+								  slpcall->session_id);
+
+		send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody",
+				content);
+
+		g_free(content);
+
+		slplink = slpcall->slplink;
+
+		msnobj_data = (char *)purple_base64_decode(context, &len);
+		obj = msn_object_new_from_string(msnobj_data);
+		type = msn_object_get_type(obj);
+		g_free(msnobj_data);
+
+		if (!(type == MSN_OBJECT_USERTILE))
+		{
+			purple_debug_error("msn", "Wrong object?\n");
+			msn_object_destroy(obj);
+			g_return_if_reached();
+		}
+
+		img = msn_object_get_image(obj);
+		if (img == NULL)
+		{
+			purple_debug_error("msn", "Wrong object.\n");
+			msn_object_destroy(obj);
+			g_return_if_reached();
+		}
+
+		msn_object_destroy(obj);
+
+		slpsession = msn_slplink_find_slp_session(slplink,
+												  slpcall->session_id);
+
+		/* DATA PREP */
+		slpmsg = msn_slpmsg_new(slplink);
+		slpmsg->slpcall = slpcall;
+		slpmsg->slpsession = slpsession;
+		slpmsg->session_id = slpsession->id;
+		msn_slpmsg_set_body(slpmsg, NULL, 4);
+#ifdef MSN_DEBUG_SLP
+		slpmsg->info = "SLP DATA PREP";
+#endif
+		msn_slplink_queue_slpmsg(slplink, slpmsg);
+
+		/* DATA */
+		slpmsg = msn_slpmsg_new(slplink);
+		slpmsg->slpcall = slpcall;
+		slpmsg->slpsession = slpsession;
+		slpmsg->flags = 0x20;
+#ifdef MSN_DEBUG_SLP
+		slpmsg->info = "SLP DATA";
+#endif
+		msn_slpmsg_set_image(slpmsg, img);
+		msn_slplink_queue_slpmsg(slplink, slpmsg);
+	}
+	else if (!strcmp(euf_guid, "5D3E02AB-6190-11D3-BBBB-00C04F795683"))
+	{
+		/* File Transfer */
+		PurpleAccount *account;
+		PurpleXfer *xfer;
+		char *bin;
+		gsize bin_len;
+		guint32 file_size;
+		char *file_name;
+		gunichar2 *uni_name;
+
+		account = slpcall->slplink->session->account;
+
+		slpcall->cb = msn_xfer_completed_cb;
+		slpcall->end_cb = msn_xfer_end_cb;
+		slpcall->progress_cb = msn_xfer_progress_cb;
+		slpcall->branch = g_strdup(branch);
+
+		slpcall->pending = TRUE;
+
+		xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE,
+							 slpcall->slplink->remote_user);
+		if (xfer)
+		{
+			bin = (char *)purple_base64_decode(context, &bin_len);
+			file_size = GUINT32_FROM_LE(*(gsize *)(bin + 8));
+
+			uni_name = (gunichar2 *)(bin + 20);
+			while(*uni_name != 0 && ((char *)uni_name - (bin + 20)) < MAX_FILE_NAME_LEN) {
+				*uni_name = GUINT16_FROM_LE(*uni_name);
+				uni_name++;
+			}
+
+			file_name = g_utf16_to_utf8((const gunichar2 *)(bin + 20), -1,
+										NULL, NULL, NULL);
+
+			g_free(bin);
+
+			purple_xfer_set_filename(xfer, file_name);
+			purple_xfer_set_size(xfer, file_size);
+			purple_xfer_set_init_fnc(xfer, msn_xfer_init);
+			purple_xfer_set_request_denied_fnc(xfer, msn_xfer_cancel);
+			purple_xfer_set_cancel_recv_fnc(xfer, msn_xfer_cancel);
+
+			slpcall->xfer = xfer;
+			purple_xfer_ref(slpcall->xfer);
+
+			xfer->data = slpcall;
+
+			purple_xfer_request(xfer);
+		}
+	}
+}
+
+void
+send_bye(MsnSlpCall *slpcall, const char *type)
+{
+	MsnSlpLink *slplink;
+	MsnSlpMessage *slpmsg;
+	char *header;
+
+	slplink = slpcall->slplink;
+
+	g_return_if_fail(slplink != NULL);
+
+	header = g_strdup_printf("BYE MSNMSGR:%s MSNSLP/1.0",
+							 slplink->local_user);
+
+	slpmsg = msn_slpmsg_sip_new(slpcall, 0, header,
+								"A0D624A6-6C0C-4283-A9E0-BC97B4B46D32",
+								type,
+								"\r\n");
+	g_free(header);
+
+#ifdef MSN_DEBUG_SLP
+	slpmsg->info = "SLP BYE";
+	slpmsg->text_body = TRUE;
+#endif
+
+	msn_slplink_queue_slpmsg(slplink, slpmsg);
+}
+
+static void
+got_invite(MsnSlpCall *slpcall,
+		   const char *branch, const char *type, const char *content)
+{
+	MsnSlpLink *slplink;
+
+	slplink = slpcall->slplink;
+
+	if (!strcmp(type, "application/x-msnmsgr-sessionreqbody"))
+	{
+		char *euf_guid, *context;
+		char *temp;
+
+		euf_guid = get_token(content, "EUF-GUID: {", "}\r\n");
+
+		temp = get_token(content, "SessionID: ", "\r\n");
+		if (temp != NULL)
+			slpcall->session_id = atoi(temp);
+		g_free(temp);
+
+		temp = get_token(content, "AppID: ", "\r\n");
+		if (temp != NULL)
+			slpcall->app_id = atoi(temp);
+		g_free(temp);
+
+		context = get_token(content, "Context: ", "\r\n");
+
+		if (context != NULL)
+			got_sessionreq(slpcall, branch, euf_guid, context);
+
+		g_free(context);
+		g_free(euf_guid);
+	}
+	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
+	{
+		/* A direct connection? */
+
+		char *listening, *nonce;
+		char *content;
+
+		if (FALSE)
+		{
+#if 0
+			MsnDirectConn *directconn;
+			/* const char *ip_addr; */
+			char *ip_port;
+			int port;
+
+			/* ip_addr = purple_prefs_get_string("/purple/ft/public_ip"); */
+			ip_port = "5190";
+			listening = "true";
+			nonce = rand_guid();
+
+			directconn = msn_directconn_new(slplink);
+
+			/* msn_directconn_parse_nonce(directconn, nonce); */
+			directconn->nonce = g_strdup(nonce);
+
+			msn_directconn_listen(directconn);
+
+			port = directconn->port;
+
+			content = g_strdup_printf(
+				"Bridge: TCPv1\r\n"
+				"Listening: %s\r\n"
+				"Nonce: {%s}\r\n"
+				"Ipv4Internal-Addrs: 192.168.0.82\r\n"
+				"Ipv4Internal-Port: %d\r\n"
+				"\r\n",
+				listening,
+				nonce,
+				port);
+#endif
+		}
+		else
+		{
+			listening = "false";
+			nonce = g_strdup("00000000-0000-0000-0000-000000000000");
+
+			content = g_strdup_printf(
+				"Bridge: TCPv1\r\n"
+				"Listening: %s\r\n"
+				"Nonce: {%s}\r\n"
+				"\r\n",
+				listening,
+				nonce);
+		}
+
+		send_ok(slpcall, branch,
+				"application/x-msnmsgr-transrespbody", content);
+
+		g_free(content);
+		g_free(nonce);
+	}
+	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
+	{
+#if 0
+		char *ip_addrs;
+		char *temp;
+		char *nonce;
+		int port;
+
+		nonce = get_token(content, "Nonce: {", "}\r\n");
+		ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
+
+		temp = get_token(content, "IPv4Internal-Port: ", "\r\n");
+		if (temp != NULL)
+			port = atoi(temp);
+		else
+			port = -1;
+		g_free(temp);
+
+		if (ip_addrs == NULL)
+			return;
+
+		if (port > 0)
+			got_transresp(slpcall, nonce, ip_addrs, port);
+
+		g_free(nonce);
+		g_free(ip_addrs);
+#endif
+	}
+}
+
+static void
+got_ok(MsnSlpCall *slpcall,
+	   const char *type, const char *content)
+{
+	g_return_if_fail(slpcall != NULL);
+	g_return_if_fail(type    != NULL);
+
+	if (!strcmp(type, "application/x-msnmsgr-sessionreqbody"))
+	{
+#if 0
+		if (slpcall->type == MSN_SLPCALL_DC)
+		{
+			/* First let's try a DirectConnection. */
+
+			MsnSlpLink *slplink;
+			MsnSlpMessage *slpmsg;
+			char *header;
+			char *content;
+			char *branch;
+
+			slplink = slpcall->slplink;
+
+			branch = rand_guid();
+
+			content = g_strdup_printf(
+				"Bridges: TRUDPv1 TCPv1\r\n"
+				"NetID: 0\r\n"
+				"Conn-Type: Direct-Connect\r\n"
+				"UPnPNat: false\r\n"
+				"ICF: false\r\n"
+			);
+
+			header = g_strdup_printf("INVITE MSNMSGR:%s MSNSLP/1.0",
+									 slplink->remote_user);
+
+			slpmsg = msn_slp_sipmsg_new(slpcall, 0, header, branch,
+										"application/x-msnmsgr-transreqbody",
+										content);
+
+#ifdef MSN_DEBUG_SLP
+			slpmsg->info = "SLP INVITE";
+			slpmsg->text_body = TRUE;
+#endif
+			msn_slplink_send_slpmsg(slplink, slpmsg);
+
+			g_free(header);
+			g_free(content);
+
+			g_free(branch);
+		}
+		else
+		{
+			msn_slp_call_session_init(slpcall);
+		}
+#else
+		msn_slp_call_session_init(slpcall);
+#endif
+	}
+	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
+	{
+		/* Do we get this? */
+		purple_debug_info("msn", "OK with transreqbody\n");
+	}
+	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
+	{
+#if 0
+		char *ip_addrs;
+		char *temp;
+		char *nonce;
+		int port;
+
+		nonce = get_token(content, "Nonce: {", "}\r\n");
+		ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
+
+		temp = get_token(content, "IPv4Internal-Port: ", "\r\n");
+		if (temp != NULL)
+			port = atoi(temp);
+		else
+			port = -1;
+		g_free(temp);
+
+		if (ip_addrs == NULL)
+			return;
+
+		if (port > 0)
+			got_transresp(slpcall, nonce, ip_addrs, port);
+
+		g_free(nonce);
+		g_free(ip_addrs);
+#endif
+	}
+}
+
+MsnSlpCall *
+msn_slp_sip_recv(MsnSlpLink *slplink, const char *body)
+{
+	MsnSlpCall *slpcall;
+
+	if (body == NULL)
+	{
+		purple_debug_warning("msn", "received bogus message\n");
+		return NULL;
+	}
+
+	if (!strncmp(body, "INVITE", strlen("INVITE")))
+	{
+		char *branch;
+		char *content;
+		char *content_type;
+
+		slpcall = msn_slp_call_new(slplink);
+
+		/* From: <msnmsgr:buddy at hotmail.com> */
+#if 0
+		slpcall->remote_user = get_token(body, "From: <msnmsgr:", ">\r\n");
+#endif
+
+		branch = get_token(body, ";branch={", "}");
+
+		slpcall->id = get_token(body, "Call-ID: {", "}");
+
+#if 0
+		long content_len = -1;
+
+		temp = get_token(body, "Content-Length: ", "\r\n");
+		if (temp != NULL)
+			content_len = atoi(temp);
+		g_free(temp);
+#endif
+		content_type = get_token(body, "Content-Type: ", "\r\n");
+
+		content = get_token(body, "\r\n\r\n", NULL);
+
+		got_invite(slpcall, branch, content_type, content);
+
+		g_free(branch);
+		g_free(content_type);
+		g_free(content);
+	}
+	else if (!strncmp(body, "MSNSLP/1.0 ", strlen("MSNSLP/1.0 ")))
+	{
+		char *content;
+		char *content_type;
+		/* Make sure this is "OK" */
+		const char *status = body + strlen("MSNSLP/1.0 ");
+		char *call_id;
+
+		call_id = get_token(body, "Call-ID: {", "}");
+		slpcall = msn_slplink_find_slp_call(slplink, call_id);
+		g_free(call_id);
+
+		g_return_val_if_fail(slpcall != NULL, NULL);
+
+		if (strncmp(status, "200 OK", 6))
+		{
+			/* It's not valid. Kill this off. */
+			char temp[32];
+			const char *c;
+
+			/* Eww */
+			if ((c = strchr(status, '\r')) || (c = strchr(status, '\n')) ||
+				(c = strchr(status, '\0')))
+			{
+				size_t offset =  c - status;
+				if (offset >= sizeof(temp))
+					offset = sizeof(temp) - 1;
+
+				strncpy(temp, status, offset);
+				temp[offset] = '\0';
+			}
+
+			purple_debug_error("msn", "Received non-OK result: %s\n", temp);
+
+			slpcall->wasted = TRUE;
+
+			/* msn_slp_call_destroy(slpcall); */
+			return slpcall;
+		}
+
+		content_type = get_token(body, "Content-Type: ", "\r\n");
+
+		content = get_token(body, "\r\n\r\n", NULL);
+
+		got_ok(slpcall, content_type, content);
+
+		g_free(content_type);
+		g_free(content);
+	}
+	else if (!strncmp(body, "BYE", strlen("BYE")))
+	{
+		char *call_id;
+
+		call_id = get_token(body, "Call-ID: {", "}");
+		slpcall = msn_slplink_find_slp_call(slplink, call_id);
+		g_free(call_id);
+
+		if (slpcall != NULL)
+			slpcall->wasted = TRUE;
+
+		/* msn_slp_call_destroy(slpcall); */
+	}
+	else
+		slpcall = NULL;
+
+	return slpcall;
+}
+
+/**************************************************************************
+ * Msg Callbacks
+ **************************************************************************/
+
+void
+msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	MsnSession *session;
+	MsnSlpLink *slplink;
+
+	session = cmdproc->servconn->session;
+	slplink = msn_session_get_slplink(session, msg->remote_user);
+
+	if (slplink->swboard == NULL)
+	{
+		/* We will need this in order to change its flags. */
+		slplink->swboard = (MsnSwitchBoard *)cmdproc->data;
+		/* If swboard is NULL, something has probably gone wrong earlier on
+		 * I didn't want to do this, but MSN 7 is somehow causing us to crash
+		 * here, I couldn't reproduce it to debug more, and people are
+		 * reporting bugs. Hopefully this doesn't cause more crashes. Stu.
+		 */
+		if (slplink->swboard != NULL)
+			slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink);
+		else
+			purple_debug_error("msn", "msn_p2p_msg, swboard is NULL, ouch!\n");
+	}
+
+	msn_slplink_process_msg(slplink, msg);
+}
+
+static void
+got_emoticon(MsnSlpCall *slpcall,
+			 const guchar *data, gsize size)
+{
+
+	PurpleConversation *conv;
+	PurpleConnection *gc;
+	const char *who;
+
+	gc = slpcall->slplink->session->account->gc;
+	who = slpcall->slplink->remote_user;
+
+	if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, who, gc->account))) {
+
+		/* FIXME: it would be better if we wrote the data as we received it
+		          instead of all at once, calling write multiple times and
+		          close once at the very end
+		*/
+		purple_conv_custom_smiley_write(conv, slpcall->data_info, data, size);
+		purple_conv_custom_smiley_close(conv, slpcall->data_info);
+	}
+#ifdef MSN_DEBUG_UD
+	purple_debug_info("msn", "Got smiley: %s\n", slpcall->data_info);
+#endif
+}
+
+void
+msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	MsnSession *session;
+	MsnSlpLink *slplink;
+	MsnObject *obj;
+	char **tokens;
+	char *smile, *body_str;
+	const char *body, *who, *sha1;
+	guint tok;
+	size_t body_len;
+
+	PurpleConversation *conv;
+
+	session = cmdproc->servconn->session;
+
+	if (!purple_account_get_bool(session->account, "custom_smileys", TRUE))
+		return;
+
+	body = msn_message_get_bin_data(msg, &body_len);
+	body_str = g_strndup(body, body_len);
+
+	/* MSN Messenger 7 may send more than one MSNObject in a single message...
+	 * Maybe 10 tokens is a reasonable max value. */
+	tokens = g_strsplit(body_str, "\t", 10);
+
+	g_free(body_str);
+
+	for (tok = 0; tok < 9; tok += 2) {
+		if (tokens[tok] == NULL || tokens[tok + 1] == NULL) {
+			break;
+		}
+
+		smile = tokens[tok];
+		obj = msn_object_new_from_string(purple_url_decode(tokens[tok + 1]));
+
+		if (obj == NULL)
+			break;
+
+		who = msn_object_get_creator(obj);
+		sha1 = msn_object_get_sha1(obj);
+
+		slplink = msn_session_get_slplink(session, who);
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, who,
+												   session->account);
+
+		/* If the conversation doesn't exist then this is a custom smiley
+		 * used in the first message in a MSN conversation: we need to create
+		 * the conversation now, otherwise the custom smiley won't be shown.
+		 * This happens because every GtkIMHtml has its own smiley tree: if
+		 * the conversation doesn't exist then we cannot associate the new
+		 * smiley with its GtkIMHtml widget. */
+		if (!conv) {
+			conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, session->account, who);
+		}
+
+		if (purple_conv_custom_smiley_add(conv, smile, "sha1", sha1, TRUE)) {
+			msn_slplink_request_object(slplink, smile, got_emoticon, NULL, obj);
+		}
+
+		msn_object_destroy(obj);
+		obj =   NULL;
+		who =   NULL;
+		sha1 = NULL;
+	}
+	g_strfreev(tokens);
+}
+
+static gboolean
+buddy_icon_cached(PurpleConnection *gc, MsnObject *obj)
+{
+	PurpleAccount *account;
+	PurpleBuddy *buddy;
+	const char *old;
+	const char *new;
+
+	g_return_val_if_fail(obj != NULL, FALSE);
+
+	account = purple_connection_get_account(gc);
+
+	buddy = purple_find_buddy(account, msn_object_get_creator(obj));
+	if (buddy == NULL)
+		return FALSE;
+
+	old = purple_buddy_icons_get_checksum_for_user(buddy);
+	new = msn_object_get_sha1(obj);
+
+	if (new == NULL)
+		return FALSE;
+
+	/* If the old and new checksums are the same, and the file actually exists,
+	 * then return TRUE */
+	if (old != NULL && !strcmp(old, new))
+		return TRUE;
+
+	return FALSE;
+}
+
+static void
+msn_release_buddy_icon_request(MsnUserList *userlist)
+{
+	MsnUser *user;
+
+	g_return_if_fail(userlist != NULL);
+
+#ifdef MSN_DEBUG_UD
+	purple_debug_info("msn", "Releasing buddy icon request\n");
+#endif
+
+	if (userlist->buddy_icon_window > 0)
+	{
+		GQueue *queue;
+		PurpleAccount *account;
+		const char *username;
+
+		queue = userlist->buddy_icon_requests;
+
+		if (g_queue_is_empty(userlist->buddy_icon_requests))
+			return;
+
+		user = g_queue_pop_head(queue);
+
+		account  = userlist->session->account;
+		username = user->passport;
+
+		userlist->buddy_icon_window--;
+		msn_request_user_display(user);
+
+#ifdef MSN_DEBUG_UD
+		purple_debug_info("msn", "msn_release_buddy_icon_request(): buddy_icon_window-- yields =%d\n",
+						userlist->buddy_icon_window);
+#endif
+	}
+}
+
+/*
+ * Called on a timeout from end_user_display(). Frees a buddy icon window slow and dequeues the next
+ * buddy icon request if there is one.
+ */
+static gboolean
+msn_release_buddy_icon_request_timeout(gpointer data)
+{
+	MsnUserList *userlist = (MsnUserList *)data;
+	
+	/* Free one window slot */
+	userlist->buddy_icon_window++;	
+	
+	/* Clear the tag for our former request timer */
+	userlist->buddy_icon_request_timer = 0;
+	
+	msn_release_buddy_icon_request(userlist);
+	
+	return FALSE;
+}
+
+void
+msn_queue_buddy_icon_request(MsnUser *user)
+{
+	PurpleAccount *account;
+	MsnObject *obj;
+	GQueue *queue;
+
+	g_return_if_fail(user != NULL);
+
+	account = user->userlist->session->account;
+
+	obj = msn_user_get_object(user);
+
+	if (obj == NULL)
+	{
+		purple_buddy_icons_set_for_user(account, user->passport, NULL, 0, NULL);
+		return;
+	}
+
+	if (!buddy_icon_cached(account->gc, obj))
+	{
+		MsnUserList *userlist;
+
+		userlist = user->userlist;
+		queue = userlist->buddy_icon_requests;
+
+#ifdef MSN_DEBUG_UD
+		purple_debug_info("msn", "Queueing buddy icon request for %s (buddy_icon_window = %i)\n",
+						user->passport, userlist->buddy_icon_window);
+#endif
+
+		g_queue_push_tail(queue, user);
+
+		if (userlist->buddy_icon_window > 0)
+			msn_release_buddy_icon_request(userlist);
+	}
+}
+
+static void
+got_user_display(MsnSlpCall *slpcall,
+				 const guchar *data, gsize size)
+{
+	MsnUserList *userlist;
+	const char *info;
+	PurpleAccount *account;
+
+	g_return_if_fail(slpcall != NULL);
+
+	info = slpcall->data_info;
+#ifdef MSN_DEBUG_UD
+	purple_debug_info("msn", "Got User Display: %s\n", slpcall->slplink->remote_user);
+#endif
+
+	userlist = slpcall->slplink->session->userlist;
+	account = slpcall->slplink->session->account;
+
+	purple_buddy_icons_set_for_user(account, slpcall->slplink->remote_user,
+								  g_memdup(data, size), size, info);
+
+#if 0
+	/* Free one window slot */
+	userlist->buddy_icon_window++;
+
+	purple_debug_info("msn", "got_user_display(): buddy_icon_window++ yields =%d\n",
+					userlist->buddy_icon_window);
+
+	msn_release_buddy_icon_request(userlist);
+#endif
+}
+
+static void
+end_user_display(MsnSlpCall *slpcall, MsnSession *session)
+{
+	MsnUserList *userlist;
+
+	g_return_if_fail(session != NULL);
+
+#ifdef MSN_DEBUG_UD
+	purple_debug_info("msn", "End User Display\n");
+#endif
+
+	userlist = session->userlist;
+
+	/* If the session is being destroyed we better stop doing anything. */
+	if (session->destroying)
+		return;
+
+	/* Delay before freeing a buddy icon window slot and requesting the next icon, if appropriate.
+	 * If we don't delay, we'll rapidly hit the MSN equivalent of AIM's rate limiting; the server will
+	 * send us an error 800 like so:
+	 *
+	 * C: NS 000: XFR 21 SB
+	 * S: NS 000: 800 21
+	 */
+	if (userlist->buddy_icon_request_timer) {
+		/* Free the window slot used by this previous request */
+		userlist->buddy_icon_window++;
+
+		/* Clear our pending timeout */
+		purple_timeout_remove(userlist->buddy_icon_request_timer);
+	}
+
+	/* Wait BUDDY_ICON_DELAY ms before freeing our window slot and requesting the next icon. */
+	userlist->buddy_icon_request_timer = purple_timeout_add(BUDDY_ICON_DELAY, 
+														  msn_release_buddy_icon_request_timeout, userlist);
+}
+
+void
+msn_request_user_display(MsnUser *user)
+{
+	PurpleAccount *account;
+	MsnSession *session;
+	MsnSlpLink *slplink;
+	MsnObject *obj;
+	const char *info;
+
+	session = user->userlist->session;
+	account = session->account;
+
+	slplink = msn_session_get_slplink(session, user->passport);
+
+	obj = msn_user_get_object(user);
+
+	info = msn_object_get_sha1(obj);
+
+	if (g_ascii_strcasecmp(user->passport,
+						   purple_account_get_username(account)))
+	{
+		msn_slplink_request_object(slplink, info, got_user_display,
+								   end_user_display, obj);
+	}
+	else
+	{
+		MsnObject *my_obj = NULL;
+		gconstpointer data = NULL;
+		size_t len = 0;
+
+#ifdef MSN_DEBUG_UD
+		purple_debug_info("msn", "Requesting our own user display\n");
+#endif
+
+		my_obj = msn_user_get_object(session->user);
+
+		if (my_obj != NULL)
+		{
+			PurpleStoredImage *img = msn_object_get_image(my_obj);
+			data = purple_imgstore_get_data(img);
+			len = purple_imgstore_get_size(img);
+		}
+
+		purple_buddy_icons_set_for_user(account, user->passport, g_memdup(data, len), len, info);
+
+		/* Free one window slot */
+		session->userlist->buddy_icon_window++;
+
+#ifdef MSN_DEBUG_UD
+		purple_debug_info("msn", "msn_request_user_display(): buddy_icon_window++ yields =%d\n",
+						session->userlist->buddy_icon_window);
+#endif
+
+		msn_release_buddy_icon_request(session->userlist);
+	}
+}
============================================================
--- libpurple/protocols/msnp9/slp.h	dc0dde5fc9f6fe677e799f5b3f179deca9440155
+++ libpurple/protocols/msnp9/slp.h	dc0dde5fc9f6fe677e799f5b3f179deca9440155
@@ -0,0 +1,48 @@
+/**
+ * @file slp.h MSNSLP support
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_SLP_H_
+#define _MSN_SLP_H_
+
+#include "slpcall.h"
+#include "session.h"
+#include "internal.h"
+#include "ft.h"
+
+void msn_xfer_progress_cb(MsnSlpCall *slpcall, gsize total_length, gsize
+						  len, gsize offset);
+
+MsnSlpCall * msn_slp_sip_recv(MsnSlpLink *slplink,
+							  const char *body);
+
+void send_bye(MsnSlpCall *slpcall, const char *type);
+
+void msn_xfer_completed_cb(MsnSlpCall *slpcall,
+						   const guchar *body, gsize size);
+
+void msn_xfer_cancel(PurpleXfer *xfer);
+void msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session);
+
+void msn_queue_buddy_icon_request(MsnUser *user);
+
+#endif /* _MSN_SLP_H_ */
============================================================
--- libpurple/protocols/msnp9/slpcall.c	b030a1b9dbaca78638648cdba5ef30448101f2ab
+++ libpurple/protocols/msnp9/slpcall.c	b030a1b9dbaca78638648cdba5ef30448101f2ab
@@ -0,0 +1,270 @@
+/**
+ * @file slpcall.c SLP Call Functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "slpcall.h"
+#include "slpsession.h"
+
+#include "slp.h"
+
+/* #define MSN_DEBUG_SLPCALL */
+
+/**************************************************************************
+ * Util
+ **************************************************************************/
+
+static char *
+rand_guid()
+{
+	return g_strdup_printf("%4X%4X-%4X-%4X-%4X-%4X%4X%4X",
+			rand() % 0xAAFF + 0x1111,
+			rand() % 0xAAFF + 0x1111,
+			rand() % 0xAAFF + 0x1111,
+			rand() % 0xAAFF + 0x1111,
+			rand() % 0xAAFF + 0x1111,
+			rand() % 0xAAFF + 0x1111,
+			rand() % 0xAAFF + 0x1111,
+			rand() % 0xAAFF + 0x1111);
+}
+
+/**************************************************************************
+ * Main
+ **************************************************************************/
+
+MsnSlpCall *
+msn_slp_call_new(MsnSlpLink *slplink)
+{
+	MsnSlpCall *slpcall;
+
+	g_return_val_if_fail(slplink != NULL, NULL);
+
+	slpcall = g_new0(MsnSlpCall, 1);
+
+#ifdef MSN_DEBUG_SLPCALL
+	purple_debug_info("msn", "slpcall_new: slpcall(%p)\n", slpcall);
+#endif
+
+	slpcall->slplink = slplink;
+
+	msn_slplink_add_slpcall(slplink, slpcall);
+
+	slpcall->timer = purple_timeout_add(MSN_SLPCALL_TIMEOUT, msn_slp_call_timeout, slpcall);
+
+	return slpcall;
+}
+
+void
+msn_slp_call_destroy(MsnSlpCall *slpcall)
+{
+	GList *e;
+	MsnSession *session;
+
+#ifdef MSN_DEBUG_SLPCALL
+	purple_debug_info("msn", "slpcall_destroy: slpcall(%p)\n", slpcall);
+#endif
+
+	g_return_if_fail(slpcall != NULL);
+
+	if (slpcall->timer)
+		purple_timeout_remove(slpcall->timer);
+
+	if (slpcall->id != NULL)
+		g_free(slpcall->id);
+
+	if (slpcall->branch != NULL)
+		g_free(slpcall->branch);
+
+	if (slpcall->data_info != NULL)
+		g_free(slpcall->data_info);
+
+	for (e = slpcall->slplink->slp_msgs; e != NULL; )
+	{
+		MsnSlpMessage *slpmsg = e->data;
+		e = e->next;
+
+#ifdef MSN_DEBUG_SLPCALL_VERBOSE
+		purple_debug_info("msn", "slpcall_destroy: trying slpmsg(%p)\n",
+						slpmsg);
+#endif
+
+		if (slpmsg->slpcall == slpcall)
+		{
+			msn_slpmsg_destroy(slpmsg);
+		}
+	}
+
+	session = slpcall->slplink->session;
+
+	msn_slplink_remove_slpcall(slpcall->slplink, slpcall);
+
+	if (slpcall->end_cb != NULL)
+		slpcall->end_cb(slpcall, session);
+
+	if (slpcall->xfer != NULL)
+		purple_xfer_unref(slpcall->xfer);
+
+	g_free(slpcall);
+}
+
+void
+msn_slp_call_init(MsnSlpCall *slpcall, MsnSlpCallType type)
+{
+	slpcall->session_id = rand() % 0xFFFFFF00 + 4;
+	slpcall->id = rand_guid();
+	slpcall->type = type;
+}
+
+void
+msn_slp_call_session_init(MsnSlpCall *slpcall)
+{
+	MsnSlpSession *slpsession;
+
+	slpsession = msn_slp_session_new(slpcall);
+
+	if (slpcall->session_init_cb)
+		slpcall->session_init_cb(slpsession);
+
+	slpcall->started = TRUE;
+}
+
+void
+msn_slp_call_invite(MsnSlpCall *slpcall, const char *euf_guid,
+					int app_id, const char *context)
+{
+	MsnSlpLink *slplink;
+	MsnSlpMessage *slpmsg;
+	char *header;
+	char *content;
+
+	g_return_if_fail(slpcall != NULL);
+	g_return_if_fail(context != NULL);
+
+	slplink = slpcall->slplink;
+
+	slpcall->branch = rand_guid();
+
+	content = g_strdup_printf(
+		"EUF-GUID: {%s}\r\n"
+		"SessionID: %lu\r\n"
+		"AppID: %d\r\n"
+		"Context: %s\r\n\r\n",
+		euf_guid,
+		slpcall->session_id,
+		app_id,
+		context);
+
+	header = g_strdup_printf("INVITE MSNMSGR:%s MSNSLP/1.0",
+							 slplink->remote_user);
+
+	slpmsg = msn_slpmsg_sip_new(slpcall, 0, header, slpcall->branch,
+								"application/x-msnmsgr-sessionreqbody", content);
+
+#ifdef MSN_DEBUG_SLP
+	slpmsg->info = "SLP INVITE";
+	slpmsg->text_body = TRUE;
+#endif
+
+	msn_slplink_send_slpmsg(slplink, slpmsg);
+
+	g_free(header);
+	g_free(content);
+}
+
+void
+msn_slp_call_close(MsnSlpCall *slpcall)
+{
+	g_return_if_fail(slpcall != NULL);
+	g_return_if_fail(slpcall->slplink != NULL);
+
+	send_bye(slpcall, "application/x-msnmsgr-sessionclosebody");
+	msn_slplink_unleash(slpcall->slplink);
+	msn_slp_call_destroy(slpcall);
+}
+
+gboolean
+msn_slp_call_timeout(gpointer data)
+{
+	MsnSlpCall *slpcall;
+
+	slpcall = data;
+
+#ifdef MSN_DEBUG_SLPCALL
+	purple_debug_info("msn", "slpcall_timeout: slpcall(%p)\n", slpcall);
+#endif
+
+	if (!slpcall->pending && !slpcall->progress)
+	{
+		msn_slp_call_destroy(slpcall);
+		return FALSE;
+	}
+
+	slpcall->progress = FALSE;
+
+	return TRUE;
+}
+
+MsnSlpCall *
+msn_slp_process_msg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg)
+{
+	MsnSlpCall *slpcall;
+	const guchar *body;
+	gsize body_len;
+
+	slpcall = NULL;
+	body = slpmsg->buffer;
+	body_len = slpmsg->size;
+
+	if (slpmsg->flags == 0x0)
+	{
+		char *body_str;
+
+		body_str = g_strndup((const char *)body, body_len);
+		slpcall = msn_slp_sip_recv(slplink, body_str);
+		g_free(body_str);
+	}
+	else if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030)
+	{
+		slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->session_id);
+
+		if (slpcall != NULL)
+		{
+			if (slpcall->timer)
+				purple_timeout_remove(slpcall->timer);
+
+			slpcall->cb(slpcall, body, body_len);
+
+			slpcall->wasted = TRUE;
+		}
+	}
+#if 0
+	else if (slpmsg->flags == 0x100)
+	{
+		slpcall = slplink->directconn->initial_call;
+
+		if (slpcall != NULL)
+			msn_slp_call_session_init(slpcall);
+	}
+#endif
+
+	return slpcall;
+}
============================================================
--- libpurple/protocols/msnp9/slpcall.h	d2e0eca1cdb4f053fc4085d66c951c10a17adcac
+++ libpurple/protocols/msnp9/slpcall.h	d2e0eca1cdb4f053fc4085d66c951c10a17adcac
@@ -0,0 +1,91 @@
+/**
+ * @file slpcall.h SLP Call functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_SLPCALL_H_
+#define _MSN_SLPCALL_H_
+
+#include "internal.h"
+
+typedef struct _MsnSlpCall MsnSlpCall;
+
+#include "slplink.h"
+#include "slpsession.h"
+
+/* The official client seems to timeout slp calls after 5 minutes */
+#define MSN_SLPCALL_TIMEOUT 300000
+
+typedef enum
+{
+	MSN_SLPCALL_ANY,
+	MSN_SLPCALL_DC,
+
+} MsnSlpCallType;
+
+struct _MsnSlpCall
+{
+	/* MsnSession *session; */
+	MsnSlpLink *slplink;
+
+	MsnSlpCallType type;
+
+	/* Call-ID */
+	char *id;
+	char *branch;
+
+	long session_id;
+	long app_id;
+
+	gboolean pending; /**< A flag that states if we should wait for this
+						slpcall to start and do not time out. */
+	gboolean progress; /**< A flag that states if there has been progress since
+						 the last time out. */
+	gboolean wasted; /**< A flag that states if this slpcall is going to be
+					   destroyed. */
+	gboolean started; /**< A flag that states if this slpcall's session has
+						been initiated. */
+
+	void (*progress_cb)(MsnSlpCall *slpcall,
+						gsize total_length, gsize len, gsize offset);
+	void (*session_init_cb)(MsnSlpSession *slpsession);
+
+	/* Can be checksum, or smile */
+	char *data_info;
+
+	void *xfer;
+
+	MsnSlpCb cb;
+	void (*end_cb)(MsnSlpCall *slpcall, MsnSession *session);
+
+	int timer;
+};
+
+MsnSlpCall *msn_slp_call_new(MsnSlpLink *slplink);
+void msn_slp_call_init(MsnSlpCall *slpcall, MsnSlpCallType type);
+void msn_slp_call_session_init(MsnSlpCall *slpcall);
+void msn_slp_call_destroy(MsnSlpCall *slpcall);
+void msn_slp_call_invite(MsnSlpCall *slpcall, const char *euf_guid,
+						 int app_id, const char *context);
+void msn_slp_call_close(MsnSlpCall *slpcall);
+gboolean msn_slp_call_timeout(gpointer data);
+
+#endif /* _MSN_SLPCALL_H_ */
============================================================
--- libpurple/protocols/msnp9/slplink.c	8d60b4b4cc819be569c7fcabeac71e820226b75a
+++ libpurple/protocols/msnp9/slplink.c	8d60b4b4cc819be569c7fcabeac71e820226b75a
@@ -0,0 +1,811 @@
+/**
+ * @file slplink.c MSNSLP Link support
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "slplink.h"
+
+#include "switchboard.h"
+#include "slp.h"
+
+void msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg);
+
+#ifdef MSN_DEBUG_SLP_FILES
+static int m_sc = 0;
+static int m_rc = 0;
+
+static void
+debug_msg_to_file(MsnMessage *msg, gboolean send)
+{
+	char *tmp;
+	char *dir;
+	char *pload;
+	FILE *tf;
+	int c;
+	gsize pload_size;
+
+	dir = send ? "send" : "recv";
+	c = send ? m_sc++ : m_rc++;
+	tmp = g_strdup_printf("%s/msntest/%s/%03d", g_get_home_dir(), dir, c);
+	tf = g_fopen(tmp, "wb");
+	if (tf == NULL)
+	{
+		purple_debug_error("msn", "could not open debug file\n");
+		return;
+	}
+	pload = msn_message_gen_payload(msg, &pload_size);
+	fwrite(pload, 1, pload_size, tf);
+	fclose(tf);
+	g_free(tmp);
+}
+#endif
+
+/**************************************************************************
+ * Main
+ **************************************************************************/
+
+MsnSlpLink *
+msn_slplink_new(MsnSession *session, const char *username)
+{
+	MsnSlpLink *slplink;
+
+	g_return_val_if_fail(session != NULL, NULL);
+
+	slplink = g_new0(MsnSlpLink, 1);
+
+#ifdef MSN_DEBUG_SLPLINK
+	purple_debug_info("msn", "slplink_new: slplink(%p)\n", slplink);
+#endif
+
+	slplink->session = session;
+	slplink->slp_seq_id = rand() % 0xFFFFFF00 + 4;
+
+	slplink->local_user = g_strdup(msn_user_get_passport(session->user));
+	slplink->remote_user = g_strdup(username);
+
+	slplink->slp_msg_queue = g_queue_new();
+
+	session->slplinks =
+		g_list_append(session->slplinks, slplink);
+
+	return slplink;
+}
+
+void
+msn_slplink_destroy(MsnSlpLink *slplink)
+{
+	MsnSession *session;
+
+#ifdef MSN_DEBUG_SLPLINK
+	purple_debug_info("msn", "slplink_destroy: slplink(%p)\n", slplink);
+#endif
+
+	g_return_if_fail(slplink != NULL);
+
+	if (slplink->swboard != NULL)
+		slplink->swboard->slplinks = g_list_remove(slplink->swboard->slplinks, slplink);
+
+	session = slplink->session;
+
+	if (slplink->local_user != NULL)
+		g_free(slplink->local_user);
+
+	if (slplink->remote_user != NULL)
+		g_free(slplink->remote_user);
+
+	if (slplink->directconn != NULL)
+		msn_directconn_destroy(slplink->directconn);
+
+	while (slplink->slp_calls != NULL)
+		msn_slp_call_destroy(slplink->slp_calls->data);
+
+	session->slplinks =
+		g_list_remove(session->slplinks, slplink);
+
+	g_free(slplink);
+}
+
+MsnSlpLink *
+msn_session_find_slplink(MsnSession *session, const char *who)
+{
+	GList *l;
+
+	for (l = session->slplinks; l != NULL; l = l->next)
+	{
+		MsnSlpLink *slplink;
+
+		slplink = l->data;
+
+		if (!strcmp(slplink->remote_user, who))
+			return slplink;
+	}
+
+	return NULL;
+}
+
+MsnSlpLink *
+msn_session_get_slplink(MsnSession *session, const char *username)
+{
+	MsnSlpLink *slplink;
+
+	g_return_val_if_fail(session != NULL, NULL);
+	g_return_val_if_fail(username != NULL, NULL);
+
+	slplink = msn_session_find_slplink(session, username);
+
+	if (slplink == NULL)
+		slplink = msn_slplink_new(session, username);
+
+	return slplink;
+}
+
+MsnSlpSession *
+msn_slplink_find_slp_session(MsnSlpLink *slplink, long session_id)
+{
+	GList *l;
+	MsnSlpSession *slpsession;
+
+	for (l = slplink->slp_sessions; l != NULL; l = l->next)
+	{
+		slpsession = l->data;
+
+		if (slpsession->id == session_id)
+			return slpsession;
+	}
+
+	return NULL;
+}
+
+void
+msn_slplink_add_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall)
+{
+	if (slplink->swboard != NULL)
+		slplink->swboard->flag |= MSN_SB_FLAG_FT;
+
+	slplink->slp_calls = g_list_append(slplink->slp_calls, slpcall);
+}
+
+void
+msn_slplink_remove_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall)
+{
+	slplink->slp_calls = g_list_remove(slplink->slp_calls, slpcall);
+
+	/* The slplink has no slpcalls in it. If no one is using it, we might
+	 * destroy the switchboard, but we should be careful not to use the slplink
+	 * again. */
+	if (slplink->slp_calls == NULL)
+	{
+		if (slplink->swboard != NULL)
+		{
+			if (msn_switchboard_release(slplink->swboard, MSN_SB_FLAG_FT))
+				/* I'm not sure this is the best thing to do, but it's better
+				 * than nothing. */
+				slpcall->slplink = NULL;
+		}
+	}
+}
+
+MsnSlpCall *
+msn_slplink_find_slp_call(MsnSlpLink *slplink, const char *id)
+{
+	GList *l;
+	MsnSlpCall *slpcall;
+
+	if (!id)
+		return NULL;
+
+	for (l = slplink->slp_calls; l != NULL; l = l->next)
+	{
+		slpcall = l->data;
+
+		if (slpcall->id && !strcmp(slpcall->id, id))
+			return slpcall;
+	}
+
+	return NULL;
+}
+
+MsnSlpCall *
+msn_slplink_find_slp_call_with_session_id(MsnSlpLink *slplink, long id)
+{
+	GList *l;
+	MsnSlpCall *slpcall;
+
+	for (l = slplink->slp_calls; l != NULL; l = l->next)
+	{
+		slpcall = l->data;
+
+		if (slpcall->session_id == id)
+			return slpcall;
+	}
+
+	return NULL;
+}
+
+void
+msn_slplink_send_msg(MsnSlpLink *slplink, MsnMessage *msg)
+{
+	if (slplink->directconn != NULL)
+	{
+		msn_directconn_send_msg(slplink->directconn, msg);
+	}
+	else
+	{
+		if (slplink->swboard == NULL)
+		{
+			slplink->swboard = msn_session_get_swboard(slplink->session,
+													   slplink->remote_user, MSN_SB_FLAG_FT);
+
+			if (slplink->swboard == NULL)
+				return;
+
+			/* If swboard is destroyed we will be too */
+			slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink);
+		}
+
+		msn_switchboard_send_msg(slplink->swboard, msg, TRUE);
+	}
+}
+
+/* We have received the message ack */
+static void
+msg_ack(MsnMessage *msg, void *data)
+{
+	MsnSlpMessage *slpmsg;
+	long long real_size;
+
+	slpmsg = data;
+
+	real_size = (slpmsg->flags == 0x2) ? 0 : slpmsg->size;
+
+	slpmsg->offset += msg->msnslp_header.length;
+
+	if (slpmsg->offset < real_size)
+	{
+		msn_slplink_send_msgpart(slpmsg->slplink, slpmsg);
+	}
+	else
+	{
+		/* The whole message has been sent */
+		if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030)
+		{
+			if (slpmsg->slpcall != NULL)
+			{
+				if (slpmsg->slpcall->cb)
+					slpmsg->slpcall->cb(slpmsg->slpcall,
+						NULL, 0);
+			}
+		}
+	}
+
+	slpmsg->msgs = g_list_remove(slpmsg->msgs, msg);
+}
+
+/* We have received the message nak. */
+static void
+msg_nak(MsnMessage *msg, void *data)
+{
+	MsnSlpMessage *slpmsg;
+
+	slpmsg = data;
+
+	msn_slplink_send_msgpart(slpmsg->slplink, slpmsg);
+
+	slpmsg->msgs = g_list_remove(slpmsg->msgs, msg);
+}
+
+void
+msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg)
+{
+	MsnMessage *msg;
+	long long real_size;
+	size_t len = 0;
+
+	/* Maybe we will want to create a new msg for this slpmsg instead of
+	 * reusing the same one all the time. */
+	msg = slpmsg->msg;
+
+	real_size = (slpmsg->flags == 0x2) ? 0 : slpmsg->size;
+
+	if (slpmsg->offset < real_size)
+	{
+		if (slpmsg->fp)
+		{
+			char data[1202];
+			len = fread(data, 1, sizeof(data), slpmsg->fp);
+			msn_message_set_bin_data(msg, data, len);
+		}
+		else
+		{
+			len = slpmsg->size - slpmsg->offset;
+
+			if (len > 1202)
+				len = 1202;
+
+			msn_message_set_bin_data(msg, slpmsg->buffer + slpmsg->offset, len);
+		}
+
+		msg->msnslp_header.offset = slpmsg->offset;
+		msg->msnslp_header.length = len;
+	}
+
+#ifdef MSN_DEBUG_SLP
+	msn_message_show_readable(msg, slpmsg->info, slpmsg->text_body);
+#endif
+
+#ifdef MSN_DEBUG_SLP_FILES
+	debug_msg_to_file(msg, TRUE);
+#endif
+
+	slpmsg->msgs =
+		g_list_append(slpmsg->msgs, msg);
+	msn_slplink_send_msg(slplink, msg);
+
+	if ((slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030) &&
+		(slpmsg->slpcall != NULL))
+	{
+		slpmsg->slpcall->progress = TRUE;
+
+		if (slpmsg->slpcall->progress_cb != NULL)
+		{
+			slpmsg->slpcall->progress_cb(slpmsg->slpcall, slpmsg->size,
+										 len, slpmsg->offset);
+		}
+	}
+
+	/* slpmsg->offset += len; */
+}
+
+void
+msn_slplink_release_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg)
+{
+	MsnMessage *msg;
+
+	slpmsg->msg = msg = msn_message_new_msnslp();
+
+	if (slpmsg->flags == 0x0)
+	{
+		msg->msnslp_header.session_id = slpmsg->session_id;
+		msg->msnslp_header.ack_id = rand() % 0xFFFFFF00;
+	}
+	else if (slpmsg->flags == 0x2)
+	{
+		msg->msnslp_header.session_id = slpmsg->session_id;
+		msg->msnslp_header.ack_id = slpmsg->ack_id;
+		msg->msnslp_header.ack_size = slpmsg->ack_size;
+		msg->msnslp_header.ack_sub_id = slpmsg->ack_sub_id;
+	}
+	else if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030)
+	{
+		MsnSlpSession *slpsession;
+		slpsession = slpmsg->slpsession;
+
+		g_return_if_fail(slpsession != NULL);
+		msg->msnslp_header.session_id = slpsession->id;
+		msg->msnslp_footer.value = slpsession->app_id;
+		msg->msnslp_header.ack_id = rand() % 0xFFFFFF00;
+	}
+	else if (slpmsg->flags == 0x100)
+	{
+		msg->msnslp_header.ack_id     = slpmsg->ack_id;
+		msg->msnslp_header.ack_sub_id = slpmsg->ack_sub_id;
+		msg->msnslp_header.ack_size   = slpmsg->ack_size;
+	}
+
+	msg->msnslp_header.id = slpmsg->id;
+	msg->msnslp_header.flags = slpmsg->flags;
+
+	msg->msnslp_header.total_size = slpmsg->size;
+
+	msn_message_set_attr(msg, "P2P-Dest", slplink->remote_user);
+
+	msg->ack_cb = msg_ack;
+	msg->nak_cb = msg_nak;
+	msg->ack_data = slpmsg;
+
+	msn_slplink_send_msgpart(slplink, slpmsg);
+
+	msn_message_destroy(msg);
+}
+
+void
+msn_slplink_queue_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg)
+{
+	slpmsg->id = slplink->slp_seq_id++;
+
+	g_queue_push_head(slplink->slp_msg_queue, slpmsg);
+}
+
+void
+msn_slplink_send_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg)
+{
+	slpmsg->id = slplink->slp_seq_id++;
+
+	msn_slplink_release_slpmsg(slplink, slpmsg);
+}
+
+void
+msn_slplink_unleash(MsnSlpLink *slplink)
+{
+	MsnSlpMessage *slpmsg;
+
+	/* Send the queued msgs in the order they came. */
+
+	while ((slpmsg = g_queue_pop_tail(slplink->slp_msg_queue)) != NULL)
+	{
+		msn_slplink_release_slpmsg(slplink, slpmsg);
+	}
+}
+
+void
+msn_slplink_send_ack(MsnSlpLink *slplink, MsnMessage *msg)
+{
+	MsnSlpMessage *slpmsg;
+
+	slpmsg = msn_slpmsg_new(slplink);
+
+	slpmsg->session_id = msg->msnslp_header.session_id;
+	slpmsg->size       = msg->msnslp_header.total_size;
+	slpmsg->flags      = 0x02;
+	slpmsg->ack_id     = msg->msnslp_header.id;
+	slpmsg->ack_sub_id = msg->msnslp_header.ack_id;
+	slpmsg->ack_size   = msg->msnslp_header.total_size;
+
+#ifdef MSN_DEBUG_SLP
+	slpmsg->info = "SLP ACK";
+#endif
+
+	msn_slplink_send_slpmsg(slplink, slpmsg);
+}
+
+static void
+send_file_cb(MsnSlpSession *slpsession)
+{
+	MsnSlpCall *slpcall;
+	MsnSlpMessage *slpmsg;
+	struct stat st;
+	PurpleXfer *xfer;
+
+	slpcall = slpsession->slpcall;
+	slpmsg = msn_slpmsg_new(slpcall->slplink);
+	slpmsg->slpcall = slpcall;
+	slpmsg->flags = 0x1000030;
+	slpmsg->slpsession = slpsession;
+#ifdef MSN_DEBUG_SLP
+	slpmsg->info = "SLP FILE";
+#endif
+	xfer = (PurpleXfer *)slpcall->xfer;
+	purple_xfer_start(slpcall->xfer, 0, NULL, 0);
+	slpmsg->fp = xfer->dest_fp;
+	if (g_stat(purple_xfer_get_local_filename(xfer), &st) == 0)
+		slpmsg->size = st.st_size;
+	xfer->dest_fp = NULL; /* Disable double fclose() */
+
+	msn_slplink_send_slpmsg(slpcall->slplink, slpmsg);
+}
+
+void
+msn_slplink_process_msg(MsnSlpLink *slplink, MsnMessage *msg)
+{
+	MsnSlpMessage *slpmsg;
+	const char *data;
+	gsize offset;
+	gsize len;
+
+#ifdef MSN_DEBUG_SLP
+	msn_slpmsg_show(msg);
+#endif
+
+#ifdef MSN_DEBUG_SLP_FILES
+	debug_msg_to_file(msg, FALSE);
+#endif
+
+	if (msg->msnslp_header.total_size < msg->msnslp_header.length)
+	{
+		purple_debug_error("msn", "This can't be good\n");
+		g_return_if_reached();
+	}
+
+	slpmsg = NULL;
+	data = msn_message_get_bin_data(msg, &len);
+
+	/*
+		OVERHEAD!
+		if (msg->msnslp_header.length < msg->msnslp_header.total_size)
+	 */
+
+	offset = msg->msnslp_header.offset;
+
+	if (offset == 0)
+	{
+		slpmsg = msn_slpmsg_new(slplink);
+		slpmsg->id = msg->msnslp_header.id;
+		slpmsg->session_id = msg->msnslp_header.session_id;
+		slpmsg->size = msg->msnslp_header.total_size;
+		slpmsg->flags = msg->msnslp_header.flags;
+
+		if (slpmsg->session_id)
+		{
+			if (slpmsg->slpcall == NULL)
+				slpmsg->slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->session_id);
+
+			if (slpmsg->slpcall != NULL)
+			{
+				if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030)
+				{
+					PurpleXfer *xfer;
+
+					xfer = slpmsg->slpcall->xfer;
+
+					if (xfer != NULL)
+					{
+						purple_xfer_start(slpmsg->slpcall->xfer,
+							0, NULL, 0);
+						slpmsg->fp = ((PurpleXfer *)slpmsg->slpcall->xfer)->dest_fp;
+						xfer->dest_fp = NULL; /* Disable double fclose() */
+					}
+				}
+			}
+		}
+		if (!slpmsg->fp && slpmsg->size)
+		{
+			slpmsg->buffer = g_try_malloc(slpmsg->size);
+			if (slpmsg->buffer == NULL)
+			{
+				purple_debug_error("msn", "Failed to allocate buffer for slpmsg\n");
+				return;
+			}
+		}
+	}
+	else
+	{
+		slpmsg = msn_slplink_message_find(slplink, msg->msnslp_header.session_id, msg->msnslp_header.id);
+	}
+
+	if (slpmsg == NULL)
+	{
+		/* Probably the transfer was canceled */
+		purple_debug_error("msn", "Couldn't find slpmsg\n");
+		return;
+	}
+
+	if (slpmsg->fp)
+	{
+		/* fseek(slpmsg->fp, offset, SEEK_SET); */
+		len = fwrite(data, 1, len, slpmsg->fp);
+	}
+	else if (slpmsg->size)
+	{
+		if ((offset + len) > slpmsg->size)
+		{
+			purple_debug_error("msn", "Oversized slpmsg\n");
+			g_return_if_reached();
+		}
+		else
+			memcpy(slpmsg->buffer + offset, data, len);
+	}
+
+	if ((slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030) &&
+		(slpmsg->slpcall != NULL))
+	{
+		slpmsg->slpcall->progress = TRUE;
+
+		if (slpmsg->slpcall->progress_cb != NULL)
+		{
+			slpmsg->slpcall->progress_cb(slpmsg->slpcall, slpmsg->size,
+										 len, offset);
+		}
+	}
+
+#if 0
+	if (slpmsg->buffer == NULL)
+		return;
+#endif
+
+	if (msg->msnslp_header.offset + msg->msnslp_header.length
+		>= msg->msnslp_header.total_size)
+	{
+		/* All the pieces of the slpmsg have been received */
+		MsnSlpCall *slpcall;
+
+		slpcall = msn_slp_process_msg(slplink, slpmsg);
+
+		if (slpmsg->flags == 0x100)
+		{
+			MsnDirectConn *directconn;
+
+			directconn = slplink->directconn;
+
+			if (!directconn->acked)
+				msn_directconn_send_handshake(directconn);
+		}
+		else if (slpmsg->flags == 0x0 || slpmsg->flags == 0x20 ||
+				 slpmsg->flags == 0x1000030)
+		{
+			/* Release all the messages and send the ACK */
+
+			msn_slplink_send_ack(slplink, msg);
+			msn_slplink_unleash(slplink);
+		}
+
+		msn_slpmsg_destroy(slpmsg);
+
+		if (slpcall != NULL && slpcall->wasted)
+			msn_slp_call_destroy(slpcall);
+	}
+}
+
+MsnSlpMessage *
+msn_slplink_message_find(MsnSlpLink *slplink, long session_id, long id)
+{
+	GList *e;
+
+	for (e = slplink->slp_msgs; e != NULL; e = e->next)
+	{
+		MsnSlpMessage *slpmsg = e->data;
+
+		if ((slpmsg->session_id == session_id) && (slpmsg->id == id))
+			return slpmsg;
+	}
+
+	return NULL;
+}
+
+typedef struct
+{
+	guint32 length;
+	guint32 unk1;
+	guint32 file_size;
+	guint32 unk2;
+	guint32 unk3;
+} MsnContextHeader;
+
+#define MAX_FILE_NAME_LEN 0x226
+
+static gchar *
+gen_context(const char *file_name, const char *file_path)
+{
+	struct stat st;
+	gsize size = 0;
+	MsnContextHeader header;
+	gchar *u8 = NULL;
+	guchar *base;
+	guchar *n;
+	gchar *ret;
+	gunichar2 *uni = NULL;
+	glong currentChar = 0;
+	glong uni_len = 0;
+	gsize len;
+
+	if (g_stat(file_path, &st) == 0)
+		size = st.st_size;
+
+	if(!file_name) {
+		u8 = purple_utf8_try_convert(g_basename(file_path));
+		file_name = u8;
+	}
+
+	uni = g_utf8_to_utf16(file_name, -1, NULL, &uni_len, NULL);
+
+	if(u8) {
+		g_free(u8);
+		file_name = NULL;
+		u8 = NULL;
+	}
+
+	len = sizeof(MsnContextHeader) + MAX_FILE_NAME_LEN + 4;
+
+	header.length = GUINT32_TO_LE(len);
+	header.unk1 = GUINT32_TO_LE(2);
+	header.file_size = GUINT32_TO_LE(size);
+	header.unk2 = GUINT32_TO_LE(0);
+	header.unk3 = GUINT32_TO_LE(0);
+
+	base = g_malloc(len + 1);
+	n = base;
+
+	memcpy(n, &header, sizeof(MsnContextHeader));
+	n += sizeof(MsnContextHeader);
+
+	memset(n, 0x00, MAX_FILE_NAME_LEN);
+	for(currentChar = 0; currentChar < uni_len; currentChar++) {
+		*((gunichar2 *)n + currentChar) = GUINT16_TO_LE(uni[currentChar]);
+	}
+	n += MAX_FILE_NAME_LEN;
+
+	memset(n, 0xFF, 4);
+	n += 4;
+
+	g_free(uni);
+	ret = purple_base64_encode(base, len);
+	g_free(base);
+	return ret;
+}
+
+void
+msn_slplink_request_ft(MsnSlpLink *slplink, PurpleXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+	char *context;
+	const char *fn;
+	const char *fp;
+
+	fn = purple_xfer_get_filename(xfer);
+	fp = purple_xfer_get_local_filename(xfer);
+
+	g_return_if_fail(slplink != NULL);
+	g_return_if_fail(fp != NULL);
+
+	slpcall = msn_slp_call_new(slplink);
+	msn_slp_call_init(slpcall, MSN_SLPCALL_DC);
+
+	slpcall->session_init_cb = send_file_cb;
+	slpcall->end_cb = msn_xfer_end_cb;
+	slpcall->progress_cb = msn_xfer_progress_cb;
+	slpcall->cb = msn_xfer_completed_cb;
+	slpcall->xfer = xfer;
+	purple_xfer_ref(slpcall->xfer);
+
+	slpcall->pending = TRUE;
+
+	purple_xfer_set_cancel_send_fnc(xfer, msn_xfer_cancel);
+
+	xfer->data = slpcall;
+
+	context = gen_context(fn, fp);
+
+	msn_slp_call_invite(slpcall, "5D3E02AB-6190-11D3-BBBB-00C04F795683", 2,
+						context);
+
+	g_free(context);
+}
+
+void
+msn_slplink_request_object(MsnSlpLink *slplink,
+						   const char *info,
+						   MsnSlpCb cb,
+						   MsnSlpEndCb end_cb,
+						   const MsnObject *obj)
+{
+	MsnSlpCall *slpcall;
+	char *msnobj_data;
+	char *msnobj_base64;
+
+	g_return_if_fail(slplink != NULL);
+	g_return_if_fail(obj     != NULL);
+
+	msnobj_data = msn_object_to_string(obj);
+	msnobj_base64 = purple_base64_encode((const guchar *)msnobj_data, strlen(msnobj_data));
+	g_free(msnobj_data);
+
+	slpcall = msn_slp_call_new(slplink);
+	msn_slp_call_init(slpcall, MSN_SLPCALL_ANY);
+
+	slpcall->data_info = g_strdup(info);
+	slpcall->cb = cb;
+	slpcall->end_cb = end_cb;
+
+	msn_slp_call_invite(slpcall, "A4268EEC-FEC5-49E5-95C3-F126696BDBF6", 1,
+						msnobj_base64);
+
+	g_free(msnobj_base64);
+}
============================================================
--- libpurple/protocols/msnp9/slplink.h	d8756cf2f68f983800d3822e83b52b0c6d0d0613
+++ libpurple/protocols/msnp9/slplink.h	d8756cf2f68f983800d3822e83b52b0c6d0d0613
@@ -0,0 +1,97 @@
+/**
+ * @file slplink.h MSNSLP Link support
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_SLPLINK_H_
+#define _MSN_SLPLINK_H_
+
+typedef struct _MsnSlpLink MsnSlpLink;
+
+#include "directconn.h"
+#include "slpcall.h"
+#include "slpmsg.h"
+
+#include "switchboard.h"
+
+#include "ft.h"
+
+#include "session.h"
+
+typedef void (*MsnSlpCb)(MsnSlpCall *slpcall,
+						 const guchar *data, gsize size);
+typedef void (*MsnSlpEndCb)(MsnSlpCall *slpcall, MsnSession *session);
+
+struct _MsnSlpLink
+{
+	MsnSession *session;
+	MsnSwitchBoard *swboard;
+
+	char *local_user;
+	char *remote_user;
+
+	int slp_seq_id;
+
+	MsnDirectConn *directconn;
+
+	GList *slp_calls;
+	GList *slp_sessions;
+	GList *slp_msgs;
+
+	GQueue *slp_msg_queue;
+};
+
+MsnSlpLink *msn_slplink_new(MsnSession *session, const char *username);
+void msn_slplink_destroy(MsnSlpLink *slplink);
+MsnSlpLink *msn_session_find_slplink(MsnSession *session,
+									 const char *who);
+MsnSlpLink *msn_session_get_slplink(MsnSession *session, const char *username);
+MsnSlpSession *msn_slplink_find_slp_session(MsnSlpLink *slplink,
+											long session_id);
+void msn_slplink_add_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall);
+void msn_slplink_remove_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall);
+MsnSlpCall *msn_slplink_find_slp_call(MsnSlpLink *slplink,
+									  const char *id);
+MsnSlpCall *msn_slplink_find_slp_call_with_session_id(MsnSlpLink *slplink, long id);
+void msn_slplink_send_msg(MsnSlpLink *slplink, MsnMessage *msg);
+void msn_slplink_release_slpmsg(MsnSlpLink *slplink,
+								MsnSlpMessage *slpmsg);
+void msn_slplink_queue_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg);
+void msn_slplink_send_slpmsg(MsnSlpLink *slplink,
+							 MsnSlpMessage *slpmsg);
+void msn_slplink_unleash(MsnSlpLink *slplink);
+void msn_slplink_send_ack(MsnSlpLink *slplink, MsnMessage *msg);
+void msn_slplink_process_msg(MsnSlpLink *slplink, MsnMessage *msg);
+MsnSlpMessage *msn_slplink_message_find(MsnSlpLink *slplink, long session_id, long id);
+void msn_slplink_append_slp_msg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg);
+void msn_slplink_remove_slp_msg(MsnSlpLink *slplink,
+								MsnSlpMessage *slpmsg);
+void msn_slplink_request_ft(MsnSlpLink *slplink, PurpleXfer *xfer);
+
+void msn_slplink_request_object(MsnSlpLink *slplink,
+								const char *info,
+								MsnSlpCb cb,
+								MsnSlpEndCb end_cb,
+								const MsnObject *obj);
+
+MsnSlpCall *msn_slp_process_msg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg);
+
+#endif /* _MSN_SLPLINK_H_ */
============================================================
--- libpurple/protocols/msnp9/slpmsg.c	2bb5642de9d0eba1cbc2938063a2b85d49faff30
+++ libpurple/protocols/msnp9/slpmsg.c	2bb5642de9d0eba1cbc2938063a2b85d49faff30
@@ -0,0 +1,239 @@
+/**
+ * @file slpmsg.h SLP Message functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "slpmsg.h"
+#include "slplink.h"
+
+/**************************************************************************
+ * SLP Message
+ **************************************************************************/
+
+MsnSlpMessage *
+msn_slpmsg_new(MsnSlpLink *slplink)
+{
+	MsnSlpMessage *slpmsg;
+
+	slpmsg = g_new0(MsnSlpMessage, 1);
+
+#ifdef MSN_DEBUG_SLPMSG
+	purple_debug_info("msn", "slpmsg new (%p)\n", slpmsg);
+#endif
+
+	slpmsg->slplink = slplink;
+
+	slplink->slp_msgs =
+		g_list_append(slplink->slp_msgs, slpmsg);
+
+	return slpmsg;
+}
+
+void
+msn_slpmsg_destroy(MsnSlpMessage *slpmsg)
+{
+	MsnSlpLink *slplink;
+	GList *cur;
+
+	g_return_if_fail(slpmsg != NULL);
+
+#ifdef MSN_DEBUG_SLPMSG
+	purple_debug_info("msn", "slpmsg destroy (%p)\n", slpmsg);
+#endif
+
+	slplink = slpmsg->slplink;
+
+	if (slpmsg->fp != NULL)
+		fclose(slpmsg->fp);
+
+	purple_imgstore_unref(slpmsg->img);
+
+	/* We don't want to free the data of the PurpleStoredImage,
+	 * but to avoid code duplication, it's sharing buffer. */
+	if (slpmsg->img == NULL)
+		g_free(slpmsg->buffer);
+
+#ifdef MSN_DEBUG_SLP
+	/*
+	if (slpmsg->info != NULL)
+		g_free(slpmsg->info);
+	*/
+#endif
+
+	for (cur = slpmsg->msgs; cur != NULL; cur = cur->next)
+	{
+		/* Something is pointing to this slpmsg, so we should remove that
+		 * pointer to prevent a crash. */
+		/* Ex: a user goes offline and after that we receive an ACK */
+
+		MsnMessage *msg = cur->data;
+
+#ifdef MSN_DEBUG_SLPMSG
+		purple_debug_info("msn", "Unlink slpmsg callbacks.\n");
+#endif
+
+		msg->ack_cb = NULL;
+		msg->nak_cb = NULL;
+		msg->ack_data = NULL;
+	}
+
+	slplink->slp_msgs = g_list_remove(slplink->slp_msgs, slpmsg);
+
+	g_free(slpmsg);
+}
+
+void
+msn_slpmsg_set_body(MsnSlpMessage *slpmsg, const char *body,
+						 long long size)
+{
+	/* We can only have one data source at a time. */
+	g_return_if_fail(slpmsg->buffer == NULL);
+	g_return_if_fail(slpmsg->img == NULL);
+	g_return_if_fail(slpmsg->fp == NULL);
+
+	if (body != NULL)
+		slpmsg->buffer = g_memdup(body, size);
+	else
+		slpmsg->buffer = g_new0(guchar, size);
+
+	slpmsg->size = size;
+}
+
+void
+msn_slpmsg_set_image(MsnSlpMessage *slpmsg, PurpleStoredImage *img)
+{
+	/* We can only have one data source at a time. */
+	g_return_if_fail(slpmsg->buffer == NULL);
+	g_return_if_fail(slpmsg->img == NULL);
+	g_return_if_fail(slpmsg->fp == NULL);
+
+	slpmsg->img = purple_imgstore_ref(img);
+	slpmsg->buffer = (guchar *)purple_imgstore_get_data(img);
+	slpmsg->size = purple_imgstore_get_size(img);
+}
+
+void
+msn_slpmsg_open_file(MsnSlpMessage *slpmsg, const char *file_name)
+{
+	struct stat st;
+
+	/* We can only have one data source at a time. */
+	g_return_if_fail(slpmsg->buffer == NULL);
+	g_return_if_fail(slpmsg->img == NULL);
+	g_return_if_fail(slpmsg->fp == NULL);
+
+	slpmsg->fp = g_fopen(file_name, "rb");
+
+	if (g_stat(file_name, &st) == 0)
+		slpmsg->size = st.st_size;
+}
+
+#ifdef MSN_DEBUG_SLP
+void
+msn_slpmsg_show(MsnMessage *msg)
+{
+	const char *info;
+	gboolean text;
+	guint32 flags;
+
+	text = FALSE;
+
+	flags = GUINT32_TO_LE(msg->msnslp_header.flags);
+
+	switch (flags)
+	{
+		case 0x0:
+			info = "SLP CONTROL";
+			text = TRUE;
+			break;
+		case 0x2:
+			info = "SLP ACK"; break;
+		case 0x20:
+		case 0x1000030:
+			info = "SLP DATA"; break;
+		default:
+			info = "SLP UNKNOWN"; break;
+	}
+
+	msn_message_show_readable(msg, info, text);
+}
+#endif
+
+MsnSlpMessage *
+msn_slpmsg_sip_new(MsnSlpCall *slpcall, int cseq,
+				   const char *header, const char *branch,
+				   const char *content_type, const char *content)
+{
+	MsnSlpLink *slplink;
+	MsnSlpMessage *slpmsg;
+	char *body;
+	gsize body_len;
+	gsize content_len;
+
+	g_return_val_if_fail(slpcall != NULL, NULL);
+	g_return_val_if_fail(header  != NULL, NULL);
+
+	slplink = slpcall->slplink;
+
+	/* Let's remember that "content" should end with a 0x00 */
+
+	content_len = (content != NULL) ? strlen(content) + 1 : 0;
+
+	body = g_strdup_printf(
+		"%s\r\n"
+		"To: <msnmsgr:%s>\r\n"
+		"From: <msnmsgr:%s>\r\n"
+		"Via: MSNSLP/1.0/TLP ;branch={%s}\r\n"
+		"CSeq: %d\r\n"
+		"Call-ID: {%s}\r\n"
+		"Max-Forwards: 0\r\n"
+		"Content-Type: %s\r\n"
+		"Content-Length: %" G_GSIZE_FORMAT "\r\n"
+		"\r\n",
+		header,
+		slplink->remote_user,
+		slplink->local_user,
+		branch,
+		cseq,
+		slpcall->id,
+		content_type,
+		content_len);
+
+	body_len = strlen(body);
+
+	if (content_len > 0)
+	{
+		body_len += content_len;
+		body = g_realloc(body, body_len);
+		g_strlcat(body, content, body_len);
+	}
+
+	slpmsg = msn_slpmsg_new(slplink);
+	msn_slpmsg_set_body(slpmsg, body, body_len);
+
+	slpmsg->sip = TRUE;
+	slpmsg->slpcall = slpcall;
+
+	g_free(body);
+
+	return slpmsg;
+}
============================================================
--- libpurple/protocols/msnp9/slpmsg.h	2348f7f31881bd0d508206bcbd1e5747a45a08ba
+++ libpurple/protocols/msnp9/slpmsg.h	2348f7f31881bd0d508206bcbd1e5747a45a08ba
@@ -0,0 +1,109 @@
+/**
+ * @file slpmsg.h SLP Message functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_SLPMSG_H_
+#define _MSN_SLPMSG_H_
+
+typedef struct _MsnSlpMessage MsnSlpMessage;
+
+#include "imgstore.h"
+
+#include "slpsession.h"
+#include "slpcall.h"
+#include "slplink.h"
+#include "session.h"
+#include "msg.h"
+
+#include "slp.h"
+
+/**
+ * A SLP Message  This contains everything that we will need to send a SLP
+ * Message even if has to be sent in several parts.
+ */
+struct _MsnSlpMessage
+{
+	MsnSlpSession *slpsession;
+	MsnSlpCall *slpcall; /**< The slpcall to which this slp message belongs (if applicable). */
+	MsnSlpLink *slplink; /**< The slplink through which this slp message is being sent. */
+	MsnSession *session;
+
+	long session_id;
+	long id;
+	long ack_id;
+	long ack_sub_id;
+	long long ack_size;
+	long app_id;
+
+	gboolean sip; /**< A flag that states if this is a SIP slp message. */
+	int ref_count; /**< The reference count. */
+	long flags;
+
+	FILE *fp;
+	PurpleStoredImage *img;
+	guchar *buffer;
+	long long offset;
+	long long size;
+
+	GList *msgs; /**< The real messages. */
+
+#if 1
+	MsnMessage *msg; /**< The temporary real message that will be sent. */
+#endif
+
+#ifdef MSN_DEBUG_SLP
+	char *info;
+	gboolean text_body;
+#endif
+};
+
+/**
+ * Creates a new slp message
+ *
+ * @param slplink The slplink through which this slp message will be sent.
+ * @return The created slp message.
+ */
+MsnSlpMessage *msn_slpmsg_new(MsnSlpLink *slplink);
+
+/**
+ * Destroys a slp message
+ *
+ * @param slpmsg The slp message to destory.
+ */
+void msn_slpmsg_destroy(MsnSlpMessage *slpmsg);
+
+void msn_slpmsg_set_body(MsnSlpMessage *slpmsg, const char *body,
+						 long long size);
+void msn_slpmsg_set_image(MsnSlpMessage *slpmsg, PurpleStoredImage *img);
+void msn_slpmsg_open_file(MsnSlpMessage *slpmsg,
+						  const char *file_name);
+MsnSlpMessage * msn_slpmsg_sip_new(MsnSlpCall *slpcall, int cseq,
+								   const char *header,
+								   const char *branch,
+								   const char *content_type,
+								   const char *content);
+
+#ifdef MSN_DEBUG_SLP
+void msn_slpmsg_show(MsnMessage *msg);
+#endif
+
+#endif /* _MSN_SLPMSG_H_ */
============================================================
--- libpurple/protocols/msnp9/slpsession.c	c9755afff147cb825e0271a98d875825785f3b26
+++ libpurple/protocols/msnp9/slpsession.c	c9755afff147cb825e0271a98d875825785f3b26
@@ -0,0 +1,77 @@
+/**
+ * @file slpsession.h SLP Session functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "slpsession.h"
+
+/**************************************************************************
+ * SLP Session
+ **************************************************************************/
+
+MsnSlpSession *
+msn_slp_session_new(MsnSlpCall *slpcall)
+{
+	MsnSlpSession *slpsession;
+
+	g_return_val_if_fail(slpcall != NULL, NULL);
+
+	slpsession = g_new0(MsnSlpSession, 1);
+
+	slpsession->slpcall = slpcall;
+	slpsession->id = slpcall->session_id;
+	slpsession->call_id = slpcall->id;
+	slpsession->app_id = slpcall->app_id;
+
+	slpcall->slplink->slp_sessions =
+		g_list_append(slpcall->slplink->slp_sessions, slpsession);
+
+	return slpsession;
+}
+
+void
+msn_slp_session_destroy(MsnSlpSession *slpsession)
+{
+	g_return_if_fail(slpsession != NULL);
+
+	if (slpsession->call_id != NULL)
+		g_free(slpsession->call_id);
+
+	slpsession->slpcall->slplink->slp_sessions =
+		g_list_remove(slpsession->slpcall->slplink->slp_sessions, slpsession);
+
+	g_free(slpsession);
+}
+
+#if 0
+static void
+msn_slp_session_send_slpmsg(MsnSlpSession *slpsession, MsnSlpMessage *slpmsg)
+{
+	slpmsg->slpsession = slpsession;
+
+#if 0
+	slpmsg->session_id = slpsession->id;
+	slpmsg->app_id = slpsession->app_id;
+#endif
+
+	msn_slplink_send_slpmsg(slpsession->slpcall->slplink, slpmsg);
+}
+#endif
============================================================
--- libpurple/protocols/msnp9/slpsession.h	c517973d7c955c7f606495a17064ff941877f349
+++ libpurple/protocols/msnp9/slpsession.h	c517973d7c955c7f606495a17064ff941877f349
@@ -0,0 +1,48 @@
+/**
+ * @file slpsession.h SLP Session functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_SLPSESSION_H_
+#define _MSN_SLPSESSION_H_
+
+typedef struct _MsnSlpSession MsnSlpSession;
+
+#include "slpcall.h"
+#include "slpsession.h"
+#include "slpmsg.h"
+
+struct _MsnSlpSession
+{
+	/* MsnSlpLink *slplink; */
+	MsnSlpCall *slpcall;
+
+	long id;
+
+	long app_id;
+	char *call_id;
+};
+
+MsnSlpSession *msn_slp_session_new(MsnSlpCall *slpcall);
+void msn_slp_session_destroy(MsnSlpSession *slpsession);
+void msn_slpsession_send_slpmsg(MsnSlpSession *slpsession,
+								MsnSlpMessage *slpmsg);
+#endif /* _MSN_SLPSESSION_H_ */
============================================================
--- libpurple/protocols/msnp9/state.c	5c872464e2b4a88a401384c77118fd5570f11111
+++ libpurple/protocols/msnp9/state.c	5c872464e2b4a88a401384c77118fd5570f11111
@@ -0,0 +1,132 @@
+/**
+ * @file state.c State functions and definitions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "state.h"
+
+static const char *away_text[] =
+{
+	N_("Available"),
+	N_("Available"),
+	N_("Busy"),
+	N_("Idle"),
+	N_("Be Right Back"),
+	N_("Away From Computer"),
+	N_("On The Phone"),
+	N_("Out To Lunch"),
+	N_("Available"),
+	N_("Available")
+};
+
+void
+msn_change_status(MsnSession *session)
+{
+	PurpleAccount *account;
+	MsnCmdProc *cmdproc;
+	MsnUser *user;
+	MsnObject *msnobj;
+	const char *state_text;
+
+	g_return_if_fail(session != NULL);
+	g_return_if_fail(session->notification != NULL);
+
+	account = session->account;
+	cmdproc = session->notification->cmdproc;
+	user = session->user;
+	state_text = msn_state_get_text(msn_state_from_account(account));
+
+	/* If we're not logged in yet, don't send the status to the server,
+	 * it will be sent when login completes
+	 */
+	if (!session->logged_in)
+		return;
+
+	msnobj = msn_user_get_object(user);
+
+	if (msnobj == NULL)
+	{
+		msn_cmdproc_send(cmdproc, "CHG", "%s %d", state_text,
+						 MSN_CLIENT_ID);
+	}
+	else
+	{
+		char *msnobj_str;
+
+		msnobj_str = msn_object_to_string(msnobj);
+
+		msn_cmdproc_send(cmdproc, "CHG", "%s %d %s", state_text,
+						 MSN_CLIENT_ID, purple_url_encode(msnobj_str));
+
+		g_free(msnobj_str);
+	}
+}
+
+const char *
+msn_away_get_text(MsnAwayType type)
+{
+	g_return_val_if_fail(type <= MSN_HIDDEN, NULL);
+
+	return _(away_text[type]);
+}
+
+const char *
+msn_state_get_text(MsnAwayType state)
+{
+	static char *status_text[] =
+	{ "NLN", "NLN", "BSY", "IDL", "BRB", "AWY", "PHN", "LUN", "HDN", "HDN" };
+
+	return status_text[state];
+}
+
+MsnAwayType
+msn_state_from_account(PurpleAccount *account)
+{
+	MsnAwayType msnstatus;
+	PurplePresence *presence;
+	PurpleStatus *status;
+	const char *status_id;
+
+	presence = purple_account_get_presence(account);
+	status = purple_presence_get_active_status(presence);
+	status_id = purple_status_get_id(status);
+
+	if (!strcmp(status_id, "away"))
+		msnstatus = MSN_AWAY;
+	else if (!strcmp(status_id, "brb"))
+		msnstatus = MSN_BRB;
+	else if (!strcmp(status_id, "busy"))
+		msnstatus = MSN_BUSY;
+	else if (!strcmp(status_id, "phone"))
+		msnstatus = MSN_PHONE;
+	else if (!strcmp(status_id, "lunch"))
+		msnstatus = MSN_LUNCH;
+	else if (!strcmp(status_id, "invisible"))
+		msnstatus = MSN_HIDDEN;
+	else
+		msnstatus = MSN_ONLINE;
+
+	if ((msnstatus == MSN_ONLINE) && purple_presence_is_idle(presence))
+		msnstatus = MSN_IDLE;
+
+	return msnstatus;
+}
============================================================
--- libpurple/protocols/msnp9/state.h	868b82e2dfe7df042e06bcecedf855ded40b644e
+++ libpurple/protocols/msnp9/state.h	868b82e2dfe7df042e06bcecedf855ded40b644e
@@ -0,0 +1,64 @@
+/**
+ * @file state.h State functions and definitions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_STATE_H_
+#define _MSN_STATE_H_
+
+/**
+ * Away types.
+ */
+typedef enum
+{
+	MSN_ONLINE  = 1,
+	MSN_BUSY    = 2,
+	MSN_IDLE    = 3,
+	MSN_BRB     = 4,
+	MSN_AWAY    = 5,
+	MSN_PHONE   = 6,
+	MSN_LUNCH   = 7,
+	MSN_OFFLINE = 8,
+	MSN_HIDDEN  = 9
+
+} MsnAwayType;
+
+/**
+ * Changes the status of the user.
+ *
+ * @param session The MSN session.
+ */
+void msn_change_status(MsnSession *session);
+
+/**
+ * Returns the string representation of an away type.
+ *
+ * @param type The away type.
+ *
+ * @return The string representation of the away type.
+ */
+const char *msn_away_get_text(MsnAwayType type);
+
+const char *msn_state_get_text(MsnAwayType state);
+
+MsnAwayType msn_state_from_account(PurpleAccount *account);
+
+#endif /* _MSN_STATE_H_ */
============================================================
--- libpurple/protocols/msnp9/switchboard.c	db55194a4bdc9036a8d6afc56f7f1384c786fcec
+++ libpurple/protocols/msnp9/switchboard.c	db55194a4bdc9036a8d6afc56f7f1384c786fcec
@@ -0,0 +1,1294 @@
+/**
+ * @file switchboard.c MSN switchboard functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "prefs.h"
+#include "switchboard.h"
+#include "notification.h"
+#include "msn-utils.h"
+
+#include "error.h"
+
+static MsnTable *cbs_table;
+
+static void msg_error_helper(MsnCmdProc *cmdproc, MsnMessage *msg,
+							 MsnMsgErrorType error);
+
+/**************************************************************************
+ * Main
+ **************************************************************************/
+
+MsnSwitchBoard *
+msn_switchboard_new(MsnSession *session)
+{
+	MsnSwitchBoard *swboard;
+	MsnServConn *servconn;
+
+	g_return_val_if_fail(session != NULL, NULL);
+
+	swboard = g_new0(MsnSwitchBoard, 1);
+
+	swboard->session = session;
+	swboard->servconn = servconn = msn_servconn_new(session, MSN_SERVCONN_SB);
+	swboard->cmdproc = servconn->cmdproc;
+
+	swboard->msg_queue = g_queue_new();
+	swboard->empty = TRUE;
+
+	swboard->cmdproc->data = swboard;
+	swboard->cmdproc->cbs_table = cbs_table;
+
+	session->switches = g_list_append(session->switches, swboard);
+
+	return swboard;
+}
+
+void
+msn_switchboard_destroy(MsnSwitchBoard *swboard)
+{
+	MsnSession *session;
+	MsnMessage *msg;
+	GList *l;
+
+#ifdef MSN_DEBUG_SB
+	purple_debug_info("msn", "switchboard_destroy: swboard(%p)\n", swboard);
+#endif
+
+	g_return_if_fail(swboard != NULL);
+
+	if (swboard->destroying)
+		return;
+
+	swboard->destroying = TRUE;
+
+	/* If it linked us is because its looking for trouble */
+	while (swboard->slplinks != NULL)
+		msn_slplink_destroy(swboard->slplinks->data);
+
+	/* Destroy the message queue */
+	while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL)
+	{
+		if (swboard->error != MSN_SB_ERROR_NONE)
+		{
+			/* The messages could not be sent due to a switchboard error */
+			msg_error_helper(swboard->cmdproc, msg,
+							 MSN_MSG_ERROR_SB);
+		}
+		msn_message_unref(msg);
+	}
+
+	g_queue_free(swboard->msg_queue);
+
+	/* msg_error_helper will both remove the msg from ack_list and
+	   unref it, so we don't need to do either here */
+	while ((l = swboard->ack_list) != NULL)
+		msg_error_helper(swboard->cmdproc, l->data, MSN_MSG_ERROR_SB);
+
+	g_free(swboard->im_user);
+	g_free(swboard->auth_key);
+	g_free(swboard->session_id);
+
+	for (l = swboard->users; l != NULL; l = l->next)
+		g_free(l->data);
+
+	session = swboard->session;
+	session->switches = g_list_remove(session->switches, swboard);
+
+#if 0
+	/* This should never happen or we are in trouble. */
+	if (swboard->servconn != NULL)
+		msn_servconn_destroy(swboard->servconn);
+#endif
+
+	swboard->cmdproc->data = NULL;
+
+	msn_servconn_set_disconnect_cb(swboard->servconn, NULL);
+
+	msn_servconn_destroy(swboard->servconn);
+
+	g_free(swboard);
+}
+
+void
+msn_switchboard_set_auth_key(MsnSwitchBoard *swboard, const char *key)
+{
+	g_return_if_fail(swboard != NULL);
+	g_return_if_fail(key != NULL);
+
+	swboard->auth_key = g_strdup(key);
+}
+
+const char *
+msn_switchboard_get_auth_key(MsnSwitchBoard *swboard)
+{
+	g_return_val_if_fail(swboard != NULL, NULL);
+
+	return swboard->auth_key;
+}
+
+void
+msn_switchboard_set_session_id(MsnSwitchBoard *swboard, const char *id)
+{
+	g_return_if_fail(swboard != NULL);
+	g_return_if_fail(id != NULL);
+
+	if (swboard->session_id != NULL)
+		g_free(swboard->session_id);
+
+	swboard->session_id = g_strdup(id);
+}
+
+const char *
+msn_switchboard_get_session_id(MsnSwitchBoard *swboard)
+{
+	g_return_val_if_fail(swboard != NULL, NULL);
+
+	return swboard->session_id;
+}
+
+void
+msn_switchboard_set_invited(MsnSwitchBoard *swboard, gboolean invited)
+{
+	g_return_if_fail(swboard != NULL);
+
+	swboard->invited = invited;
+}
+
+gboolean
+msn_switchboard_is_invited(MsnSwitchBoard *swboard)
+{
+	g_return_val_if_fail(swboard != NULL, FALSE);
+
+	return swboard->invited;
+}
+
+/**************************************************************************
+ * Utility
+ **************************************************************************/
+
+static void
+send_clientcaps(MsnSwitchBoard *swboard)
+{
+	MsnMessage *msg;
+
+	msg = msn_message_new(MSN_MSG_CAPS);
+	msn_message_set_content_type(msg, "text/x-clientcaps");
+	msn_message_set_flag(msg, 'U');
+	msn_message_set_bin_data(msg, MSN_CLIENTINFO, strlen(MSN_CLIENTINFO));
+
+	msn_switchboard_send_msg(swboard, msg, TRUE);
+
+	msn_message_destroy(msg);
+}
+
+static void
+msn_switchboard_add_user(MsnSwitchBoard *swboard, const char *user)
+{
+	MsnCmdProc *cmdproc;
+	PurpleAccount *account;
+
+	g_return_if_fail(swboard != NULL);
+
+	cmdproc = swboard->cmdproc;
+	account = cmdproc->session->account;
+
+	swboard->users = g_list_prepend(swboard->users, g_strdup(user));
+	swboard->current_users++;
+	swboard->empty = FALSE;
+
+#ifdef MSN_DEBUG_CHAT
+	purple_debug_info("msn", "user=[%s], total=%d\n", user,
+					swboard->current_users);
+#endif
+
+	if (!(swboard->flag & MSN_SB_FLAG_IM) && (swboard->conv != NULL))
+	{
+		/* This is a helper switchboard. */
+		purple_debug_error("msn", "switchboard_add_user: conv != NULL\n");
+		return;
+	}
+
+	if ((swboard->conv != NULL) &&
+		(purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT))
+	{
+		purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv), user, NULL,
+								PURPLE_CBFLAGS_NONE, TRUE);
+	}
+	else if (swboard->current_users > 1 || swboard->total_users > 1)
+	{
+		if (swboard->conv == NULL ||
+			purple_conversation_get_type(swboard->conv) != PURPLE_CONV_TYPE_CHAT)
+		{
+			GList *l;
+
+#ifdef MSN_DEBUG_CHAT
+			purple_debug_info("msn", "[chat] Switching to chat.\n");
+#endif
+
+#if 0
+			/* this is bad - it causes msn_switchboard_close to be called on the
+			 * switchboard we're in the middle of using :( */
+			if (swboard->conv != NULL)
+				purple_conversation_destroy(swboard->conv);
+#endif
+
+			swboard->chat_id = cmdproc->session->conv_seq++;
+			swboard->flag |= MSN_SB_FLAG_IM;
+			swboard->conv = serv_got_joined_chat(account->gc,
+												 swboard->chat_id,
+												 "MSN Chat");
+
+			for (l = swboard->users; l != NULL; l = l->next)
+			{
+				const char *tmp_user;
+
+				tmp_user = l->data;
+
+#ifdef MSN_DEBUG_CHAT
+				purple_debug_info("msn", "[chat] Adding [%s].\n", tmp_user);
+#endif
+
+				purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv),
+										tmp_user, NULL, PURPLE_CBFLAGS_NONE, TRUE);
+			}
+
+#ifdef MSN_DEBUG_CHAT
+			purple_debug_info("msn", "[chat] We add ourselves.\n");
+#endif
+
+			purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv),
+									purple_account_get_username(account),
+									NULL, PURPLE_CBFLAGS_NONE, TRUE);
+
+			g_free(swboard->im_user);
+			swboard->im_user = NULL;
+		}
+	}
+	else if (swboard->conv == NULL)
+	{
+		swboard->conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
+															user, account);
+	}
+	else
+	{
+		purple_debug_warning("msn", "switchboard_add_user: This should not happen!\n");
+	}
+}
+
+static PurpleConversation *
+msn_switchboard_get_conv(MsnSwitchBoard *swboard)
+{
+	PurpleAccount *account;
+
+	g_return_val_if_fail(swboard != NULL, NULL);
+
+	if (swboard->conv != NULL)
+		return swboard->conv;
+
+	purple_debug_error("msn", "Switchboard with unassigned conversation\n");
+
+	account = swboard->session->account;
+
+	return (swboard->conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
+												  account, swboard->im_user));
+}
+
+static void
+msn_switchboard_report_user(MsnSwitchBoard *swboard, PurpleMessageFlags flags, const char *msg)
+{
+	PurpleConversation *conv;
+
+	g_return_if_fail(swboard != NULL);
+	g_return_if_fail(msg != NULL);
+
+	if ((conv = msn_switchboard_get_conv(swboard)) != NULL)
+	{
+		purple_conversation_write(conv, NULL, msg, flags, time(NULL));
+	}
+}
+
+static void
+swboard_error_helper(MsnSwitchBoard *swboard, int reason, const char *passport)
+{
+	g_return_if_fail(swboard != NULL);
+
+	purple_debug_warning("msg", "Error: Unable to call the user %s for reason %i\n",
+					   passport ? passport : "(null)", reason);
+
+	/* TODO: if current_users > 0, this is probably a chat and an invite failed,
+	 * we should report that in the chat or something */
+	if (swboard->current_users == 0)
+	{
+		swboard->error = reason;
+		msn_switchboard_close(swboard);
+	}
+}
+
+static void
+cal_error_helper(MsnTransaction *trans, int reason)
+{
+	MsnSwitchBoard *swboard;
+	const char *passport;
+	char **params;
+
+	params = g_strsplit(trans->params, " ", 0);
+
+	passport = params[0];
+
+	swboard = trans->data;
+
+	purple_debug_warning("msn", "cal_error_helper: command %s failed for reason %i\n",trans->command,reason);
+
+	swboard_error_helper(swboard, reason, passport);
+
+	g_strfreev(params);
+}
+
+static void
+msg_error_helper(MsnCmdProc *cmdproc, MsnMessage *msg, MsnMsgErrorType error)
+{
+	MsnSwitchBoard *swboard;
+
+	g_return_if_fail(cmdproc != NULL);
+	g_return_if_fail(msg     != NULL);
+
+	if ((error != MSN_MSG_ERROR_SB) && (msg->nak_cb != NULL))
+		msg->nak_cb(msg, msg->ack_data);
+
+	swboard = cmdproc->data;
+
+	/* This is not good, and should be fixed somewhere else. */
+	g_return_if_fail(swboard != NULL);
+
+	if (msg->type == MSN_MSG_TEXT)
+	{
+		const char *format, *str_reason;
+		char *body_str, *body_enc, *pre, *post;
+
+#if 0
+		if (swboard->conv == NULL)
+		{
+			if (msg->ack_ref)
+				msn_message_unref(msg);
+
+			return;
+		}
+#endif
+
+		if (error == MSN_MSG_ERROR_TIMEOUT)
+		{
+			str_reason = _("Message may have not been sent "
+						   "because a timeout occurred:");
+		}
+		else if (error == MSN_MSG_ERROR_SB)
+		{
+			switch (swboard->error)
+			{
+				case MSN_SB_ERROR_OFFLINE:
+					str_reason = _("Message could not be sent, "
+								   "not allowed while invisible:");
+					break;
+				case MSN_SB_ERROR_USER_OFFLINE:
+					str_reason = _("Message could not be sent "
+								   "because the user is offline:");
+					break;
+				case MSN_SB_ERROR_CONNECTION:
+					str_reason = _("Message could not be sent "
+								   "because a connection error occurred:");
+					break;
+				case MSN_SB_ERROR_TOO_FAST:
+					str_reason = _("Message could not be sent "
+								   "because we are sending too quickly:");
+					break;
+				case MSN_SB_ERROR_AUTHFAILED:
+					str_reason = _("Message could not be sent "
+								   "because we were unable to establish a "
+								   "session with the server. This is "
+								   "likely a server problem, try again in "
+								   "a few minutes:");
+					break;
+				default:
+					str_reason = _("Message could not be sent "
+								   "because an error with "
+								   "the switchboard occurred:");
+					break;
+			}
+		}
+		else
+		{
+			str_reason = _("Message may have not been sent "
+						   "because an unknown error occurred:");
+		}
+
+		body_str = msn_message_to_string(msg);
+		body_enc = g_markup_escape_text(body_str, -1);
+		g_free(body_str);
+
+		format = msn_message_get_attr(msg, "X-MMS-IM-Format");
+		msn_parse_format(format, &pre, &post);
+		body_str = g_strdup_printf("%s%s%s", pre ? pre : "",
+								   body_enc ? body_enc : "", post ? post : "");
+		g_free(body_enc);
+		g_free(pre);
+		g_free(post);
+
+		msn_switchboard_report_user(swboard, PURPLE_MESSAGE_ERROR,
+									str_reason);
+		msn_switchboard_report_user(swboard, PURPLE_MESSAGE_RAW,
+									body_str);
+
+		g_free(body_str);
+	}
+
+	/* If a timeout occures we will want the msg around just in case we
+	 * receive the ACK after the timeout. */
+	if (msg->ack_ref && error != MSN_MSG_ERROR_TIMEOUT)
+	{
+		swboard->ack_list = g_list_remove(swboard->ack_list, msg);
+		msn_message_unref(msg);
+	}
+}
+
+/**************************************************************************
+ * Message Stuff
+ **************************************************************************/
+
+/** Called when a message times out. */
+static void
+msg_timeout(MsnCmdProc *cmdproc, MsnTransaction *trans)
+{
+	MsnMessage *msg;
+
+	msg = trans->data;
+
+	msg_error_helper(cmdproc, msg, MSN_MSG_ERROR_TIMEOUT);
+}
+
+/** Called when we receive an error of a message. */
+static void
+msg_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error)
+{
+	msg_error_helper(cmdproc, trans->data, MSN_MSG_ERROR_UNKNOWN);
+}
+
+#if 0
+/** Called when we receive an ack of a special message. */
+static void
+msg_ack(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnMessage *msg;
+
+	msg = cmd->trans->data;
+
+	if (msg->ack_cb != NULL)
+		msg->ack_cb(msg->ack_data);
+
+	msn_message_unref(msg);
+}
+
+/** Called when we receive a nak of a special message. */
+static void
+msg_nak(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnMessage *msg;
+
+	msg = cmd->trans->data;
+
+	msn_message_unref(msg);
+}
+#endif
+
+static void
+release_msg(MsnSwitchBoard *swboard, MsnMessage *msg)
+{
+	MsnCmdProc *cmdproc;
+	MsnTransaction *trans;
+	char *payload;
+	gsize payload_len;
+
+	g_return_if_fail(swboard != NULL);
+	g_return_if_fail(msg     != NULL);
+
+	cmdproc = swboard->cmdproc;
+
+	payload = msn_message_gen_payload(msg, &payload_len);
+
+#ifdef MSN_DEBUG_SB
+	msn_message_show_readable(msg, "SB SEND", FALSE);
+#endif
+
+	trans = msn_transaction_new(cmdproc, "MSG", "%c %d",
+								msn_message_get_flag(msg), payload_len);
+
+	/* Data for callbacks */
+	msn_transaction_set_data(trans, msg);
+
+	if (msg->type == MSN_MSG_TEXT)
+	{
+		msg->ack_ref = TRUE;
+		msn_message_ref(msg);
+		swboard->ack_list = g_list_append(swboard->ack_list, msg);
+		msn_transaction_set_timeout_cb(trans, msg_timeout);
+	}
+	else if (msg->type == MSN_MSG_SLP)
+	{
+		msg->ack_ref = TRUE;
+		msn_message_ref(msg);
+		swboard->ack_list = g_list_append(swboard->ack_list, msg);
+		msn_transaction_set_timeout_cb(trans, msg_timeout);
+#if 0
+		if (msg->ack_cb != NULL)
+		{
+			msn_transaction_add_cb(trans, "ACK", msg_ack);
+			msn_transaction_add_cb(trans, "NAK", msg_nak);
+		}
+#endif
+	}
+
+	trans->payload = payload;
+	trans->payload_len = payload_len;
+
+	msg->trans = trans;
+
+	msn_cmdproc_send_trans(cmdproc, trans);
+}
+
+static void
+queue_msg(MsnSwitchBoard *swboard, MsnMessage *msg)
+{
+	g_return_if_fail(swboard != NULL);
+	g_return_if_fail(msg     != NULL);
+
+	purple_debug_info("msn", "Appending message to queue.\n");
+
+	g_queue_push_tail(swboard->msg_queue, msg);
+
+	msn_message_ref(msg);
+}
+
+static void
+process_queue(MsnSwitchBoard *swboard)
+{
+	MsnMessage *msg;
+
+	g_return_if_fail(swboard != NULL);
+
+	purple_debug_info("msn", "Processing queue\n");
+
+	while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL)
+	{
+		purple_debug_info("msn", "Sending message\n");
+		release_msg(swboard, msg);
+		msn_message_unref(msg);
+	}
+}
+
+gboolean
+msn_switchboard_can_send(MsnSwitchBoard *swboard)
+{
+	g_return_val_if_fail(swboard != NULL, FALSE);
+
+	if (swboard->empty || !g_queue_is_empty(swboard->msg_queue))
+		return FALSE;
+
+	return TRUE;
+}
+
+void
+msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg,
+						 gboolean queue)
+{
+	g_return_if_fail(swboard != NULL);
+	g_return_if_fail(msg     != NULL);
+
+	if (msn_switchboard_can_send(swboard))
+		release_msg(swboard, msg);
+	else if (queue)
+		queue_msg(swboard, msg);
+}
+
+/**************************************************************************
+ * Switchboard Commands
+ **************************************************************************/
+
+static void
+ans_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSwitchBoard *swboard;
+
+	swboard = cmdproc->data;
+	swboard->ready = TRUE;
+}
+
+static void
+bye_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSwitchBoard *swboard;
+	const char *user;
+
+	swboard = cmdproc->data;
+	user = cmd->params[0];
+
+	/* cmdproc->data is set to NULL when the switchboard is destroyed;
+	 * we may get a bye shortly thereafter. */
+	g_return_if_fail(swboard != NULL);
+
+	if (!(swboard->flag & MSN_SB_FLAG_IM) && (swboard->conv != NULL))
+		purple_debug_error("msn_switchboard", "bye_cmd: helper bug\n");
+
+	if (swboard->conv == NULL)
+	{
+		/* This is a helper switchboard */
+		msn_switchboard_destroy(swboard);
+	}
+	else if ((swboard->current_users > 1) ||
+			 (purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT))
+	{
+		/* This is a switchboard used for a chat */
+		purple_conv_chat_remove_user(PURPLE_CONV_CHAT(swboard->conv), user, NULL);
+		swboard->current_users--;
+		if (swboard->current_users == 0)
+			msn_switchboard_destroy(swboard);
+	}
+	else
+	{
+		/* This is a switchboard used for a im session */
+		msn_switchboard_destroy(swboard);
+	}
+}
+
+static void
+iro_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	MsnSwitchBoard *swboard;
+
+	account = cmdproc->session->account;
+	gc = account->gc;
+	swboard = cmdproc->data;
+
+	swboard->total_users = atoi(cmd->params[2]);
+
+	msn_switchboard_add_user(swboard, cmd->params[3]);
+}
+
+static void
+joi_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session;
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	MsnSwitchBoard *swboard;
+	const char *passport;
+
+	passport = cmd->params[0];
+
+	session = cmdproc->session;
+	account = session->account;
+	gc = account->gc;
+	swboard = cmdproc->data;
+
+	msn_switchboard_add_user(swboard, passport);
+
+	process_queue(swboard);
+
+	if (!session->http_method)
+		send_clientcaps(swboard);
+
+	if (swboard->closed)
+		msn_switchboard_close(swboard);
+}
+
+static void
+msg_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len)
+{
+	MsnMessage *msg;
+
+	msg = msn_message_new_from_cmd(cmdproc->session, cmd);
+
+	msn_message_parse_payload(msg, payload, len);
+#ifdef MSN_DEBUG_SB
+	msn_message_show_readable(msg, "SB RECV", FALSE);
+#endif
+
+	if (msg->remote_user != NULL)
+		g_free (msg->remote_user);
+
+	msg->remote_user = g_strdup(cmd->params[0]);
+	msn_cmdproc_process_msg(cmdproc, msg);
+
+	msn_message_destroy(msg);
+}
+
+static void
+msg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	cmdproc->servconn->payload_len = atoi(cmd->params[2]);
+	cmdproc->last_cmd->payload_cb = msg_cmd_post;
+}
+
+static void
+nak_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnMessage *msg;
+
+	msg = cmd->trans->data;
+	g_return_if_fail(msg != NULL);
+
+	msg_error_helper(cmdproc, msg, MSN_MSG_ERROR_NAK);
+}
+
+static void
+ack_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSwitchBoard *swboard;
+	MsnMessage *msg;
+
+	msg = cmd->trans->data;
+
+	if (msg->ack_cb != NULL)
+		msg->ack_cb(msg, msg->ack_data);
+
+	swboard = cmdproc->data;
+	if (swboard)
+		swboard->ack_list = g_list_remove(swboard->ack_list, msg);
+	msn_message_unref(msg);
+}
+
+static void
+out_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	PurpleConnection *gc;
+	MsnSwitchBoard *swboard;
+
+	gc = cmdproc->session->account->gc;
+	swboard = cmdproc->data;
+
+	if (swboard->current_users > 1)
+		serv_got_chat_left(gc, swboard->chat_id);
+
+	msn_switchboard_disconnect(swboard);
+}
+
+static void
+usr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSwitchBoard *swboard;
+
+	swboard = cmdproc->data;
+
+#if 0
+	GList *l;
+
+	for (l = swboard->users; l != NULL; l = l->next)
+	{
+		const char *user;
+		user = l->data;
+
+		msn_cmdproc_send(cmdproc, "CAL", "%s", user);
+	}
+#endif
+
+	swboard->ready = TRUE;
+	msn_cmdproc_process_queue(cmdproc);
+}
+
+/**************************************************************************
+ * Message Handlers
+ **************************************************************************/
+static void
+plain_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	PurpleConnection *gc;
+	MsnSwitchBoard *swboard;
+	const char *body;
+	char *body_str;
+	char *body_enc;
+	char *body_final;
+	size_t body_len;
+	const char *passport;
+	const char *value;
+
+	gc = cmdproc->session->account->gc;
+	swboard = cmdproc->data;
+
+	body = msn_message_get_bin_data(msg, &body_len);
+	body_str = g_strndup(body, body_len);
+	body_enc = g_markup_escape_text(body_str, -1);
+	g_free(body_str);
+
+	passport = msg->remote_user;
+
+	if (!strcmp(passport, "messenger at microsoft.com") &&
+		strstr(body, "immediate security update"))
+	{
+		return;
+	}
+
+#if 0
+	if ((value = msn_message_get_attr(msg, "User-Agent")) != NULL)
+	{
+		purple_debug_misc("msn", "User-Agent = '%s'\n", value);
+	}
+#endif
+
+	if ((value = msn_message_get_attr(msg, "X-MMS-IM-Format")) != NULL)
+	{
+		char *pre, *post;
+
+		msn_parse_format(value, &pre, &post);
+
+		body_final = g_strdup_printf("%s%s%s", pre ? pre : "",
+									 body_enc ? body_enc : "", post ? post : "");
+
+		g_free(pre);
+		g_free(post);
+		g_free(body_enc);
+	}
+	else
+	{
+		body_final = body_enc;
+	}
+
+	swboard->flag |= MSN_SB_FLAG_IM;
+
+	if (swboard->current_users > 1 ||
+		((swboard->conv != NULL) &&
+		 purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT))
+	{
+		/* If current_users is always ok as it should then there is no need to
+		 * check if this is a chat. */
+		if (swboard->current_users <= 1)
+			purple_debug_misc("msn", "plain_msg: current_users(%d)\n",
+							swboard->current_users);
+
+		serv_got_chat_in(gc, swboard->chat_id, passport, 0, body_final,
+						 time(NULL));
+		if (swboard->conv == NULL)
+		{
+			swboard->conv = purple_find_chat(gc, swboard->chat_id);
+			swboard->flag |= MSN_SB_FLAG_IM;
+		}
+	}
+	else
+	{
+		serv_got_im(gc, passport, body_final, 0, time(NULL));
+		if (swboard->conv == NULL)
+		{
+			swboard->conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
+									passport, purple_connection_get_account(gc));
+			swboard->flag |= MSN_SB_FLAG_IM;
+		}
+	}
+
+	g_free(body_final);
+}
+
+static void
+control_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	PurpleConnection *gc;
+	MsnSwitchBoard *swboard;
+	char *passport;
+
+	gc = cmdproc->session->account->gc;
+	swboard = cmdproc->data;
+	passport = msg->remote_user;
+
+	if (swboard->current_users == 1 &&
+		msn_message_get_attr(msg, "TypingUser") != NULL)
+	{
+		serv_got_typing(gc, passport, MSN_TYPING_RECV_TIMEOUT,
+						PURPLE_TYPING);
+	}
+}
+
+static void
+clientcaps_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+#if 0
+	MsnSession *session;
+	MsnSwitchBoard *swboard;
+	MsnUser *user;
+	GHashTable *clientcaps;
+	const char *value;
+
+	char *passport = msg->sender;
+
+	session = cmdproc->session;
+	swboard = cmdproc->servconn->swboard;
+
+	clientcaps = msn_message_get_hashtable_from_body(msg);
+#endif
+}
+
+static void
+nudge_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	MsnSwitchBoard *swboard;
+	PurpleAccount *account;
+	const char *user;
+
+	swboard = cmdproc->data;
+	account = cmdproc->session->account;
+	user = msg->remote_user;
+
+	serv_got_attention(account->gc, user, MSN_NUDGE);
+}
+
+/**************************************************************************
+ * Connect stuff
+ **************************************************************************/
+static void
+ans_usr_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error);
+
+static void
+connect_cb(MsnServConn *servconn)
+{
+	MsnSwitchBoard *swboard;
+	MsnTransaction *trans;
+	MsnCmdProc *cmdproc;
+	PurpleAccount *account;
+
+	cmdproc = servconn->cmdproc;
+	g_return_if_fail(cmdproc != NULL);
+
+	account = cmdproc->session->account;
+	swboard = cmdproc->data;
+	g_return_if_fail(swboard != NULL);
+
+	if (msn_switchboard_is_invited(swboard))
+	{
+		swboard->empty = FALSE;
+
+		trans = msn_transaction_new(cmdproc, "ANS", "%s %s %s",
+									purple_account_get_username(account),
+									swboard->auth_key, swboard->session_id);
+	}
+	else
+	{
+		trans = msn_transaction_new(cmdproc, "USR", "%s %s",
+									purple_account_get_username(account),
+									swboard->auth_key);
+	}
+
+	msn_transaction_set_error_cb(trans, ans_usr_error);
+	msn_transaction_set_data(trans, swboard);
+	msn_cmdproc_send_trans(cmdproc, trans);
+}
+
+static void
+ans_usr_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error)
+{
+	MsnSwitchBoard *swboard;
+	char **params;
+	char *passport;
+	int reason = MSN_SB_ERROR_UNKNOWN;
+
+	if (error == 911)
+	{
+		reason = MSN_SB_ERROR_AUTHFAILED;
+	}
+
+	purple_debug_warning("msn", "ans_usr_error: command %s gave error %i\n", trans->command, error);
+
+	params = g_strsplit(trans->params, " ", 0);
+	passport = params[0];
+	swboard = trans->data;
+
+	swboard_error_helper(swboard, reason, passport);
+
+	g_strfreev(params);
+}
+
+static void
+disconnect_cb(MsnServConn *servconn)
+{
+	MsnSwitchBoard *swboard;
+
+	swboard = servconn->cmdproc->data;
+	g_return_if_fail(swboard != NULL);
+
+	msn_servconn_set_disconnect_cb(swboard->servconn, NULL);
+
+	msn_switchboard_destroy(swboard);
+}
+
+gboolean
+msn_switchboard_connect(MsnSwitchBoard *swboard, const char *host, int port)
+{
+	g_return_val_if_fail(swboard != NULL, FALSE);
+
+	msn_servconn_set_connect_cb(swboard->servconn, connect_cb);
+	msn_servconn_set_disconnect_cb(swboard->servconn, disconnect_cb);
+
+	return msn_servconn_connect(swboard->servconn, host, port);
+}
+
+void
+msn_switchboard_disconnect(MsnSwitchBoard *swboard)
+{
+	g_return_if_fail(swboard != NULL);
+
+	msn_servconn_disconnect(swboard->servconn);
+}
+
+/**************************************************************************
+ * Call stuff
+ **************************************************************************/
+static void
+got_cal(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+#if 0
+	MsnSwitchBoard *swboard;
+	const char *user;
+
+	swboard = cmdproc->data;
+
+	user = cmd->params[0];
+
+	msn_switchboard_add_user(swboard, user);
+#endif
+}
+
+static void
+cal_timeout(MsnCmdProc *cmdproc, MsnTransaction *trans)
+{
+	purple_debug_warning("msn", "cal_timeout: command %s timed out\n", trans->command);
+
+	cal_error_helper(trans, MSN_SB_ERROR_UNKNOWN);
+}
+
+static void
+cal_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error)
+{
+	int reason = MSN_SB_ERROR_UNKNOWN;
+
+	if (error == 215)
+	{
+		purple_debug_info("msn", "Invited user already in switchboard\n");
+		return;
+	}
+	else if (error == 217)
+	{
+		reason = MSN_SB_ERROR_USER_OFFLINE;
+	}
+
+	purple_debug_warning("msn", "cal_error: command %s gave error %i\n", trans->command, error);
+
+	cal_error_helper(trans, reason);
+}
+
+void
+msn_switchboard_request_add_user(MsnSwitchBoard *swboard, const char *user)
+{
+	MsnTransaction *trans;
+	MsnCmdProc *cmdproc;
+
+	g_return_if_fail(swboard != NULL);
+
+	cmdproc = swboard->cmdproc;
+
+	trans = msn_transaction_new(cmdproc, "CAL", "%s", user);
+	/* this doesn't do anything, but users seem to think that
+	 * 'Unhandled command' is some kind of error, so we don't report it */
+	msn_transaction_add_cb(trans, "CAL", got_cal);
+
+	msn_transaction_set_data(trans, swboard);
+	msn_transaction_set_timeout_cb(trans, cal_timeout);
+
+	if (swboard->ready)
+		msn_cmdproc_send_trans(cmdproc, trans);
+	else
+		msn_cmdproc_queue_trans(cmdproc, trans);
+}
+
+/**************************************************************************
+ * Create & Transfer stuff
+ **************************************************************************/
+
+static void
+got_swboard(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSwitchBoard *swboard;
+	char *host;
+	int port;
+	swboard = cmd->trans->data;
+
+	if (g_list_find(cmdproc->session->switches, swboard) == NULL)
+		/* The conversation window was closed. */
+		return;
+
+	msn_switchboard_set_auth_key(swboard, cmd->params[4]);
+
+	msn_parse_socket(cmd->params[2], &host, &port);
+
+	if (!msn_switchboard_connect(swboard, host, port))
+		msn_switchboard_destroy(swboard);
+
+	g_free(host);
+}
+
+static void
+xfr_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error)
+{
+	MsnSwitchBoard *swboard;
+	int reason = MSN_SB_ERROR_UNKNOWN;
+
+	if (error == 913)
+		reason = MSN_SB_ERROR_OFFLINE;
+	else if (error == 800)
+		reason = MSN_SB_ERROR_TOO_FAST;
+
+	swboard = trans->data;
+
+	purple_debug_info("msn", "xfr_error %i for %s: trans %x, command %s, reason %i\n",
+					error, (swboard->im_user ? swboard->im_user : "(null)"), trans,
+					(trans->command ? trans->command : "(null)"), reason);
+
+	swboard_error_helper(swboard, reason, swboard->im_user);
+}
+
+void
+msn_switchboard_request(MsnSwitchBoard *swboard)
+{
+	MsnCmdProc *cmdproc;
+	MsnTransaction *trans;
+
+	g_return_if_fail(swboard != NULL);
+
+	cmdproc = swboard->session->notification->cmdproc;
+
+	trans = msn_transaction_new(cmdproc, "XFR", "%s", "SB");
+	msn_transaction_add_cb(trans, "XFR", got_swboard);
+
+	msn_transaction_set_data(trans, swboard);
+	msn_transaction_set_error_cb(trans, xfr_error);
+
+	msn_cmdproc_send_trans(cmdproc, trans);
+}
+
+void
+msn_switchboard_close(MsnSwitchBoard *swboard)
+{
+	g_return_if_fail(swboard != NULL);
+
+	if (swboard->error != MSN_SB_ERROR_NONE)
+	{
+		msn_switchboard_destroy(swboard);
+	}
+	else if (g_queue_is_empty(swboard->msg_queue) ||
+			 !swboard->session->connected)
+	{
+		MsnCmdProc *cmdproc;
+		cmdproc = swboard->cmdproc;
+		msn_cmdproc_send_quick(cmdproc, "OUT", NULL, NULL);
+
+		msn_switchboard_destroy(swboard);
+	}
+	else
+	{
+		swboard->closed = TRUE;
+	}
+}
+
+gboolean
+msn_switchboard_release(MsnSwitchBoard *swboard, MsnSBFlag flag)
+{
+	g_return_val_if_fail(swboard != NULL, FALSE);
+
+	swboard->flag &= ~flag;
+
+	if (flag == MSN_SB_FLAG_IM)
+		/* Forget any conversation that used to be associated with this
+		 * swboard. */
+		swboard->conv = NULL;
+
+	if (swboard->flag == 0)
+	{
+		msn_switchboard_close(swboard);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/**************************************************************************
+ * Init stuff
+ **************************************************************************/
+
+void
+msn_switchboard_init(void)
+{
+	cbs_table = msn_table_new();
+
+	msn_table_add_cmd(cbs_table, "ANS", "ANS", ans_cmd);
+	msn_table_add_cmd(cbs_table, "ANS", "IRO", iro_cmd);
+
+	msn_table_add_cmd(cbs_table, "MSG", "ACK", ack_cmd);
+	msn_table_add_cmd(cbs_table, "MSG", "NAK", nak_cmd);
+
+	msn_table_add_cmd(cbs_table, "USR", "USR", usr_cmd);
+
+	msn_table_add_cmd(cbs_table, NULL, "MSG", msg_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "JOI", joi_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "BYE", bye_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "OUT", out_cmd);
+
+#if 0
+	/* They might skip the history */
+	msn_table_add_cmd(cbs_table, NULL, "ACK", NULL);
+#endif
+
+	msn_table_add_error(cbs_table, "MSG", msg_error);
+	msn_table_add_error(cbs_table, "CAL", cal_error);
+
+	/* Register the message type callbacks. */
+	msn_table_add_msg_type(cbs_table, "text/plain",
+						   plain_msg);
+	msn_table_add_msg_type(cbs_table, "text/x-msmsgscontrol",
+						   control_msg);
+	msn_table_add_msg_type(cbs_table, "text/x-clientcaps",
+						   clientcaps_msg);
+	msn_table_add_msg_type(cbs_table, "text/x-clientinfo",
+						   clientcaps_msg);
+	msn_table_add_msg_type(cbs_table, "application/x-msnmsgrp2p",
+						   msn_p2p_msg);
+	msn_table_add_msg_type(cbs_table, "text/x-mms-emoticon",
+						   msn_emoticon_msg);
+	msn_table_add_msg_type(cbs_table, "text/x-mms-animemoticon",
+	                                           msn_emoticon_msg);
+	msn_table_add_msg_type(cbs_table, "text/x-msnmsgr-datacast",
+						   nudge_msg);
+#if 0
+	msn_table_add_msg_type(cbs_table, "text/x-msmmsginvite",
+						   msn_invite_msg);
+#endif
+}
+
+void
+msn_switchboard_end(void)
+{
+	msn_table_destroy(cbs_table);
+}
============================================================
--- libpurple/protocols/msnp9/switchboard.h	cc95597b760e613460b789d4e0739f291af06c48
+++ libpurple/protocols/msnp9/switchboard.h	cc95597b760e613460b789d4e0739f291af06c48
@@ -0,0 +1,277 @@
+/**
+ * @file switchboard.h MSN switchboard functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_SWITCHBOARD_H_
+#define _MSN_SWITCHBOARD_H_
+
+typedef struct _MsnSwitchBoard MsnSwitchBoard;
+
+#include "conversation.h"
+
+#include "msg.h"
+#include "user.h"
+
+#include "servconn.h"
+
+#include "slplink.h"
+
+/**
+ * A switchboard error.
+ */
+typedef enum
+{
+	MSN_SB_ERROR_NONE, /**< No error. */
+	MSN_SB_ERROR_CAL, /**< The user could not join (answer the call). */
+	MSN_SB_ERROR_OFFLINE, /**< The account is offline. */
+	MSN_SB_ERROR_USER_OFFLINE, /**< The user to call is offline. */
+	MSN_SB_ERROR_CONNECTION, /**< There was a connection error. */
+	MSN_SB_ERROR_TOO_FAST, /**< We are sending too fast */
+	MSN_SB_ERROR_AUTHFAILED, /**< Authentication failed joining the switchboard session */
+	MSN_SB_ERROR_UNKNOWN /**< An unknown error occurred. */
+
+} MsnSBErrorType;
+
+/**
+ * A switchboard flag.
+ */
+typedef enum
+{
+	MSN_SB_FLAG_IM = 0x01, /**< This switchboard is being used for a conversation. */
+	MSN_SB_FLAG_FT = 0x02, /**< This switchboard is being used for file transfer. */
+
+} MsnSBFlag;
+
+/**
+ * A switchboard.
+ *
+ * A place where a bunch of users send messages to the rest of the users.
+ */
+struct _MsnSwitchBoard
+{
+	MsnSession *session;
+	MsnServConn *servconn;
+	MsnCmdProc *cmdproc;
+	char *im_user;
+
+	MsnSBFlag flag;
+	char *auth_key;
+	char *session_id;
+
+	PurpleConversation *conv; /**< The conversation that displays the
+							  messages of this switchboard, or @c NULL if
+							  this is a helper switchboard. */
+
+	gboolean empty;			/**< A flag that states if the swithcboard has no
+							  users in it. */
+	gboolean invited;		/**< A flag that states if we were invited to the
+							  switchboard. */
+	gboolean ready;			/**< A flag that states if this switchboard is
+							  ready to be used. */
+	gboolean closed;		/**< A flag that states if the switchboard has
+							  been closed by the user. */
+	gboolean destroying;	/**< A flag that states if the switchboard is
+							  alredy on the process of destruction. */
+
+	int current_users;
+	int total_users;
+	GList *users;
+
+	int chat_id;
+
+	GQueue *msg_queue; /**< Queue of messages to send. */
+	GList *ack_list; /**< List of messages waiting for an ack. */
+
+	MsnSBErrorType error; /**< The error that occurred in this switchboard
+							(if applicable). */
+	GList *slplinks; /**< The list of slplinks that are using this switchboard. */
+};
+
+/**
+ * Initialize the variables for switchboard creation.
+ */
+void msn_switchboard_init(void);
+
+/**
+ * Destroy the variables for switchboard creation.
+ */
+void msn_switchboard_end(void);
+
+/**
+ * Creates a new switchboard.
+ *
+ * @param session The MSN session.
+ *
+ * @return The new switchboard.
+ */
+MsnSwitchBoard *msn_switchboard_new(MsnSession *session);
+
+/**
+ * Destroys a switchboard.
+ *
+ * @param swboard The switchboard to destroy.
+ */
+void msn_switchboard_destroy(MsnSwitchBoard *swboard);
+
+/**
+ * Sets the auth key the switchboard must use when connecting.
+ *
+ * @param swboard The switchboard.
+ * @param key     The auth key.
+ */
+void msn_switchboard_set_auth_key(MsnSwitchBoard *swboard, const char *key);
+
+/**
+ * Returns the auth key the switchboard must use when connecting.
+ *
+ * @param swboard The switchboard.
+ *
+ * @return The auth key.
+ */
+const char *msn_switchboard_get_auth_key(MsnSwitchBoard *swboard);
+
+/**
+ * Sets the session ID the switchboard must use when connecting.
+ *
+ * @param swboard The switchboard.
+ * @param id      The session ID.
+ */
+void msn_switchboard_set_session_id(MsnSwitchBoard *swboard, const char *id);
+
+/**
+ * Returns the session ID the switchboard must use when connecting.
+ *
+ * @param swboard The switchboard.
+ *
+ * @return The session ID.
+ */
+const char *msn_switchboard_get_session_id(MsnSwitchBoard *swboard);
+
+/**
+ * Sets whether or not we were invited to this switchboard.
+ *
+ * @param swboard The switchboard.
+ * @param invite  @c TRUE if invited, @c FALSE otherwise.
+ */
+void msn_switchboard_set_invited(MsnSwitchBoard *swboard, gboolean invited);
+
+/**
+ * Returns whether or not we were invited to this switchboard.
+ *
+ * @param swboard The switchboard.
+ *
+ * @return @c TRUE if invited, @c FALSE otherwise.
+ */
+gboolean msn_switchboard_is_invited(MsnSwitchBoard *swboard);
+
+/**
+ * Connects to a switchboard.
+ *
+ * @param swboard The switchboard.
+ * @param host    The switchboard server host.
+ * @param port    The switcbharod server port.
+ *
+ * @return @c TRUE if able to connect, or @c FALSE otherwise.
+ */
+gboolean msn_switchboard_connect(MsnSwitchBoard *swboard,
+								 const char *host, int port);
+
+/**
+ * Disconnects from a switchboard.
+ *
+ * @param swboard The switchboard to disconnect from.
+ */
+void msn_switchboard_disconnect(MsnSwitchBoard *swboard);
+
+/**
+ * Closes the switchboard.
+ *
+ * Called when a conversation is closed.
+ *
+ * @param swboard The switchboard to close.
+ */
+void msn_switchboard_close(MsnSwitchBoard *swboard);
+
+/**
+ * Release a switchboard from a certain function.
+ *
+ * @param swboard The switchboard to release.
+ * @param flag The flag that states the function.
+ *
+ * @return @c TRUE if the switchboard was closed, @c FALSE otherwise.
+ */
+gboolean msn_switchboard_release(MsnSwitchBoard *swboard, MsnSBFlag flag);
+
+/**
+ * Returns whether or not we currently can send a message through this
+ * switchboard.
+ *
+ * @param swboard The switchboard.
+ *
+ * @return @c TRUE if a message can be sent, @c FALSE otherwise.
+ */
+gboolean msn_switchboard_can_send(MsnSwitchBoard *swboard);
+
+/**
+ * Sends a message through this switchboard.
+ *
+ * @param swboard The switchboard.
+ * @param msg The message.
+ * @param queue A flag that states if we want this message to be queued (in
+ * the case it cannot currently be sent).
+ *
+ * @return @c TRUE if a message can be sent, @c FALSE otherwise.
+ */
+void msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg,
+							  gboolean queue);
+
+gboolean msn_switchboard_chat_leave(MsnSwitchBoard *swboard);
+gboolean msn_switchboard_chat_invite(MsnSwitchBoard *swboard, const char *who);
+
+void msn_switchboard_request(MsnSwitchBoard *swboard);
+void msn_switchboard_request_add_user(MsnSwitchBoard *swboard, const char *user);
+
+/**
+ * Processes peer to peer messages.
+ *
+ * @param cmdproc The command processor.
+ * @param msg     The message.
+ */
+void msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
+
+/**
+ * Processes emoticon messages.
+ *
+ * @param cmdproc The command processor.
+ * @param msg     The message.
+ */
+void msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
+
+/**
+ * Processes INVITE messages.
+ *
+ * @param cmdproc The command processor.
+ * @param msg     The message.
+ */
+void msn_invite_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
+
+#endif /* _MSN_SWITCHBOARD_H_ */
============================================================
--- libpurple/protocols/msnp9/sync.c	a07f7804d5d27843b891a5064a57f0107a51bfe6
+++ libpurple/protocols/msnp9/sync.c	a07f7804d5d27843b891a5064a57f0107a51bfe6
@@ -0,0 +1,258 @@
+/**
+ * @file sync.c MSN list synchronization functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "sync.h"
+#include "state.h"
+
+static MsnTable *cbs_table;
+
+static void
+blp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	PurpleConnection *gc = cmdproc->session->account->gc;
+	const char *list_name;
+
+	list_name = cmd->params[0];
+
+	if (!g_ascii_strcasecmp(list_name, "AL"))
+	{
+		/*
+		 * If the current setting is AL, messages from users who
+		 * are not in BL will be delivered.
+		 *
+		 * In other words, deny some.
+		 */
+		gc->account->perm_deny = PURPLE_PRIVACY_DENY_USERS;
+	}
+	else
+	{
+		/* If the current setting is BL, only messages from people
+		 * who are in the AL will be delivered.
+		 *
+		 * In other words, permit some.
+		 */
+		gc->account->perm_deny = PURPLE_PRIVACY_ALLOW_USERS;
+	}
+}
+
+static void
+prp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session = cmdproc->session;
+	const char *type, *value;
+
+	type  = cmd->params[0];
+	value = cmd->params[1];
+
+	if (cmd->param_count == 2)
+	{
+		if (!strcmp(type, "PHH"))
+			msn_user_set_home_phone(session->user, purple_url_decode(value));
+		else if (!strcmp(type, "PHW"))
+			msn_user_set_work_phone(session->user, purple_url_decode(value));
+		else if (!strcmp(type, "PHM"))
+			msn_user_set_mobile_phone(session->user, purple_url_decode(value));
+	}
+	else
+	{
+		if (!strcmp(type, "PHH"))
+			msn_user_set_home_phone(session->user, NULL);
+		else if (!strcmp(type, "PHW"))
+			msn_user_set_work_phone(session->user, NULL);
+		else if (!strcmp(type, "PHM"))
+			msn_user_set_mobile_phone(session->user, NULL);
+	}
+}
+
+static void
+lsg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session = cmdproc->session;
+	const char *name;
+	int group_id;
+
+	group_id = atoi(cmd->params[0]);
+	name = purple_url_decode(cmd->params[1]);
+
+	msn_group_new(session->userlist, group_id, name);
+
+	/* HACK */
+	if (group_id == 0)
+	{
+		/* Group of ungroupped buddies */
+		if (session->sync->total_users == 0)
+		{
+			cmdproc->cbs_table = session->sync->old_cbs_table;
+
+			msn_session_finish_login(session);
+
+			msn_sync_destroy(session->sync);
+			session->sync = NULL;
+		}
+		return;
+	}
+
+	if ((purple_find_group(name)) == NULL)
+	{
+		PurpleGroup *g = purple_group_new(name);
+		purple_blist_add_group(g, NULL);
+	}
+}
+
+static void
+lst_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session = cmdproc->session;
+	char *passport = NULL;
+	const char *friend = NULL;
+	int list_op;
+	MsnUser *user;
+
+	passport = cmd->params[0];
+	friend   = purple_url_decode(cmd->params[1]);
+	list_op  = atoi(cmd->params[2]);
+
+	user = msn_user_new(session->userlist, passport, friend);
+
+	msn_userlist_add_user(session->userlist, user);
+
+	session->sync->last_user = user;
+
+	/* TODO: This can be improved */
+
+	if (list_op & MSN_LIST_FL_OP)
+	{
+		char **c;
+		char **tokens;
+		const char *group_nums;
+		GSList *group_ids;
+
+		group_nums = cmd->params[3];
+
+		group_ids = NULL;
+
+		tokens = g_strsplit(group_nums, ",", -1);
+
+		for (c = tokens; *c != NULL; c++)
+		{
+			int id;
+
+			id = atoi(*c);
+			group_ids = g_slist_append(group_ids, GINT_TO_POINTER(id));
+		}
+
+		g_strfreev(tokens);
+
+		msn_got_lst_user(session, user, list_op, group_ids);
+
+		g_slist_free(group_ids);
+	}
+	else
+	{
+		msn_got_lst_user(session, user, list_op, NULL);
+	}
+
+	session->sync->num_users++;
+
+	if (session->sync->num_users == session->sync->total_users)
+	{
+		cmdproc->cbs_table = session->sync->old_cbs_table;
+
+		msn_session_finish_login(session);
+
+		msn_sync_destroy(session->sync);
+		session->sync = NULL;
+	}
+}
+
+static void
+bpr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSync *sync = cmdproc->session->sync;
+	const char *type, *value;
+	MsnUser *user;
+
+	user = sync->last_user;
+
+	g_return_if_fail(user != NULL);
+
+	type     = cmd->params[0];
+	value    = cmd->params[1];
+
+	if (value)
+	{
+		if (!strcmp(type, "MOB"))
+		{
+			if (!strcmp(value, "Y"))
+				user->mobile = TRUE;
+		}
+		else if (!strcmp(type, "PHH"))
+			msn_user_set_home_phone(user, purple_url_decode(value));
+		else if (!strcmp(type, "PHW"))
+			msn_user_set_work_phone(user, purple_url_decode(value));
+		else if (!strcmp(type, "PHM"))
+			msn_user_set_mobile_phone(user, purple_url_decode(value));
+	}
+}
+
+void
+msn_sync_init(void)
+{
+	/* TODO: check prp, blp, bpr */
+
+	cbs_table = msn_table_new();
+
+	/* Syncing */
+	msn_table_add_cmd(cbs_table, NULL, "GTC", NULL);
+	msn_table_add_cmd(cbs_table, NULL, "BLP", blp_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "PRP", prp_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "LSG", lsg_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "LST", lst_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "BPR", bpr_cmd);
+}
+
+void
+msn_sync_end(void)
+{
+	msn_table_destroy(cbs_table);
+}
+
+MsnSync *
+msn_sync_new(MsnSession *session)
+{
+	MsnSync *sync;
+
+	sync = g_new0(MsnSync, 1);
+
+	sync->session = session;
+	sync->cbs_table = cbs_table;
+
+	return sync;
+}
+
+void
+msn_sync_destroy(MsnSync *sync)
+{
+	g_free(sync);
+}
============================================================
--- libpurple/protocols/msnp9/sync.h	0b8d0bab3f32537a85aa3702d3c32b39853646c1
+++ libpurple/protocols/msnp9/sync.h	0b8d0bab3f32537a85aa3702d3c32b39853646c1
@@ -0,0 +1,52 @@
+/**
+ * @file sync.h MSN list synchronization functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_SYNC_H_
+#define _MSN_SYNC_H_
+
+typedef struct _MsnSync MsnSync;
+
+#include "session.h"
+#include "table.h"
+#include "user.h"
+
+struct _MsnSync
+{
+	MsnSession *session;
+	MsnTable *cbs_table;
+	MsnTable *old_cbs_table;
+
+	int num_users;
+	int total_users;
+	int num_groups;
+	int total_groups;
+	MsnUser *last_user;
+};
+
+void msn_sync_init(void);
+void msn_sync_end(void);
+
+MsnSync * msn_sync_new(MsnSession *session);
+void msn_sync_destroy(MsnSync *sync);
+
+#endif /* _MSN_SYNC_H_ */
============================================================
--- libpurple/protocols/msnp9/table.c	107d26064461bde97ac90b9a1bebc84eae7d349f
+++ libpurple/protocols/msnp9/table.c	107d26064461bde97ac90b9a1bebc84eae7d349f
@@ -0,0 +1,132 @@
+/**
+ * @file table.c MSN helper structure
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "table.h"
+
+static void
+null_cmd_cb(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+}
+
+static void
+null_error_cb(MsnCmdProc *cmdproc, MsnTransaction *trans, int error)
+{
+}
+
+MsnTable *
+msn_table_new()
+{
+	MsnTable *table;
+
+	table = g_new0(MsnTable, 1);
+
+	table->cmds = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_hash_table_destroy);
+	table->msgs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+	table->errors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+
+	table->async = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+	table->fallback = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+
+	return table;
+}
+
+void
+msn_table_destroy(MsnTable *table)
+{
+	g_return_if_fail(table != NULL);
+
+	g_hash_table_destroy(table->cmds);
+	g_hash_table_destroy(table->msgs);
+	g_hash_table_destroy(table->errors);
+
+	g_hash_table_destroy(table->async);
+	g_hash_table_destroy(table->fallback);
+
+	g_free(table);
+}
+
+void
+msn_table_add_cmd(MsnTable *table,
+				  char *command, char *answer, MsnTransCb cb)
+{
+	GHashTable *cbs;
+
+	g_return_if_fail(table  != NULL);
+	g_return_if_fail(answer != NULL);
+
+	cbs = NULL;
+
+	if (command == NULL)
+	{
+		cbs = table->async;
+	}
+	else if (strcmp(command, "fallback") == 0)
+	{
+		cbs = table->fallback;
+	}
+	else
+	{
+		cbs = g_hash_table_lookup(table->cmds, command);
+
+		if (cbs == NULL)
+		{
+			cbs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+			g_hash_table_insert(table->cmds, command, cbs);
+		}
+	}
+
+	if (cb == NULL)
+		cb = null_cmd_cb;
+
+	g_hash_table_insert(cbs, answer, cb);
+}
+
+void
+msn_table_add_error(MsnTable *table,
+					char *answer, MsnErrorCb cb)
+{
+	g_return_if_fail(table  != NULL);
+	g_return_if_fail(answer != NULL);
+
+	if (cb == NULL)
+		cb = null_error_cb;
+
+	g_hash_table_insert(table->errors, answer, cb);
+}
+
+void
+msn_table_add_msg_type(MsnTable *table,
+					   char *type, MsnMsgTypeCb cb)
+{
+	g_return_if_fail(table != NULL);
+	g_return_if_fail(type  != NULL);
+	g_return_if_fail(cb    != NULL);
+
+#if 0
+	if (cb == NULL)
+		cb = null_msg_cb;
+#endif
+
+	g_hash_table_insert(table->msgs, type, cb);
+}
============================================================
--- libpurple/protocols/msnp9/table.h	7e7d867aa720802926c279840b6a238f76d1beb0
+++ libpurple/protocols/msnp9/table.h	7e7d867aa720802926c279840b6a238f76d1beb0
@@ -0,0 +1,53 @@
+/**
+ * @file table.h MSN helper structure
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_TABLE_H_
+#define _MSN_TABLE_H_
+
+typedef struct _MsnTable MsnTable;
+
+#include "cmdproc.h"
+#include "transaction.h"
+#include "msg.h"
+
+typedef void (*MsnMsgTypeCb)(MsnCmdProc *cmdproc, MsnMessage *msg);
+
+struct _MsnTable
+{
+	GHashTable *cmds;
+	GHashTable *msgs;
+	GHashTable *errors;
+
+	GHashTable *async;
+	GHashTable *fallback;
+};
+
+MsnTable *msn_table_new(void);
+void msn_table_destroy(MsnTable *table);
+
+void msn_table_add_cmd(MsnTable *table, char *command, char *answer,
+					   MsnTransCb cb);
+void msn_table_add_error(MsnTable *table, char *answer, MsnErrorCb cb);
+void msn_table_add_msg_type(MsnTable *table, char *type, MsnMsgTypeCb cb);
+
+#endif /* _MSN_TABLE_H_ */
============================================================
--- libpurple/protocols/msnp9/transaction.c	499cec7eb2dcb61d2f2b16270fb92a449e67bde5
+++ libpurple/protocols/msnp9/transaction.c	499cec7eb2dcb61d2f2b16270fb92a449e67bde5
@@ -0,0 +1,221 @@
+/**
+ * @file transaction.c MSN transaction functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "transaction.h"
+
+MsnTransaction *
+msn_transaction_new(MsnCmdProc *cmdproc, const char *command,
+					const char *format, ...)
+{
+	MsnTransaction *trans;
+	va_list arg;
+
+	g_return_val_if_fail(command != NULL, NULL);
+
+	trans = g_new0(MsnTransaction, 1);
+
+	trans->cmdproc = cmdproc;
+	trans->command = g_strdup(command);
+
+	if (format != NULL)
+	{
+		va_start(arg, format);
+		trans->params = g_strdup_vprintf(format, arg);
+		va_end(arg);
+	}
+
+	/* trans->queue = g_queue_new(); */
+
+	return trans;
+}
+
+void
+msn_transaction_destroy(MsnTransaction *trans)
+{
+	g_return_if_fail(trans != NULL);
+
+	g_free(trans->command);
+	g_free(trans->params);
+	g_free(trans->payload);
+
+#if 0
+	if (trans->pendent_cmd != NULL)
+		msn_message_unref(trans->pendent_msg);
+#endif
+
+#if 0
+	MsnTransaction *elem;
+	if (trans->queue != NULL)
+	{
+		while ((elem = g_queue_pop_head(trans->queue)) != NULL)
+			msn_transaction_destroy(elem);
+
+		g_queue_free(trans->queue);
+	}
+#endif
+
+	if (trans->callbacks != NULL && trans->has_custom_callbacks)
+		g_hash_table_destroy(trans->callbacks);
+
+	if (trans->timer)
+		purple_timeout_remove(trans->timer);
+
+	g_free(trans);
+}
+
+char *
+msn_transaction_to_string(MsnTransaction *trans)
+{
+	char *str;
+
+	g_return_val_if_fail(trans != NULL, FALSE);
+
+	if (trans->params != NULL)
+		str = g_strdup_printf("%s %u %s\r\n", trans->command, trans->trId, trans->params);
+	else
+		str = g_strdup_printf("%s %u\r\n", trans->command, trans->trId);
+
+	return str;
+}
+
+void
+msn_transaction_queue_cmd(MsnTransaction *trans, MsnCommand *cmd)
+{
+	purple_debug_info("msn", "queueing command.\n");
+	trans->pendent_cmd = cmd;
+	msn_command_ref(cmd);
+}
+
+void
+msn_transaction_unqueue_cmd(MsnTransaction *trans, MsnCmdProc *cmdproc)
+{
+	MsnCommand *cmd;
+
+	if (!cmdproc->servconn->connected)
+		return;
+
+	purple_debug_info("msn", "unqueueing command.\n");
+	cmd = trans->pendent_cmd;
+
+	g_return_if_fail(cmd != NULL);
+
+	msn_cmdproc_process_cmd(cmdproc, cmd);
+	msn_command_unref(cmd);
+
+	trans->pendent_cmd = NULL;
+}
+
+#if 0
+void
+msn_transaction_queue(MsnTransaction *trans, MsnTransaction *elem)
+{
+	if (trans->queue == NULL)
+		trans->queue = g_queue_new();
+
+	g_queue_push_tail(trans->queue, elem);
+}
+
+void
+msn_transaction_unqueue(MsnTransaction *trans, MsnCmdProc *cmdproc)
+{
+	MsnTransaction *elem;
+
+	while ((elem = g_queue_pop_head(trans->queue)) != NULL)
+		msn_cmdproc_send_trans(cmdproc, elem);
+}
+#endif
+
+void
+msn_transaction_set_payload(MsnTransaction *trans,
+							const char *payload, int payload_len)
+{
+	g_return_if_fail(trans   != NULL);
+	g_return_if_fail(payload != NULL);
+
+	trans->payload = g_strdup(payload);
+	trans->payload_len = payload_len ? payload_len : strlen(trans->payload);
+}
+
+void
+msn_transaction_set_data(MsnTransaction *trans, void *data)
+{
+	g_return_if_fail(trans != NULL);
+
+	trans->data = data;
+}
+
+void
+msn_transaction_add_cb(MsnTransaction *trans, char *answer,
+					   MsnTransCb cb)
+{
+	g_return_if_fail(trans  != NULL);
+	g_return_if_fail(answer != NULL);
+
+	if (trans->callbacks == NULL)
+	{
+		trans->has_custom_callbacks = TRUE;
+		trans->callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+												 NULL);
+	}
+	else if (trans->has_custom_callbacks != TRUE)
+		g_return_if_reached ();
+
+	g_hash_table_insert(trans->callbacks, answer, cb);
+}
+
+static gboolean
+transaction_timeout(gpointer data)
+{
+	MsnTransaction *trans;
+
+	trans = data;
+	g_return_val_if_fail(trans != NULL, FALSE);
+
+#if 0
+	purple_debug_info("msn", "timed out: %s %d %s\n", trans->command, trans->trId, trans->params);
+#endif
+
+	if (trans->timeout_cb != NULL)
+		trans->timeout_cb(trans->cmdproc, trans);
+
+	return FALSE;
+}
+
+void
+msn_transaction_set_timeout_cb(MsnTransaction *trans, MsnTimeoutCb cb)
+{
+	if (trans->timer)
+	{
+		purple_debug_error("msn", "This shouldn't be happening\n");
+		purple_timeout_remove(trans->timer);
+	}
+	trans->timeout_cb = cb;
+	trans->timer = purple_timeout_add(60000, transaction_timeout, trans);
+}
+
+void
+msn_transaction_set_error_cb(MsnTransaction *trans, MsnErrorCb cb)
+{
+	trans->error_cb = cb;
+}
============================================================
--- libpurple/protocols/msnp9/transaction.h	66e57f331b13f074c254c7fcfca6cdd2dfbc99de
+++ libpurple/protocols/msnp9/transaction.h	66e57f331b13f074c254c7fcfca6cdd2dfbc99de
@@ -0,0 +1,80 @@
+/**
+ * @file transaction.h MSN transaction functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_TRANSACTION_H
+#define _MSN_TRANSACTION_H
+
+typedef struct _MsnTransaction MsnTransaction;
+
+#include "command.h"
+#include "cmdproc.h"
+
+typedef void (*MsnTransCb)(MsnCmdProc *cmdproc, MsnCommand *cmd);
+typedef void (*MsnTimeoutCb)(MsnCmdProc *cmdproc, MsnTransaction *trans);
+typedef void (*MsnErrorCb)(MsnCmdProc *cmdproc, MsnTransaction *trans,
+						   int error);
+
+/**
+ * A transaction. A sending command that will initiate the transaction.
+ */
+struct _MsnTransaction
+{
+	MsnCmdProc *cmdproc;
+	unsigned int trId;
+
+	char *command;
+	char *params;
+
+	int timer;
+
+	void *data; /**< The data to be used on the different callbacks. */
+	GHashTable *callbacks;
+	gboolean has_custom_callbacks;
+	MsnErrorCb error_cb;
+	MsnTimeoutCb timeout_cb;
+
+	char *payload;
+	size_t payload_len;
+
+	GQueue *queue;
+	MsnCommand *pendent_cmd; /**< The command that is waiting for the result of
+							   this transaction. */
+};
+
+MsnTransaction *msn_transaction_new(MsnCmdProc *cmdproc,
+									const char *command,
+									const char *format, ...);
+void msn_transaction_destroy(MsnTransaction *trans);
+
+char *msn_transaction_to_string(MsnTransaction *trans);
+void msn_transaction_queue_cmd(MsnTransaction *trans, MsnCommand *cmd);
+void msn_transaction_unqueue_cmd(MsnTransaction *trans, MsnCmdProc *cmdproc);
+void msn_transaction_set_payload(MsnTransaction *trans,
+								 const char *payload, int payload_len);
+void msn_transaction_set_data(MsnTransaction *trans, void *data);
+void msn_transaction_add_cb(MsnTransaction *trans, char *answer,
+							MsnTransCb cb);
+void msn_transaction_set_error_cb(MsnTransaction *trans, MsnErrorCb cb);
+void msn_transaction_set_timeout_cb(MsnTransaction *trans, MsnTimeoutCb cb);
+
+#endif /* _MSN_TRANSACTION_H */
============================================================
--- libpurple/protocols/msnp9/user.c	bd0a32e939658130a876b77028bf17c07beef542
+++ libpurple/protocols/msnp9/user.c	bd0a32e939658130a876b77028bf17c07beef542
@@ -0,0 +1,392 @@
+/**
+ * @file user.c User functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "user.h"
+#include "slp.h"
+
+MsnUser *
+msn_user_new(MsnUserList *userlist, const char *passport,
+			 const char *store_name)
+{
+	MsnUser *user;
+
+	user = g_new0(MsnUser, 1);
+
+	user->userlist = userlist;
+
+	msn_user_set_passport(user, passport);
+	msn_user_set_store_name(user, store_name);
+
+	/*
+	 * XXX This seems to reset the friendly name from what it should be
+	 *     to the passport when moving users. So, screw it :)
+	 */
+#if 0
+	if (name != NULL)
+		msn_user_set_name(user, name);
+#endif
+
+	return user;
+}
+
+void
+msn_user_destroy(MsnUser *user)
+{
+	g_return_if_fail(user != NULL);
+
+	if (user->clientcaps != NULL)
+		g_hash_table_destroy(user->clientcaps);
+
+	if (user->group_ids != NULL)
+		g_list_free(user->group_ids);
+
+	if (user->msnobj != NULL)
+		msn_object_destroy(user->msnobj);
+
+	g_free(user->passport);
+	g_free(user->friendly_name);
+	g_free(user->store_name);
+	g_free(user->phone.home);
+	g_free(user->phone.work);
+	g_free(user->phone.mobile);
+
+	g_free(user);
+}
+
+void
+msn_user_update(MsnUser *user)
+{
+	PurpleAccount *account;
+
+	account = user->userlist->session->account;
+
+	if (user->status != NULL) {
+		if (!strcmp(user->status, "offline") && user->mobile) {
+			purple_prpl_got_user_status(account, user->passport, "offline", NULL);
+			purple_prpl_got_user_status(account, user->passport, "mobile", NULL);
+		} else {
+			purple_prpl_got_user_status(account, user->passport, user->status, NULL);
+			purple_prpl_got_user_status_deactive(account, user->passport, "mobile");
+		}
+	}
+
+	if (user->idle)
+		purple_prpl_got_user_idle(account, user->passport, TRUE, -1);
+	else
+		purple_prpl_got_user_idle(account, user->passport, FALSE, 0);
+}
+
+void
+msn_user_set_state(MsnUser *user, const char *state)
+{
+	const char *status;
+
+	if (!g_ascii_strcasecmp(state, "BSY"))
+		status = "busy";
+	else if (!g_ascii_strcasecmp(state, "BRB"))
+		status = "brb";
+	else if (!g_ascii_strcasecmp(state, "AWY"))
+		status = "away";
+	else if (!g_ascii_strcasecmp(state, "PHN"))
+		status = "phone";
+	else if (!g_ascii_strcasecmp(state, "LUN"))
+		status = "lunch";
+	else
+		status = "available";
+
+	if (!g_ascii_strcasecmp(state, "IDL"))
+		user->idle = TRUE;
+	else
+		user->idle = FALSE;
+
+	user->status = status;
+}
+
+void
+msn_user_set_passport(MsnUser *user, const char *passport)
+{
+	g_return_if_fail(user != NULL);
+
+	g_free(user->passport);
+	user->passport = g_strdup(passport);
+}
+
+void
+msn_user_set_friendly_name(MsnUser *user, const char *name)
+{
+	g_return_if_fail(user != NULL);
+
+	g_free(user->friendly_name);
+	user->friendly_name = g_strdup(name);
+}
+
+void
+msn_user_set_store_name(MsnUser *user, const char *name)
+{
+	g_return_if_fail(user != NULL);
+
+	g_free(user->store_name);
+	user->store_name = g_strdup(name);
+}
+
+void
+msn_user_set_buddy_icon(MsnUser *user, PurpleStoredImage *img)
+{
+	MsnObject *msnobj = msn_user_get_object(user);
+
+	g_return_if_fail(user != NULL);
+
+	if (img == NULL)
+		msn_user_set_object(user, NULL);
+	else
+	{
+		PurpleCipherContext *ctx;
+		char *buf;
+		gconstpointer data = purple_imgstore_get_data(img);
+		size_t size = purple_imgstore_get_size(img);
+		char *base64;
+		unsigned char digest[20];
+
+		if (msnobj == NULL)
+		{
+			msnobj = msn_object_new();
+			msn_object_set_local(msnobj);
+			msn_object_set_type(msnobj, MSN_OBJECT_USERTILE);
+			msn_object_set_location(msnobj, "TFR2C2.tmp");
+			msn_object_set_creator(msnobj, msn_user_get_passport(user));
+
+			msn_user_set_object(user, msnobj);
+		}
+
+		msn_object_set_image(msnobj, img);
+
+		/* Compute the SHA1D field. */
+		memset(digest, 0, sizeof(digest));
+
+		ctx = purple_cipher_context_new_by_name("sha1", NULL);
+		purple_cipher_context_append(ctx, data, size);
+		purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
+
+		base64 = purple_base64_encode(digest, sizeof(digest));
+		msn_object_set_sha1d(msnobj, base64);
+		g_free(base64);
+
+		msn_object_set_size(msnobj, size);
+
+		/* Compute the SHA1C field. */
+		buf = g_strdup_printf(
+			"Creator%sSize%dType%dLocation%sFriendly%sSHA1D%s",
+			msn_object_get_creator(msnobj),
+			msn_object_get_size(msnobj),
+			msn_object_get_type(msnobj),
+			msn_object_get_location(msnobj),
+			msn_object_get_friendly(msnobj),
+			msn_object_get_sha1d(msnobj));
+
+		memset(digest, 0, sizeof(digest));
+
+		purple_cipher_context_reset(ctx, NULL);
+		purple_cipher_context_append(ctx, (const guchar *)buf, strlen(buf));
+		purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
+		purple_cipher_context_destroy(ctx);
+		g_free(buf);
+
+		base64 = purple_base64_encode(digest, sizeof(digest));
+		msn_object_set_sha1c(msnobj, base64);
+		g_free(base64);
+	}
+}
+
+void
+msn_user_add_group_id(MsnUser *user, int id)
+{
+	MsnUserList *userlist;
+	PurpleAccount *account;
+	PurpleBuddy *b;
+	PurpleGroup *g;
+	const char *passport;
+	const char *group_name;
+
+	g_return_if_fail(user != NULL);
+	g_return_if_fail(id >= 0);
+
+	user->group_ids = g_list_append(user->group_ids, GINT_TO_POINTER(id));
+
+	userlist = user->userlist;
+	account = userlist->session->account;
+	passport = msn_user_get_passport(user);
+
+	group_name = msn_userlist_find_group_name(userlist, id);
+
+	g = purple_find_group(group_name);
+
+	if ((id == 0) && (g == NULL))
+	{
+		g = purple_group_new(group_name);
+		purple_blist_add_group(g, NULL);
+	}
+
+	b = purple_find_buddy_in_group(account, passport, g);
+
+	if (b == NULL)
+	{
+		b = purple_buddy_new(account, passport, NULL);
+
+		purple_blist_add_buddy(b, NULL, g, NULL);
+	}
+
+	b->proto_data = user;
+}
+
+void
+msn_user_remove_group_id(MsnUser *user, int id)
+{
+	g_return_if_fail(user != NULL);
+	g_return_if_fail(id >= 0);
+
+	user->group_ids = g_list_remove(user->group_ids, GINT_TO_POINTER(id));
+}
+
+void
+msn_user_set_home_phone(MsnUser *user, const char *number)
+{
+	g_return_if_fail(user != NULL);
+
+	if (user->phone.home != NULL)
+		g_free(user->phone.home);
+
+	user->phone.home = (number == NULL ? NULL : g_strdup(number));
+}
+
+void
+msn_user_set_work_phone(MsnUser *user, const char *number)
+{
+	g_return_if_fail(user != NULL);
+
+	if (user->phone.work != NULL)
+		g_free(user->phone.work);
+
+	user->phone.work = (number == NULL ? NULL : g_strdup(number));
+}
+
+void
+msn_user_set_mobile_phone(MsnUser *user, const char *number)
+{
+	g_return_if_fail(user != NULL);
+
+	if (user->phone.mobile != NULL)
+		g_free(user->phone.mobile);
+
+	user->phone.mobile = (number == NULL ? NULL : g_strdup(number));
+}
+
+void
+msn_user_set_object(MsnUser *user, MsnObject *obj)
+{
+	g_return_if_fail(user != NULL);
+
+	if (user->msnobj != NULL)
+		msn_object_destroy(user->msnobj);
+
+	user->msnobj = obj;
+
+	if (user->list_op & MSN_LIST_FL_OP)
+		msn_queue_buddy_icon_request(user);
+}
+
+void
+msn_user_set_client_caps(MsnUser *user, GHashTable *info)
+{
+	g_return_if_fail(user != NULL);
+	g_return_if_fail(info != NULL);
+
+	if (user->clientcaps != NULL)
+		g_hash_table_destroy(user->clientcaps);
+
+	user->clientcaps = info;
+}
+
+const char *
+msn_user_get_passport(const MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, NULL);
+
+	return user->passport;
+}
+
+const char *
+msn_user_get_friendly_name(const MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, NULL);
+
+	return user->friendly_name;
+}
+
+const char *
+msn_user_get_store_name(const MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, NULL);
+
+	return user->store_name;
+}
+
+const char *
+msn_user_get_home_phone(const MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, NULL);
+
+	return user->phone.home;
+}
+
+const char *
+msn_user_get_work_phone(const MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, NULL);
+
+	return user->phone.work;
+}
+
+const char *
+msn_user_get_mobile_phone(const MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, NULL);
+
+	return user->phone.mobile;
+}
+
+MsnObject *
+msn_user_get_object(const MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, NULL);
+
+	return user->msnobj;
+}
+
+GHashTable *
+msn_user_get_client_caps(const MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, NULL);
+
+	return user->clientcaps;
+}
============================================================
--- libpurple/protocols/msnp9/user.h	87bbce26849a85fe6ebd26e5e17bae42c4584e86
+++ libpurple/protocols/msnp9/user.h	87bbce26849a85fe6ebd26e5e17bae42c4584e86
@@ -0,0 +1,284 @@
+/**
+ * @file user.h User functions
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_USER_H_
+#define _MSN_USER_H_
+
+typedef struct _MsnUser  MsnUser;
+
+#include "session.h"
+#include "object.h"
+
+#include "userlist.h"
+
+/**
+ * A user.
+ */
+struct _MsnUser
+{
+#if 0
+	MsnSession *session;    /**< The MSN session.               */
+#endif
+	MsnUserList *userlist;
+
+	char *passport;         /**< The passport account.          */
+	char *store_name;       /**< The name stored in the server. */
+	char *friendly_name;    /**< The friendly name.             */
+
+	const char *status;     /**< The state of the user.         */
+	gboolean idle;          /**< The idle state of the user.    */
+
+	struct
+	{
+		char *home;         /**< Home phone number.             */
+		char *work;         /**< Work phone number.             */
+		char *mobile;       /**< Mobile phone number.           */
+
+	} phone;
+
+	gboolean authorized;    /**< Authorized to add this user.   */
+	gboolean mobile;        /**< Signed up with MSN Mobile.     */
+
+	GList *group_ids;       /**< The group IDs.                 */
+
+	MsnObject *msnobj;      /**< The user's MSN Object.         */
+
+	GHashTable *clientcaps; /**< The client's capabilities.     */
+
+	int list_op;
+};
+
+/**************************************************************************/
+/** @name User API                                                        */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a new user structure.
+ *
+ * @param session      The MSN session.
+ * @param passport     The initial passport.
+ * @param stored_name  The initial stored name.
+ *
+ * @return A new user structure.
+ */
+MsnUser *msn_user_new(MsnUserList *userlist, const char *passport,
+					  const char *store_name);
+
+/**
+ * Destroys a user structure.
+ *
+ * @param user The user to destroy.
+ */
+void msn_user_destroy(MsnUser *user);
+
+
+/**
+ * Updates the user.
+ *
+ * Communicates with the core to update the ui, etc.
+ *
+ * @param user The user to update.
+ */
+void msn_user_update(MsnUser *user);
+
+/**
+ * Sets the new state of user.
+ *
+ * @param user The user.
+ * @param state The state string.
+ */
+void msn_user_set_state(MsnUser *user, const char *state);
+
+/**
+ * Sets the passport account for a user.
+ *
+ * @param user     The user.
+ * @param passport The passport account.
+ */
+void msn_user_set_passport(MsnUser *user, const char *passport);
+
+/**
+ * Sets the friendly name for a user.
+ *
+ * @param user The user.
+ * @param name The friendly name.
+ */
+void msn_user_set_friendly_name(MsnUser *user, const char *name);
+
+/**
+ * Sets the store name for a user.
+ *
+ * @param user The user.
+ * @param name The store name.
+ */
+void msn_user_set_store_name(MsnUser *user, const char *name);
+
+/**
+ * Sets the buddy icon for a local user.
+ *
+ * @param user     The user.
+ * @param img      The buddy icon image
+ */
+void msn_user_set_buddy_icon(MsnUser *user, PurpleStoredImage *img);
+
+/**
+ * Sets the group ID list for a user.
+ *
+ * @param user The user.
+ * @param ids  The group ID list.
+ */
+void msn_user_set_group_ids(MsnUser *user, GList *ids);
+
+/**
+ * Adds the group ID for a user.
+ *
+ * @param user The user.
+ * @param id   The group ID.
+ */
+void msn_user_add_group_id(MsnUser *user, int id);
+
+/**
+ * Removes the group ID from a user.
+ *
+ * @param user The user.
+ * @param id   The group ID.
+ */
+void msn_user_remove_group_id(MsnUser *user, int id);
+
+/**
+ * Sets the home phone number for a user.
+ *
+ * @param user   The user.
+ * @param number The home phone number.
+ */
+void msn_user_set_home_phone(MsnUser *user, const char *number);
+
+/**
+ * Sets the work phone number for a user.
+ *
+ * @param user   The user.
+ * @param number The work phone number.
+ */
+void msn_user_set_work_phone(MsnUser *user, const char *number);
+
+/**
+ * Sets the mobile phone number for a user.
+ *
+ * @param user   The user.
+ * @param number The mobile phone number.
+ */
+void msn_user_set_mobile_phone(MsnUser *user, const char *number);
+
+/**
+ * Sets the MSNObject for a user.
+ *
+ * @param user The user.
+ * @param obj  The MSNObject.
+ */
+void msn_user_set_object(MsnUser *user, MsnObject *obj);
+
+/**
+ * Sets the client information for a user.
+ *
+ * @param user The user.
+ * @param info The client information.
+ */
+void msn_user_set_client_caps(MsnUser *user, GHashTable *info);
+
+
+/**
+ * Returns the passport account for a user.
+ *
+ * @param user The user.
+ *
+ * @return The passport account.
+ */
+const char *msn_user_get_passport(const MsnUser *user);
+
+/**
+ * Returns the friendly name for a user.
+ *
+ * @param user The user.
+ *
+ * @return The friendly name.
+ */
+const char *msn_user_get_friendly_name(const MsnUser *user);
+
+/**
+ * Returns the store name for a user.
+ *
+ * @param user The user.
+ *
+ * @return The store name.
+ */
+const char *msn_user_get_store_name(const MsnUser *user);
+
+/**
+ * Returns the home phone number for a user.
+ *
+ * @param user The user.
+ *
+ * @return The user's home phone number.
+ */
+const char *msn_user_get_home_phone(const MsnUser *user);
+
+/**
+ * Returns the work phone number for a user.
+ *
+ * @param user The user.
+ *
+ * @return The user's work phone number.
+ */
+const char *msn_user_get_work_phone(const MsnUser *user);
+
+/**
+ * Returns the mobile phone number for a user.
+ *
+ * @param user The user.
+ *
+ * @return The user's mobile phone number.
+ */
+const char *msn_user_get_mobile_phone(const MsnUser *user);
+
+/**
+ * Returns the MSNObject for a user.
+ *
+ * @param user The user.
+ *
+ * @return The MSNObject.
+ */
+MsnObject *msn_user_get_object(const MsnUser *user);
+
+/**
+ * Returns the client information for a user.
+ *
+ * @param user The user.
+ *
+ * @return The client information.
+ */
+GHashTable *msn_user_get_client_caps(const MsnUser *user);
+
+/*@}*/
+
+#endif /* _MSN_USER_H_ */
============================================================
--- libpurple/protocols/msnp9/userlist.c	326580aca1caa4d40a247dbc31880e4091a6bc9b
+++ libpurple/protocols/msnp9/userlist.c	326580aca1caa4d40a247dbc31880e4091a6bc9b
@@ -0,0 +1,699 @@
+/**
+ * @file userlist.c MSN user list support
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#include "msn.h"
+#include "userlist.h"
+
+const char *lists[] = { "FL", "AL", "BL", "RL" };
+
+typedef struct
+{
+	PurpleConnection *gc;
+	char *who;
+	char *friendly;
+
+} MsnPermitAdd;
+
+/**************************************************************************
+ * Callbacks
+ **************************************************************************/
+static void
+msn_accept_add_cb(gpointer data)
+{
+	MsnPermitAdd *pa = data;
+	MsnSession *session = pa->gc->proto_data;
+	MsnUserList *userlist = session->userlist;
+
+	msn_userlist_add_buddy(userlist, pa->who, MSN_LIST_AL, NULL);
+
+	g_free(pa->who);
+	g_free(pa->friendly);
+	g_free(pa);
+}
+
+static void
+msn_cancel_add_cb(gpointer data)
+{
+	MsnPermitAdd *pa = data;
+	MsnSession *session = pa->gc->proto_data;
+	MsnUserList *userlist = session->userlist;
+
+	msn_userlist_add_buddy(userlist, pa->who, MSN_LIST_BL, NULL);
+
+	g_free(pa->who);
+	g_free(pa->friendly);
+	g_free(pa);
+}
+
+static void
+got_new_entry(PurpleConnection *gc, const char *passport, const char *friendly)
+{
+	MsnPermitAdd *pa;
+
+	pa = g_new0(MsnPermitAdd, 1);
+	pa->who = g_strdup(passport);
+	pa->friendly = g_strdup(friendly);
+	pa->gc = gc;
+	
+	purple_account_request_authorization(purple_connection_get_account(gc), passport, NULL, friendly, NULL,
+					   purple_find_buddy(purple_connection_get_account(gc), passport) != NULL,
+					   msn_accept_add_cb, msn_cancel_add_cb, pa);
+}
+
+/**************************************************************************
+ * Utility functions
+ **************************************************************************/
+
+static gboolean
+user_is_in_group(MsnUser *user, int group_id)
+{
+	if (user == NULL)
+		return FALSE;
+
+	if (group_id < 0)
+		return FALSE;
+
+	if (g_list_find(user->group_ids, GINT_TO_POINTER(group_id)))
+		return TRUE;
+
+	return FALSE;
+}
+
+static gboolean
+user_is_there(MsnUser *user, int list_id, int group_id)
+{
+	int list_op;
+
+	if (user == NULL)
+		return FALSE;
+
+	list_op = 1 << list_id;
+
+	if (!(user->list_op & list_op))
+		return FALSE;
+
+	if (list_id == MSN_LIST_FL)
+	{
+		if (group_id >= 0)
+			return user_is_in_group(user, group_id);
+	}
+
+	return TRUE;
+}
+
+static const char*
+get_store_name(MsnUser *user)
+{
+	const char *store_name;
+
+	g_return_val_if_fail(user != NULL, NULL);
+
+	store_name = msn_user_get_store_name(user);
+
+	if (store_name != NULL)
+		store_name = purple_url_encode(store_name);
+	else
+		store_name = msn_user_get_passport(user);
+
+	/* this might be a bit of a hack, but it should prevent notification server
+	 * disconnections for people who have buddies with insane friendly names
+	 * who added you to their buddy list from being disconnected. Stu. */
+	/* Shx: What? Isn't the store_name obtained from the server, and hence it's
+	 * below the BUDDY_ALIAS_MAXLEN ? */
+	/* Stu: yeah, that's why it's a bit of a hack, as you pointed out, we're
+	 * probably decoding the incoming store_name wrong, or something. bleh. */
+
+	if (strlen(store_name) > BUDDY_ALIAS_MAXLEN)
+		store_name = msn_user_get_passport(user);
+
+	return store_name;
+}
+
+static void
+msn_request_add_group(MsnUserList *userlist, const char *who,
+					  const char *old_group_name, const char *new_group_name)
+{
+	MsnCmdProc *cmdproc;
+	MsnTransaction *trans;
+	MsnMoveBuddy *data;
+
+	cmdproc = userlist->session->notification->cmdproc;
+	data = g_new0(MsnMoveBuddy, 1);
+
+	data->who = g_strdup(who);
+
+	if (old_group_name)
+		data->old_group_name = g_strdup(old_group_name);
+
+	trans = msn_transaction_new(cmdproc, "ADG", "%s %d",
+								purple_url_encode(new_group_name),
+								0);
+
+	msn_transaction_set_data(trans, data);
+
+	msn_cmdproc_send_trans(cmdproc, trans);
+}
+
+/**************************************************************************
+ * Server functions
+ **************************************************************************/
+
+MsnListId
+msn_get_list_id(const char *list)
+{
+	if (list[0] == 'F')
+		return MSN_LIST_FL;
+	else if (list[0] == 'A')
+		return MSN_LIST_AL;
+	else if (list[0] == 'B')
+		return MSN_LIST_BL;
+	else if (list[0] == 'R')
+		return MSN_LIST_RL;
+
+	return -1;
+}
+
+void
+msn_got_add_user(MsnSession *session, MsnUser *user,
+				 MsnListId list_id, int group_id)
+{
+	PurpleAccount *account;
+	const char *passport;
+	const char *friendly;
+
+	account = session->account;
+
+	passport = msn_user_get_passport(user);
+	friendly = msn_user_get_friendly_name(user);
+
+	if (list_id == MSN_LIST_FL)
+	{
+		PurpleConnection *gc;
+
+		gc = purple_account_get_connection(account);
+
+		serv_got_alias(gc, passport, friendly);
+
+		if (group_id >= 0)
+		{
+			msn_user_add_group_id(user, group_id);
+		}
+		else
+		{
+			/* session->sync->fl_users_count++; */
+		}
+	}
+	else if (list_id == MSN_LIST_AL)
+	{
+		purple_privacy_permit_add(account, passport, TRUE);
+	}
+	else if (list_id == MSN_LIST_BL)
+	{
+		purple_privacy_deny_add(account, passport, TRUE);
+	}
+	else if (list_id == MSN_LIST_RL)
+	{
+		PurpleConnection *gc;
+		PurpleConversation *convo;
+
+		gc = purple_account_get_connection(account);
+
+		purple_debug_info("msn",
+						"%s has added you to his or her buddy list.\n",
+						passport);
+
+ 		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, passport, account);
+ 		if (convo) {
+ 			PurpleBuddy *buddy;
+ 			char *msg;
+ 
+ 			buddy = purple_find_buddy(account, passport);
+ 			msg = g_strdup_printf(
+ 				_("%s has added you to his or her buddy list."),
+ 				buddy ? purple_buddy_get_contact_alias(buddy) : passport);
+ 			purple_conv_im_write(PURPLE_CONV_IM(convo), passport, msg,
+ 				PURPLE_MESSAGE_SYSTEM, time(NULL));
+ 			g_free(msg);
+ 		}
+ 
+		if (!(user->list_op & (MSN_LIST_AL_OP | MSN_LIST_BL_OP)))
+		{
+			/*
+			 * TODO: The friendly name was NULL for me when I
+			 *       looked at this.  Maybe we should use the store
+			 *       name instead? --KingAnt
+			 */
+			got_new_entry(gc, passport, friendly);
+		}
+	}
+
+	user->list_op |= (1 << list_id);
+	/* purple_user_add_list_id (user, list_id); */
+}
+
+void
+msn_got_rem_user(MsnSession *session, MsnUser *user,
+				 MsnListId list_id, int group_id)
+{
+	PurpleAccount *account;
+	const char *passport;
+
+	account = session->account;
+
+	passport = msn_user_get_passport(user);
+
+	if (list_id == MSN_LIST_FL)
+	{
+		/* TODO: When is the user totally removed? */
+		if (group_id >= 0)
+		{
+			msn_user_remove_group_id(user, group_id);
+			return;
+		}
+		else
+		{
+			/* session->sync->fl_users_count--; */
+		}
+	}
+	else if (list_id == MSN_LIST_AL)
+	{
+		purple_privacy_permit_remove(account, passport, TRUE);
+	}
+	else if (list_id == MSN_LIST_BL)
+	{
+		purple_privacy_deny_remove(account, passport, TRUE);
+	}
+	else if (list_id == MSN_LIST_RL)
+	{
+		PurpleConversation *convo;
+
+		purple_debug_info("msn",
+						"%s has removed you from his or her buddy list.\n",
+						passport);
+
+		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, passport, account);
+		if (convo) {
+			PurpleBuddy *buddy;
+			char *msg;
+
+			buddy = purple_find_buddy(account, passport);
+			msg = g_strdup_printf(
+				_("%s has removed you from his or her buddy list."),
+				buddy ? purple_buddy_get_contact_alias(buddy) : passport);
+			purple_conv_im_write(PURPLE_CONV_IM(convo), passport, msg,
+				PURPLE_MESSAGE_SYSTEM, time(NULL));
+			g_free(msg);
+		}
+	}
+
+	user->list_op &= ~(1 << list_id);
+	/* purple_user_remove_list_id (user, list_id); */
+
+	if (user->list_op == 0)
+	{
+		purple_debug_info("msn", "Buddy '%s' shall be deleted?.\n",
+						passport);
+
+	}
+}
+
+void
+msn_got_lst_user(MsnSession *session, MsnUser *user,
+				 int list_op, GSList *group_ids)
+{
+	PurpleConnection *gc;
+	PurpleAccount *account;
+	const char *passport;
+	const char *store;
+
+	account = session->account;
+	gc = purple_account_get_connection(account);
+
+	passport = msn_user_get_passport(user);
+	store = msn_user_get_store_name(user);
+
+	if (list_op & MSN_LIST_FL_OP)
+	{
+		GSList *c;
+		for (c = group_ids; c != NULL; c = g_slist_next(c))
+		{
+			int group_id;
+			group_id = GPOINTER_TO_INT(c->data);
+			msn_user_add_group_id(user, group_id);
+		}
+
+		/* FIXME: It might be a real alias */
+		/* Umm, what? This might fix bug #1385130 */
+		serv_got_alias(gc, passport, store);
+	}
+
+	if (list_op & MSN_LIST_AL_OP)
+	{
+		/* These are users who are allowed to see our status. */
+		purple_privacy_deny_remove(account, passport, TRUE);
+		purple_privacy_permit_add(account, passport, TRUE);
+	}
+
+	if (list_op & MSN_LIST_BL_OP)
+	{
+		/* These are users who are not allowed to see our status. */
+		purple_privacy_permit_remove(account, passport, TRUE);
+		purple_privacy_deny_add(account, passport, TRUE);
+	}
+
+	if (list_op & MSN_LIST_RL_OP)
+	{
+		/* These are users who have us on their buddy list. */
+		/*
+		 * TODO: What is store name set to when this happens?
+		 *       For one of my accounts "something at hotmail.com"
+		 *       the store name was "something."  Maybe we
+		 *       should use the friendly name, instead? --KingAnt
+		 */
+
+		if (!(list_op & (MSN_LIST_AL_OP | MSN_LIST_BL_OP)))
+		{
+			got_new_entry(gc, passport, store);
+		}
+	}
+
+	user->list_op = list_op;
+}
+
+/**************************************************************************
+ * UserList functions
+ **************************************************************************/
+
+MsnUserList*
+msn_userlist_new(MsnSession *session)
+{
+	MsnUserList *userlist;
+
+	userlist = g_new0(MsnUserList, 1);
+
+	userlist->session = session;
+	userlist->buddy_icon_requests = g_queue_new();
+	
+	/* buddy_icon_window is the number of allowed simultaneous buddy icon requests.
+	 * XXX With smarter rate limiting code, we could allow more at once... 5 was the limit set when
+	 * we weren't retrieiving any more than 5 per MSN session. */
+	userlist->buddy_icon_window = 1;
+
+	return userlist;
+}
+
+void
+msn_userlist_destroy(MsnUserList *userlist)
+{
+	GList *l;
+
+	for (l = userlist->users; l != NULL; l = l->next)
+	{
+		msn_user_destroy(l->data);
+	}
+
+	g_list_free(userlist->users);
+
+	for (l = userlist->groups; l != NULL; l = l->next)
+	{
+		msn_group_destroy(l->data);
+	}
+
+	g_list_free(userlist->groups);
+
+	g_queue_free(userlist->buddy_icon_requests);
+
+	if (userlist->buddy_icon_request_timer)
+		purple_timeout_remove(userlist->buddy_icon_request_timer);
+
+	g_free(userlist);
+}
+
+void
+msn_userlist_add_user(MsnUserList *userlist, MsnUser *user)
+{
+	userlist->users = g_list_prepend(userlist->users, user);
+}
+
+void
+msn_userlist_remove_user(MsnUserList *userlist, MsnUser *user)
+{
+	userlist->users = g_list_remove(userlist->users, user);
+}
+
+MsnUser *
+msn_userlist_find_user(MsnUserList *userlist, const char *passport)
+{
+	GList *l;
+
+	g_return_val_if_fail(passport != NULL, NULL);
+
+	for (l = userlist->users; l != NULL; l = l->next)
+	{
+		MsnUser *user = (MsnUser *)l->data;
+
+		g_return_val_if_fail(user->passport != NULL, NULL);
+
+		if (!strcmp(passport, user->passport))
+			return user;
+	}
+
+	return NULL;
+}
+
+void
+msn_userlist_add_group(MsnUserList *userlist, MsnGroup *group)
+{
+	userlist->groups = g_list_append(userlist->groups, group);
+}
+
+void
+msn_userlist_remove_group(MsnUserList *userlist, MsnGroup *group)
+{
+	userlist->groups = g_list_remove(userlist->groups, group);
+}
+
+MsnGroup *
+msn_userlist_find_group_with_id(MsnUserList *userlist, int id)
+{
+	GList *l;
+
+	g_return_val_if_fail(userlist != NULL, NULL);
+	g_return_val_if_fail(id       >= 0,    NULL);
+
+	for (l = userlist->groups; l != NULL; l = l->next)
+	{
+		MsnGroup *group = l->data;
+
+		if (group->id == id)
+			return group;
+	}
+
+	return NULL;
+}
+
+MsnGroup *
+msn_userlist_find_group_with_name(MsnUserList *userlist, const char *name)
+{
+	GList *l;
+
+	g_return_val_if_fail(userlist != NULL, NULL);
+	g_return_val_if_fail(name     != NULL, NULL);
+
+	for (l = userlist->groups; l != NULL; l = l->next)
+	{
+		MsnGroup *group = l->data;
+
+		if ((group->name != NULL) && !g_ascii_strcasecmp(name, group->name))
+			return group;
+	}
+
+	return NULL;
+}
+
+int
+msn_userlist_find_group_id(MsnUserList *userlist, const char *group_name)
+{
+	MsnGroup *group;
+
+	group = msn_userlist_find_group_with_name(userlist, group_name);
+
+	if (group != NULL)
+		return msn_group_get_id(group);
+	else
+		return -1;
+}
+
+const char *
+msn_userlist_find_group_name(MsnUserList *userlist, int group_id)
+{
+	MsnGroup *group;
+
+	group = msn_userlist_find_group_with_id(userlist, group_id);
+
+	if (group != NULL)
+		return msn_group_get_name(group);
+	else
+		return NULL;
+}
+
+void
+msn_userlist_rename_group_id(MsnUserList *userlist, int group_id,
+							 const char *new_name)
+{
+	MsnGroup *group;
+
+	group = msn_userlist_find_group_with_id(userlist, group_id);
+
+	if (group != NULL)
+		msn_group_set_name(group, new_name);
+}
+
+void
+msn_userlist_remove_group_id(MsnUserList *userlist, int group_id)
+{
+	MsnGroup *group;
+
+	group = msn_userlist_find_group_with_id(userlist, group_id);
+
+	if (group != NULL)
+	{
+		msn_userlist_remove_group(userlist, group);
+		msn_group_destroy(group);
+	}
+}
+
+void
+msn_userlist_rem_buddy(MsnUserList *userlist,
+					   const char *who, int list_id, const char *group_name)
+{
+	MsnUser *user;
+	int group_id;
+	const char *list;
+
+	user = msn_userlist_find_user(userlist, who);
+	group_id = -1;
+
+	if (group_name != NULL)
+	{
+		group_id = msn_userlist_find_group_id(userlist, group_name);
+
+		if (group_id < 0)
+		{
+			/* Whoa, there is no such group. */
+			purple_debug_error("msn", "Group doesn't exist: %s\n", group_name);
+			return;
+		}
+	}
+
+	/* First we're going to check if not there. */
+	if (!(user_is_there(user, list_id, group_id)))
+	{
+		list = lists[list_id];
+		purple_debug_error("msn", "User '%s' is not there: %s\n",
+						 who, list);
+		return;
+	}
+
+	/* Then request the rem to the server. */
+	list = lists[list_id];
+
+	msn_notification_rem_buddy(userlist->session->notification, list, who, group_id);
+}
+
+void
+msn_userlist_add_buddy(MsnUserList *userlist,
+					   const char *who, int list_id,
+					   const char *group_name)
+{
+	MsnUser *user;
+	int group_id;
+	const char *list;
+	const char *store_name;
+
+	group_id = -1;
+
+	if (!purple_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
+		 * won't cause too many problems if we just ignore it */
+		if (list_id == MSN_LIST_FL)
+		{
+			char *str = g_strdup_printf(_("Unable to add \"%s\"."), who);
+			purple_notify_error(NULL, NULL, str,
+							  _("The screen name specified is invalid."));
+			g_free(str);
+		}
+
+		return;
+	}
+
+	if (group_name != NULL)
+	{
+		group_id = msn_userlist_find_group_id(userlist, group_name);
+
+		if (group_id < 0)
+		{
+			/* Whoa, we must add that group first. */
+			msn_request_add_group(userlist, who, NULL, group_name);
+			return;
+		}
+	}
+
+	user = msn_userlist_find_user(userlist, who);
+
+	/* First we're going to check if it's already there. */
+	if (user_is_there(user, list_id, group_id))
+	{
+		list = lists[list_id];
+		purple_debug_error("msn", "User '%s' is already there: %s\n", who, list);
+		return;
+	}
+
+	store_name = (user != NULL) ? get_store_name(user) : who;
+
+	/* Then request the add to the server. */
+	list = lists[list_id];
+
+	msn_notification_add_buddy(userlist->session->notification, list, who,
+							   store_name, group_id);
+}
+
+void
+msn_userlist_move_buddy(MsnUserList *userlist, const char *who,
+						const char *old_group_name, const char *new_group_name)
+{
+	int new_group_id;
+
+	new_group_id = msn_userlist_find_group_id(userlist, new_group_name);
+
+	if (new_group_id < 0)
+	{
+		msn_request_add_group(userlist, who, old_group_name, new_group_name);
+		return;
+	}
+
+	msn_userlist_add_buddy(userlist, who, MSN_LIST_FL, new_group_name);
+	msn_userlist_rem_buddy(userlist, who, MSN_LIST_FL, old_group_name);
+}
============================================================
--- libpurple/protocols/msnp9/userlist.h	4008bd8b88aace9e87aa1d02e5fbba11e5d9ff13
+++ libpurple/protocols/msnp9/userlist.h	4008bd8b88aace9e87aa1d02e5fbba11e5d9ff13
@@ -0,0 +1,103 @@
+/**
+ * @file userlist.h MSN user list support
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+#ifndef _MSN_USERLIST_H_
+#define _MSN_USERLIST_H_
+
+typedef struct _MsnUserList MsnUserList;
+
+#include "cmdproc.h"
+#include "user.h"
+#include "group.h"
+
+typedef enum
+{
+	MSN_LIST_FL,
+	MSN_LIST_AL,
+	MSN_LIST_BL,
+	MSN_LIST_RL
+
+} MsnListId;
+
+typedef struct
+{
+	char *who;
+	char *old_group_name;
+
+} MsnMoveBuddy;
+
+struct _MsnUserList
+{
+	MsnSession *session;
+
+	/* MsnUsers *users; */
+	/* MsnGroups *groups; */
+
+	GList *users;
+	GList *groups;
+
+	GQueue *buddy_icon_requests;
+	int buddy_icon_window;
+	guint buddy_icon_request_timer;
+
+	int fl_users_count;
+
+};
+
+MsnListId msn_get_list_id(const char *list);
+
+void msn_got_add_user(MsnSession *session, MsnUser *user,
+					  MsnListId list_id, int group_id);
+void msn_got_rem_user(MsnSession *session, MsnUser *user,
+					  MsnListId list_id, int group_id);
+void msn_got_lst_user(MsnSession *session, MsnUser *user,
+					  int list_op, GSList *group_ids);
+
+MsnUserList *msn_userlist_new(MsnSession *session);
+void msn_userlist_destroy(MsnUserList *userlist);
+void msn_userlist_add_user(MsnUserList *userlist, MsnUser *user);
+void msn_userlist_remove_user(MsnUserList *userlist, MsnUser *user);
+MsnUser *msn_userlist_find_user(MsnUserList *userlist,
+								const char *passport);
+void msn_userlist_add_group(MsnUserList *userlist, MsnGroup *group);
+void msn_userlist_remove_group(MsnUserList *userlist, MsnGroup *group);
+MsnGroup *msn_userlist_find_group_with_id(MsnUserList *userlist, int id);
+MsnGroup *msn_userlist_find_group_with_name(MsnUserList *userlist,
+											const char *name);
+int msn_userlist_find_group_id(MsnUserList *userlist,
+							   const char *group_name);
+const char *msn_userlist_find_group_name(MsnUserList *userlist,
+										 int group_id);
+void msn_userlist_rename_group_id(MsnUserList *userlist, int group_id,
+								  const char *new_name);
+void msn_userlist_remove_group_id(MsnUserList *userlist, int group_id);
+
+void msn_userlist_rem_buddy(MsnUserList *userlist, const char *who,
+							int list_id, const char *group_name);
+void msn_userlist_add_buddy(MsnUserList *userlist, const char *who,
+							int list_id, const char *group_name);
+void msn_userlist_move_buddy(MsnUserList *userlist, const char *who,
+							 const char *old_group_name,
+							 const char *new_group_name);
+
+#endif /* _MSN_USERLIST_H_ */
============================================================
--- configure.ac	97b47128fe6c95a900816af9be23db8e56d920e9
+++ configure.ac	317f1806d9a3e17c165f984e1a7d14b19b5f1349
@@ -929,6 +929,7 @@ AC_SUBST(GADU_CFLAGS)
 AC_SUBST(GADU_LIBS)
 AC_SUBST(GADU_CFLAGS)
 
+AC_ARG_ENABLE(msnp14,[AC_HELP_STRING([--disable-msnp14], [Disable the newer MSNP14 protocol])],,enable_msnp14=yes)
 
 AC_ARG_ENABLE(distrib,,,enable_distrib=no)
 AM_CONDITIONAL(DISTRIB, test "x$enable_distrib" = "xyes")
@@ -949,6 +950,9 @@ fi
 		STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'`
 	fi
 fi
+if test "x$enable_msnp14" != "xyes" ; then
+	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/msn/msnp9/'`
+fi
 if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then
 	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/silc/silc10/'`
 fi
@@ -973,6 +977,8 @@ for i in $STATIC_PRPLS ; do
 			STATIC_LINK_LIBS="$STATIC_LINK_LIBS \$(top_builddir)/libpurple/protocols/$i/lib${i}purple.a"
 		elif test "x$i" = "xsilc10"; then
 			STATIC_LINK_LIBS="$STATIC_LINK_LIBS \$(top_builddir)/libpurple/protocols/$i/libsilcpurple.a"
+		elif test "x$i" = "xmsnp9"; then
+			STATIC_LINK_LIBS="$STATIC_LINK_LIBS \$(top_builddir)/libpurple/protocols/$i/libmsn.a"
 		else
 			STATIC_LINK_LIBS="$STATIC_LINK_LIBS \$(top_builddir)/libpurple/protocols/$i/lib$i.a"
 		fi
@@ -985,6 +991,7 @@ for i in $STATIC_PRPLS ; do
 		irc)		static_irc=yes ;;
 		jabber)		static_jabber=yes ;;
 		msn)		static_msn=yes ;;
+		msnp9)		static_msn=yes ;;
 		myspace)	static_myspace=yes ;;
 		novell)		static_novell=yes ;;
 		oscar)		static_oscar=yes ;;
@@ -1032,6 +1039,9 @@ fi
 		DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'`
 	fi
 fi
+if test "x$enable_msnp14" != "xyes" ; then
+	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/msn/msnp9/'`
+fi
 if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then
 	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/silc/silc10/'`
 fi
@@ -1046,6 +1056,7 @@ for i in $DYNAMIC_PRPLS ; do
 		irc)		dynamic_irc=yes ;;
 		jabber)		dynamic_jabber=yes ;;
 		msn)		dynamic_msn=yes ;;
+		msnp9)		dynamic_msn=yes ;;
 		myspace)	dynamic_myspace=yes ;;
 		novell)		dynamic_novell=yes ;;
 		oscar)		dynamic_oscar=yes ;;
@@ -2235,6 +2246,7 @@ AC_OUTPUT([Makefile
 		   libpurple/protocols/irc/Makefile
 		   libpurple/protocols/jabber/Makefile
 		   libpurple/protocols/msn/Makefile
+		   libpurple/protocols/msnp9/Makefile
 		   libpurple/protocols/myspace/Makefile
 		   libpurple/protocols/novell/Makefile
 		   libpurple/protocols/null/Makefile


More information about the Commits mailing list