changeset 9193:502707ca1836

[gaim-migrate @ 9988] Patch by Felipe Contreras to add MSN file transfer and buddy icons. Please test and report any bugs! committer: Tailor Script <tailor@pidgin.im>
author Christian Hammond <chipx86@chipx86.com>
date Sun, 06 Jun 2004 02:39:08 +0000
parents 5655dcd94d0f
children 364aa73323b5
files ChangeLog src/protocols/msn/Makefile.am src/protocols/msn/Makefile.mingw src/protocols/msn/cmdproc.c src/protocols/msn/cmdproc.h src/protocols/msn/command.c src/protocols/msn/command.h src/protocols/msn/directconn.c src/protocols/msn/directconn.h src/protocols/msn/group.c src/protocols/msn/group.h src/protocols/msn/httpmethod.c src/protocols/msn/msg.c src/protocols/msn/msg.h src/protocols/msn/msn.c src/protocols/msn/msn.h src/protocols/msn/nexus.c src/protocols/msn/nexus.h src/protocols/msn/notification.c src/protocols/msn/notification.h src/protocols/msn/object.c src/protocols/msn/object.h src/protocols/msn/servconn.c src/protocols/msn/servconn.h src/protocols/msn/session.c src/protocols/msn/session.h src/protocols/msn/slp.c src/protocols/msn/slp.h src/protocols/msn/slpcall.c src/protocols/msn/slpcall.h src/protocols/msn/slplink.c src/protocols/msn/slplink.h src/protocols/msn/slpmsg.c src/protocols/msn/slpmsg.h src/protocols/msn/slpsession.c src/protocols/msn/slpsession.h src/protocols/msn/state.c src/protocols/msn/state.h src/protocols/msn/switchboard.c src/protocols/msn/switchboard.h src/protocols/msn/sync.c src/protocols/msn/sync.h src/protocols/msn/table.h src/protocols/msn/transaction.c src/protocols/msn/transaction.h src/protocols/msn/user.c src/protocols/msn/user.h src/protocols/msn/userlist.c src/protocols/msn/userlist.h src/protocols/msn/utils.c src/protocols/msn/utils.h
diffstat 51 files changed, 6030 insertions(+), 2765 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sun Jun 06 02:16:08 2004 +0000
+++ b/ChangeLog	Sun Jun 06 02:39:08 2004 +0000
@@ -13,6 +13,8 @@
 	* Show timestamps now has a per-conversation option in addition
 	  to the global one, bringing it in line with the other conver-
 	  sation options (Stu Tomlinson).
+	* Added MSN buddy icons (Felipe Contreras)
+	* Added MSN file transfer (Felipe Contreras)
 
 	Bug Fixes:
 	* Non-looping animated icons no longer cause Gaim to freeze
--- a/src/protocols/msn/Makefile.am	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/Makefile.am	Sun Jun 06 02:39:08 2004 +0000
@@ -8,6 +8,8 @@
 	cmdproc.h \
 	command.c \
 	command.h \
+	directconn.c \
+	directconn.h \
 	error.c \
 	error.h \
 	group.c \
@@ -20,30 +22,42 @@
 	msg.h \
 	msn.c \
 	msn.h \
-	msnobject.c \
-	msnobject.h \
-	msnslp.c \
-	msnslp.h \
+	notification.c \
+	notification.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 \
+	transaction.c \
+	transaction.h \
 	table.c \
 	table.h \
-	transaction.c \
-	transaction.h \
 	user.c \
 	user.h \
+	userlist.c \
+	userlist.h \
 	utils.c \
 	utils.h
 
--- a/src/protocols/msn/Makefile.mingw	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/Makefile.mingw	Sun Jun 06 02:39:08 2004 +0000
@@ -70,24 +70,30 @@
 
 C_SRC =			cmdproc.c \
 			command.c \
+			dispatch.c \
 			error.c \
 			group.c \
 			history.c \
 			httpmethod.c \
 			msg.c \
 			msn.c \
-			msnobject.c \
-			msnslp.c \
+			nexus.c \
 			notification.c \
-			nexus.c \
+			object.c \
 			page.c \
 			servconn.c \
 			session.c \
+			slp.c \
+			slpcall.c \
+			slplink.c \
+			slpmsg.c \
+			slpsession.c \
 			state.c \
 			switchboard.c \
 			table.c \
 			transaction.c \
 			user.c \
+			userlist.c \
 			utils.c
 
 
--- a/src/protocols/msn/cmdproc.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/cmdproc.c	Sun Jun 06 02:39:08 2004 +0000
@@ -22,13 +22,6 @@
 #include "msn.h"
 #include "cmdproc.h"
 
-typedef struct
-{
-	char *command;
-	MsnMessage *msg;
-
-} MsnQueueEntry;
-
 MsnCmdProc *
 msn_cmdproc_new(MsnSession *session)
 {
@@ -56,13 +49,6 @@
 
 	g_queue_free(cmdproc->txqueue);
 
-	while (cmdproc->msg_queue != NULL)
-	{
-		MsnQueueEntry *entry = cmdproc->msg_queue->data;
-
-		msn_cmdproc_unqueue_message(cmdproc, entry->msg);
-	}
-
 	msn_history_destroy(cmdproc->history);
 }
 
@@ -84,8 +70,6 @@
 	g_return_if_fail(cmdproc != NULL);
 	g_return_if_fail(trans   != NULL);
 
-	gaim_debug_info("msn", "Appending command to queue.\n");
-	
 	g_queue_push_tail(cmdproc->txqueue, trans);
 }
 
@@ -120,7 +104,7 @@
 {
 	MsnServConn *servconn;
 	char *data;
-	gsize len;
+	size_t len;
 
 	g_return_if_fail(cmdproc != NULL);
 	g_return_if_fail(trans != NULL);
@@ -218,20 +202,23 @@
 msn_cmdproc_process_payload(MsnCmdProc *cmdproc, char *payload,
 							int payload_len)
 {
-	g_return_if_fail(cmdproc             != NULL);
-	g_return_if_fail(cmdproc->payload_cb != NULL);
+	MsnCommand *last;
+
+	g_return_if_fail(cmdproc != NULL);
 
-	cmdproc->payload_cb(cmdproc, payload, payload_len);
+	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)
 {
-	MsnServConn *servconn;
 	MsnMsgCb cb;
 
-	servconn = cmdproc->servconn;
-
 	cb = g_hash_table_lookup(cmdproc->cbs_table->msgs,
 							 msn_message_get_content_type(msg));
 
@@ -249,14 +236,8 @@
 void
 msn_cmdproc_process_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	MsnSession *session;
-	MsnServConn *servconn;
+	MsnTransCb cb = NULL;
 	MsnTransaction *trans = NULL;
-	MsnTransCb cb = NULL;
-	GSList *l, *l_next = NULL;
-
-	session = cmdproc->session;
-	servconn = cmdproc->servconn;
 
 	if (cmd->trId)
 		trans = msn_history_find(cmdproc->history, cmd->trId);
@@ -291,53 +272,37 @@
 	if (cmdproc->cbs_table->async != NULL)
 		cb = g_hash_table_lookup(cmdproc->cbs_table->async, cmd->command);
 
-	if (cb == NULL && cmd->trId)
+	if (cb == NULL && trans != NULL)
 	{
-		if (trans != NULL)
-		{
-			cmd->trans = trans;
+		cmd->trans = trans;
 
-			if (trans->callbacks)
-				cb = g_hash_table_lookup(trans->callbacks, cmd->command);
-		}
+		if (trans->callbacks != NULL)
+			cb = g_hash_table_lookup(trans->callbacks, cmd->command);
 	}
 
 	if (cb != NULL)
+	{
 		cb(cmdproc, cmd);
+	}
 	else
 	{
 		gaim_debug_warning("msn", "Unhandled command '%s'\n",
 						   cmd->command);
-
-		return;
 	}
 
-	if (g_list_find(session->servconns, servconn) == NULL)
-		return;
-
-	/* Process all queued messages that are waiting on this command. */
-	for (l = cmdproc->msg_queue; l != NULL; l = l_next)
-	{
-		MsnQueueEntry *entry = l->data;
-		MsnMessage *msg;
-
-		l_next = l->next;
+#if 1
+	/* TODO this is really ugly */
+	/* Since commands have not stored payload and we need it for pendent
+	 * commands at the time we process again the same command we will try
+	 * to read again the payload of payload_len size but we will actually
+	 * read sometime else, and reading from server syncronization goes to
+	 * hell. */
+	/* Now we store the payload in the command when we queue them :D */
 
-		if (entry->command == NULL ||
-			!g_ascii_strcasecmp(entry->command, cmd->command))
-		{
-			msg = entry->msg;
-
-			msn_message_ref(msg);
-
-			msn_cmdproc_process_msg(cmdproc, msg);
-
-			msn_cmdproc_unqueue_message(cmdproc, entry->msg);
-
-			msn_message_destroy(msg);
-			entry->msg = NULL;
-		}
-	}
+	if (trans != NULL && trans->pendent_cmd != NULL)
+		if (cmdproc->ready)
+			msn_transaction_unqueue_cmd(trans, cmdproc);
+#endif
 }
 
 void
@@ -354,50 +319,29 @@
 }
 
 void
-msn_cmdproc_queue_message(MsnCmdProc *cmdproc, const char *command,
-						  MsnMessage *msg)
+msn_cmdproc_show_error(MsnCmdProc *cmdproc, int error)
 {
-	MsnQueueEntry *entry;
-
-	g_return_if_fail(cmdproc != NULL);
-	g_return_if_fail(msg != NULL);
+	GaimConnection *gc =
+		gaim_account_get_connection(cmdproc->session->account);
 
-	entry          = g_new0(MsnQueueEntry, 1);
-	entry->msg     = msg;
-	entry->command = (command == NULL ? NULL : g_strdup(command));
+	char *tmp;
 
-	cmdproc->msg_queue = g_slist_append(cmdproc->msg_queue, entry);
-
-	msn_message_ref(msg);
-}
+	tmp = NULL;
 
-void
-msn_cmdproc_unqueue_message(MsnCmdProc *cmdproc, MsnMessage *msg)
-{
-	MsnQueueEntry *entry = NULL;
-	GSList *l;
-
-	g_return_if_fail(cmdproc != NULL);
-	g_return_if_fail(msg != NULL);
-
-	for (l = cmdproc->msg_queue; l != NULL; l = l->next)
+	switch (error)
 	{
-		entry = l->data;
-
-		if (entry->msg == msg)
+		case MSN_ERROR_MISC:
+			tmp = _("Miscellaneous error"); break;
+		case MSN_ERROR_SIGNOTHER:
+			tmp = _("You have signed on from another location."); break;
+		case MSN_ERROR_SERVDOWN:
+			tmp = _("The MSN servers are going down temporarily."); break;
+		default:
 			break;
-
-		entry = NULL;
 	}
 
-	g_return_if_fail(entry != NULL);
-
-	msn_message_unref(msg);
-
-	cmdproc->msg_queue = g_slist_remove(cmdproc->msg_queue, entry);
-
-	if (entry->command != NULL)
-		g_free(entry->command);
-
-	g_free(entry);
+	if (tmp != NULL)
+	{
+		gaim_connection_error(gc, tmp);
+	}
 }
--- a/src/protocols/msn/cmdproc.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/cmdproc.h	Sun Jun 06 02:39:08 2004 +0000
@@ -30,10 +30,11 @@
 #include "command.h"
 #include "table.h"
 #include "history.h"
-#include "msg.h"
 
+#if 0
 typedef void (*MsnPayloadCb)(MsnCmdProc *cmdproc, char *payload,
 							 size_t len);
+#endif
 
 struct _MsnCmdProc
 {
@@ -49,13 +50,9 @@
 	char *last_trans;
 	
 	MsnTable *cbs_table;
-	MsnPayloadCb payload_cb;
+	/* MsnPayloadCb payload_cb; */
 
 	MsnHistory *history;
-
-	GSList *msg_queue;
-
-	char *temp;
 };
 
 MsnCmdProc *msn_cmdproc_new(MsnSession *session);
@@ -70,15 +67,16 @@
 					  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_queue_message(MsnCmdProc *cmdproc, const char *command,
-								MsnMessage *msg);
+void msn_cmdproc_disconnect(MsnCmdProc *cmdproc);
 
-void msn_cmdproc_unqueue_message(MsnCmdProc *cmdproc, MsnMessage *msg);
+void msn_cmdproc_show_error(MsnCmdProc *cmdproc, int error);
 
 #endif /* _MSN_CMDPROC_H_ */
--- a/src/protocols/msn/command.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/command.c	Sun Jun 06 02:39:08 2004 +0000
@@ -82,7 +82,10 @@
 		msn_command_unref(cmd);
 		return;
 	}
-	
+
+	if (cmd->payload != NULL)
+		g_free(cmd->payload);
+
 	g_free(cmd->command);
 	g_strfreev(cmd->params);
 	g_free(cmd);
--- a/src/protocols/msn/command.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/command.h	Sun Jun 06 02:39:08 2004 +0000
@@ -27,8 +27,10 @@
 #include "cmdproc.h"
 #include "transaction.h"
 
-/* typedef void (*MsnPayloadCb)(MsnCmdProc *cmdproc, MsnCommand *cmd, char
- * *payload, size_t len); */
+#if 1
+typedef void (*MsnPayloadCb)(MsnCmdProc *cmdproc, MsnCommand *cmd,
+							 char *payload, size_t len);
+#endif
 
 /**
  * A received command.
@@ -44,7 +46,11 @@
 	int ref_count;
 
 	MsnTransaction *trans;
-	/* MsnPayloadCb payload_cb; */
+
+	char *payload;
+	size_t payload_len;
+
+	MsnPayloadCb payload_cb;
 };
 
 MsnCommand *msn_command_from_string(const char *string);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/directconn.c	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,465 @@
+#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-%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);
+
+		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
+ **************************************************************************/
+
+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)
+	{
+		gaim_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)
+	{
+		gaim_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;
+}
+
+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 = 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 = 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);
+}
+
+void
+msn_directconn_process_msg(MsnDirectConn *directconn, MsnMessage *msg)
+{
+	gaim_debug_info("msn", "directconn: process_msg\n");
+
+	msn_slplink_process_msg(directconn->slplink, msg);
+}
+
+static void
+read_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnDirectConn* directconn;
+	char *body;
+	size_t len, body_len;
+
+	gaim_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 */
+		gaim_debug_error("msn", "error reading\n");
+
+		if (directconn->inpa)
+			gaim_input_remove(directconn->inpa);
+
+		close(directconn->fd);
+
+		msn_directconn_destroy(directconn);
+
+		return;
+	}
+
+	body_len = GUINT32_FROM_LE(body_len);
+
+	gaim_debug_info("msn", "body_len=%d\n", body_len);
+
+	if (body_len <= 0)
+	{
+		/* ERROR */
+		gaim_debug_error("msn", "error reading\n");
+
+		if (directconn->inpa)
+			gaim_input_remove(directconn->inpa);
+
+		close(directconn->fd);
+
+		msn_directconn_destroy(directconn);
+
+		return;
+	}
+
+	body = g_malloc(body_len);
+
+	/* Let's read the data. */
+	len = read(directconn->fd, body, body_len);
+
+	gaim_debug_info("msn", "len=%d\n", len);
+
+	if (len > 0)
+	{
+		MsnMessage *msg;
+
+#ifdef DEBUG_DC
+		str = g_strdup_printf("/home/revo/msntest/r%.4d.bin", directconn->c);
+
+		FILE *tf = 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 */
+		gaim_debug_error("msn", "error reading\n");
+
+		if (directconn->inpa)
+			gaim_input_remove(directconn->inpa);
+
+		close(directconn->fd);
+
+		msn_directconn_destroy(directconn);
+	}
+}
+
+static void
+connect_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnDirectConn* directconn;
+	int fd;
+
+	gaim_debug_misc("msn", "directconn: connect_cb: %d, %d.\n", source, cond);
+
+	directconn = data;
+
+	if (TRUE)
+	{
+		fd = source;
+	}
+	else
+	{
+		struct sockaddr_in client_addr;
+		int client;
+		fd = accept (source, (struct sockaddr *)&client_addr, &client);
+	}
+
+	directconn->fd = fd;
+
+	if (fd > 0)
+	{
+		directconn->inpa = gaim_input_add(fd, GAIM_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 */
+		gaim_debug_error("msn", "could not add input\n");
+
+		if (directconn->inpa)
+			gaim_input_remove(directconn->inpa);
+
+		close(directconn->fd);
+	}
+}
+
+gboolean
+msn_directconn_connect(MsnDirectConn *directconn, const char *host, int port)
+{
+	MsnSession *session;
+	int r;
+
+	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
+
+	r = gaim_proxy_connect(session->account, host, port, connect_cb,
+						   directconn);
+
+	if (r == 0)
+	{
+		return TRUE;
+	}
+	else
+		return FALSE;
+}
+
+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 = gaim_input_add(fd, GAIM_INPUT_READ,
+									  connect_cb, directconn);
+
+	directconn->port = port;
+	directconn->c = 0;
+}
+
+MsnDirectConn*
+msn_directconn_new(MsnSlpLink *slplink)
+{
+	MsnDirectConn *directconn;
+
+	directconn = g_new0(MsnDirectConn, 1);
+
+	directconn->slplink = slplink;
+
+	if (slplink->directconn != NULL)
+		gaim_debug_info("msn", "got_transresp: LEAK\n");
+
+	slplink->directconn = directconn;
+
+	return directconn;
+}
+
+void
+msn_directconn_destroy(MsnDirectConn *directconn)
+{
+	if (directconn->nonce != NULL)
+		g_free(directconn->nonce);
+
+	directconn->slplink->directconn = NULL;
+
+	g_free(directconn);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/directconn.h	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,36 @@
+#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;
+
+	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);
+void msn_directconn_listen(MsnDirectConn *directconn);
+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_ */
--- a/src/protocols/msn/group.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/group.c	Sun Jun 06 02:39:08 2004 +0000
@@ -23,26 +23,19 @@
 #include "group.h"
 
 MsnGroup *
-msn_group_new(MsnSession *session, int id, const char *name)
+msn_group_new(MsnUserList *userlist, int id, const char *name)
 {
 	MsnGroup *group;
 
-	g_return_val_if_fail(session != NULL, NULL);
-	g_return_val_if_fail(id >= 0,         NULL);
-	g_return_val_if_fail(name != NULL,    NULL);
+	g_return_val_if_fail(id >= 0,      NULL);
+	g_return_val_if_fail(name != NULL, NULL);
 
-	group = msn_groups_find_with_id(session->groups, id);
+	group = g_new0(MsnGroup, 1);
 
-	if (group == NULL) {
-		group = g_new0(MsnGroup, 1);
+	msn_userlist_add_group(userlist, group);
 
-		group->session = session;
-		group->id      = id;
-		group->name    = g_strdup(name);
-		group->users   = msn_users_new();
-	}
-
-	msn_group_ref(group);
+	group->id      = id;
+	group->name    = g_strdup(name);
 
 	return group;
 }
@@ -52,50 +45,10 @@
 {
 	g_return_if_fail(group != NULL);
 
-	if (group->ref_count > 0) {
-		msn_group_unref(group);
-
-		return;
-	}
-
-	if (group->session != NULL && group->session->groups != NULL)
-		msn_groups_remove(group->session->groups, group);
-
-	msn_users_destroy(group->users);
-
 	g_free(group->name);
 	g_free(group);
 }
 
-MsnGroup *
-msn_group_ref(MsnGroup *group)
-{
-	g_return_val_if_fail(group != NULL, NULL);
-
-	group->ref_count++;
-
-	return group;
-}
-
-MsnGroup *
-msn_group_unref(MsnGroup *group)
-{
-	g_return_val_if_fail(group != NULL, NULL);
-
-	if (group->ref_count <= 0)
-		return NULL;
-
-	group->ref_count--;
-
-	if (group->ref_count == 0) {
-		msn_group_destroy(group);
-
-		return NULL;
-	}
-
-	return group;
-}
-
 void
 msn_group_set_id(MsnGroup *group, int id)
 {
@@ -132,143 +85,3 @@
 
 	return group->name;
 }
-
-void
-msn_group_add_user(MsnGroup *group, MsnUser *user)
-{
-	g_return_if_fail(group != NULL);
-	g_return_if_fail(user  != NULL);
-
-	msn_users_add(group->users, user);
-
-	msn_user_ref(user);
-
-	gaim_debug(GAIM_DEBUG_INFO, "msn", "Adding user %s to group %s (%d)\n",
-			   msn_user_get_passport(user), msn_group_get_name(group),
-			   msn_group_get_id(group));
-}
-
-void
-msn_group_remove_user(MsnGroup *group, MsnUser *user)
-{
-	g_return_if_fail(group != NULL);
-	g_return_if_fail(user  != NULL);
-
-	msn_users_remove(group->users, user);
-
-	msn_user_unref(user);
-}
-
-MsnUsers *
-msn_group_get_users(const MsnGroup *group)
-{
-	g_return_val_if_fail(group != NULL, NULL);
-
-	return group->users;
-}
-
-
-MsnGroups *
-msn_groups_new(void)
-{
-	return g_new0(MsnGroups, 1);
-}
-
-void
-msn_groups_destroy(MsnGroups *groups)
-{
-	g_return_if_fail(groups != NULL);
-
-	while (groups->groups != NULL)
-		msn_group_destroy(groups->groups->data);
-
-	/* See if we've leaked anybody. */
-	while (groups->groups != NULL) {
-		gaim_debug(GAIM_DEBUG_WARNING, "msn",
-				   "Leaking group %s (id %d)\n",
-				   msn_group_get_name(groups->groups->data),
-				   msn_group_get_id(groups->groups->data));
-	}
-
-	g_free(groups);
-}
-
-void
-msn_groups_add(MsnGroups *groups, MsnGroup *group)
-{
-	g_return_if_fail(groups != NULL);
-	g_return_if_fail(group != NULL);
-
-	groups->groups = g_list_append(groups->groups, group);
-
-	groups->count++;
-
-	gaim_debug(GAIM_DEBUG_INFO, "msn", "Adding group %s (%d)\n",
-			   msn_group_get_name(group), msn_group_get_id(group));
-}
-
-void
-msn_groups_remove(MsnGroups *groups, MsnGroup *group)
-{
-	g_return_if_fail(groups != NULL);
-	g_return_if_fail(group != NULL);
-
-	gaim_debug(GAIM_DEBUG_INFO, "msn", "Removing group %s (%d)\n",
-			   msn_group_get_name(group), msn_group_get_id(group));
-
-	groups->groups = g_list_remove(groups->groups, group);
-
-	groups->count--;
-}
-
-size_t
-msn_groups_get_count(const MsnGroups *groups)
-{
-	g_return_val_if_fail(groups != NULL, 0);
-
-	return groups->count;
-}
-
-GList *
-msn_groups_get_list(const MsnGroups *groups)
-{
-	g_return_val_if_fail(groups != NULL, NULL);
-
-	return groups->groups;
-}
-
-MsnGroup *
-msn_groups_find_with_id(MsnGroups *groups, int id)
-{
-	GList *l;
-
-	g_return_val_if_fail(groups != NULL, NULL);
-	g_return_val_if_fail(id >= 0,        NULL);
-
-	for (l = groups->groups; l != NULL; l = l->next) {
-		MsnGroup *group = l->data;
-
-		if (group->id == id)
-			return group;
-	}
-
-	return NULL;
-}
-
-MsnGroup *
-msn_groups_find_with_name(MsnGroups *groups, const char *name)
-{
-	GList *l;
-
-	g_return_val_if_fail(groups != NULL, NULL);
-	g_return_val_if_fail(name   != NULL, NULL);
-
-	for (l = groups->groups; l != NULL; l = l->next) {
-		MsnGroup *group = l->data;
-
-		if (group->name != NULL && !g_ascii_strcasecmp(name, group->name))
-			return group;
-	}
-
-	return NULL;
-}
--- a/src/protocols/msn/group.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/group.h	Sun Jun 06 02:39:08 2004 +0000
@@ -23,36 +23,23 @@
 #define _MSN_GROUP_H_
 
 typedef struct _MsnGroup  MsnGroup;
-typedef struct _MsnGroups MsnGroups;
 
 #include <stdio.h>
 
 #include "session.h"
 #include "user.h"
 
+#include "userlist.h"
+
 /**
  * A group.
  */
 struct _MsnGroup
 {
-	size_t ref_count;       /**< The reference count.       */
-
 	MsnSession *session;    /**< The MSN session.           */
 
 	int id;                 /**< The group ID.              */
 	char *name;             /**< The name of the group.     */
-
-	MsnUsers *users;        /**< The users in the group.    */
-};
-
-/**
- * A list of groups.
- */
-struct _MsnGroups
-{
-	size_t count;  /**< The number of groups. */
-
-	GList *groups; /**< The list of groups.   */
 };
 
 /**************************************************************************/
@@ -69,7 +56,7 @@
  *
  * @return A new group structure.
  */
-MsnGroup *msn_group_new(MsnSession *session, int id, const char *name);
+MsnGroup *msn_group_new(MsnUserList *userlist, int id, const char *name);
 
 /**
  * Destroys a group structure.
@@ -79,26 +66,6 @@
 void msn_group_destroy(MsnGroup *group);
 
 /**
- * Increments the reference count on a group.
- *
- * @param group The group.
- *
- * @return @a group
- */
-MsnGroup *msn_group_ref(MsnGroup *group);
-
-/**
- * Decrements the reference count on a group.
- *
- * This will destroy the structure if the count hits 0.
- *
- * @param group The group.
- *
- * @return @a group, or @c NULL if the new count is 0.
- */
-MsnGroup *msn_group_unref(MsnGroup *group);
-
-/**
  * Sets the ID for a group.
  *
  * @param group The group.
@@ -131,105 +98,4 @@
  * @return The name.
  */
 const char *msn_group_get_name(const MsnGroup *group);
-
-/**
- * Adds a user to the group.
- *
- * @param group The group.
- * @param user  The user.
- */
-void msn_group_add_user(MsnGroup *group, MsnUser *user);
-
-/**
- * Removes a user from the group.
- *
- * @param group The group.
- * @param user  The user.
- */
-void msn_group_remove_user(MsnGroup *group, MsnUser *user);
-
-/**
- * Returns the users in a group.
- *
- * @param group The group.
- *
- * @return The list of users.
- */
-MsnUsers *msn_group_get_users(const MsnGroup *group);
-
-/*@}*/
-
-/**************************************************************************/
-/** @name Group List API                                                  */
-/**************************************************************************/
-/*@{*/
-
-/**
- * Creates a new MsnGroups structure.
- *
- * @return A new MsnGroups structure.
- */
-MsnGroups *msn_groups_new(void);
-
-/**
- * Destroys a groups list.
- *
- * @param groups The groups list.
- */
-void msn_groups_destroy(MsnGroups *groups);
-
-/**
- * Adds a group to a groups list.
- *
- * @param groups The groups list.
- * @param group  The group.
- */
-void msn_groups_add(MsnGroups *groups, MsnGroup *group);
-
-/**
- * Removes a group from a groups list.
- *
- * @param groups The groups list.
- * @param group  The group.
- */
-void msn_groups_remove(MsnGroups *groups, MsnGroup *group);
-
-/**
- * Returns the number of groups in a groups list.
- *
- * @param groups The groups list.
- *
- * @return The number of groups.
- */
-size_t msn_groups_get_count(const MsnGroups *groups);
-
-/**
- * Finds a group with the specified ID.
- *
- * @param groups A list of groups.
- * @param id     The group ID.
- *
- * @return The group if found, or @c NULL otherwise.
- */
-MsnGroup *msn_groups_find_with_id(MsnGroups *groups, int id);
-
-/**
- * Returns a GList of all groups.
- *
- * @param groups The list of groups.
- *
- * @return A GList of all groups.
- */
-GList *msn_groups_get_list(const MsnGroups *groups);
-
-/**
- * Finds a group with the specified name.
- *
- * @param groups A list of groups.
- * @param name   The group name.
- *
- * @return The group if found, or @c NULL otherwise.
- */
-MsnGroup *msn_groups_find_with_name(MsnGroups *groups, const char *name);
-
 #endif /* _MSN_GROUP_H_ */
--- a/src/protocols/msn/httpmethod.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/httpmethod.c	Sun Jun 06 02:39:08 2004 +0000
@@ -48,24 +48,32 @@
 static gboolean
 http_poll(gpointer data)
 {
-	MsnSession *session = data;
-	MsnServConn *servconn;
+	MsnSession *session;
 	GList *l;
 
-	for (l = session->servconns; l != NULL; l = l->next)
+	session = data;
+
+	for (l = session->switches; l != NULL; l = l->next)
 	{
-		servconn = (MsnServConn *)l->data;
+		MsnSwitchBoard *swboard;
+
+		swboard = l->data;
 
-		if (servconn->http_data->dirty)
+		g_return_val_if_fail(swboard->servconn->http_data != NULL, FALSE);
+
+		if (swboard->servconn->http_data->dirty)
 		{
 #if 0
 			gaim_debug_info("msn", "Polling server %s.\n",
 							servconn->http_data->gateway_host);
 #endif
-			msn_http_servconn_poll(servconn);
+			msn_http_servconn_poll(swboard->servconn);
 		}
 	}
 
+	if (session->notification->servconn->http_data->dirty)
+		msn_http_servconn_poll(session->notification->servconn);
+
 	return TRUE;
 }
 
--- a/src/protocols/msn/msg.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/msg.c	Sun Jun 06 02:39:08 2004 +0000
@@ -22,38 +22,6 @@
 #include "msn.h"
 #include "msg.h"
 
-#define GET_NEXT(tmp) \
-	while (*(tmp) && *(tmp) != ' ' && *(tmp) != '\r') \
-		(tmp)++; \
-	if (*(tmp) != '\0') *(tmp)++ = '\0'; \
-	if (*(tmp) == '\n') (tmp)++; \
-	while (*(tmp) && *(tmp) == ' ') \
-		(tmp)++
-
-#define GET_NEXT_LINE(tmp) \
-	while (*(tmp) && *(tmp) != '\r') \
-		(tmp)++; \
-	if (*(tmp) != '\0') *(tmp)++ = '\0'; \
-	if (*(tmp) == '\n') (tmp)++
-
-
-#define msn_put16(buf, data) ( \
-		(*(buf) = (unsigned char)((data)>>8)&0xff), \
-		(*((buf)+1) = (unsigned char)(data)&0xff),  \
-		2)
-#define msn_get16(buf) ((((*(buf))<<8)&0xff00) + ((*((buf)+1)) & 0xff))
-#define msn_put32(buf, data) ( \
-		(*((buf)) = (unsigned char)((data)>>24)&0xff), \
-		(*((buf)+1) = (unsigned char)((data)>>16)&0xff), \
-		(*((buf)+2) = (unsigned char)((data)>>8)&0xff), \
-		(*((buf)+3) = (unsigned char)(data)&0xff), \
-		4)
-#define msn_get32(buf) ((((*(buf))<<24)&0xff000000) + \
-		(((*((buf)+1))<<16)&0x00ff0000) + \
-		(((*((buf)+2))<< 8)&0x0000ff00) + \
-		(((*((buf)+3)    )&0x000000ff)))
-
-
 MsnMessage *
 msn_message_new(void)
 {
@@ -107,27 +75,36 @@
 	return msg;
 }
 
-MsnMessage *
-msn_message_new_msnslp_ack(MsnMessage *acked_msg)
+void
+msn_message_parse_slp_body(MsnMessage *msg, const char *body, size_t len)
 {
-	MsnMessage *msg;
+	MsnSlpHeader header;
+	const char *tmp;
 
-	g_return_val_if_fail(acked_msg != NULL, NULL);
-	g_return_val_if_fail(acked_msg->msnslp_message, NULL);
+	tmp = body;
 
-	msg = msn_message_new_msnslp();
+	/* Import the header. */
+	memcpy(&header, tmp, sizeof(header));
+	tmp += sizeof(header);
 
-	msg->msnslp_ack_message = TRUE;
-	msg->acked_msg = msn_message_ref(acked_msg);
+	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. */
+	/* msg->body_len = msg->msnslp_header.length; */
+	msg->body_len = len - (tmp - body);
+	
+	if (msg->body_len > 0)
+		msg->body = g_memdup(tmp, msg->body_len);
 
-	msg->msnslp_header.session_id  = acked_msg->msnslp_header.session_id;
-	msg->msnslp_header.total_size  = acked_msg->msnslp_header.total_size;
-	msg->msnslp_header.flags       = 0x02;
-	msg->msnslp_header.ack_id      = acked_msg->msnslp_header.id;
-	msg->msnslp_header.ack_sub_id  = acked_msg->msnslp_header.ack_id;
-	msg->msnslp_header.ack_size    = acked_msg->msnslp_header.total_size;
-
-	return msg;
+	tmp += msg->body_len;
 }
 
 void
@@ -136,33 +113,36 @@
 {
 	char *tmp_base, *tmp;
 	const char *content_type;
+	char *end;
+	char **elems, **cur, **tokens;
 
 	g_return_if_fail(payload != NULL);
 
-	tmp_base = tmp = g_memdup(payload, payload_len);
+	tmp_base = tmp = g_memdup(payload, payload_len + 1);
+	tmp[payload_len] = '\0';
 
-	/* Back to the parsination. */
-	while (*tmp != '\r')
-	{
-		char *key, *value, *c;
+	/* Parse the attributes. */
+	end = strstr(tmp, "\r\n\r\n");
+	g_return_if_fail(end != NULL);
+	*end = '\0';
 
-		key = tmp;
+	elems = g_strsplit(tmp, "\r\n", 0);
 
-		GET_NEXT(tmp); /* Key */
-
-		value = tmp;
+	for (cur = elems; *cur != NULL; cur++)
+	{
+		const char *key, *value;
 
-		GET_NEXT_LINE(tmp); /* Value */
+		tokens = g_strsplit(*cur, ": ", 2);
 
-		if ((c = strchr(key, ':')) != NULL)
-			*c = '\0';
+		key = tokens[0];
+		value = tokens[1];
 
-		if (!g_ascii_strcasecmp(key, "MIME-Version"))
+		if (!strcmp(key, "MIME-Version"))
 			continue;
 
-		if (!g_ascii_strcasecmp(key, "Content-Type"))
+		if (!strcmp(key, "Content-Type"))
 		{
-			char *charset;
+			char *charset, *c;
 
 			if ((c = strchr(value, ';')) != NULL)
 			{
@@ -178,11 +158,16 @@
 			msn_message_set_content_type(msg, value);
 		}
 		else
+		{
 			msn_message_set_attr(msg, key, value);
+		}
+
+		g_strfreev(tokens);
 	}
 
-	/* "\r\n" */
-	tmp += 2;
+	g_strfreev(elems);
+
+	tmp = end + 4;
 
 	/* Now we *should* be at the body. */
 	content_type = msn_message_get_content_type(msg);
@@ -200,9 +185,9 @@
 		tmp += sizeof(header);
 
 		msg->msnslp_header.session_id = GUINT32_FROM_LE(header.session_id);
-		msg->msnslp_header.id         = GUINT64_FROM_LE(header.id);
+		msg->msnslp_header.id         = GUINT32_FROM_LE(header.id);
 		msg->msnslp_header.offset     = GUINT64_FROM_LE(header.offset);
-		msg->msnslp_header.total_size = GUINT32_FROM_LE(header.total_size);
+		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);
@@ -230,8 +215,22 @@
 	}
 
 	g_free(tmp_base);
+}
 
-	/* Done! */
+MsnMessage *
+msn_message_new_from_cmd(MsnSession *session, MsnCommand *cmd)
+{
+	MsnMessage *msg;
+
+	g_return_val_if_fail(cmd != NULL, NULL);
+
+	msg = msn_message_new();
+
+	msg->remote_user = g_strdup(cmd->params[0]);
+	/* msg->size = atoi(cmd->params[2]); */
+	msg->cmd = cmd;
+
+	return msg;
 }
 
 void
@@ -246,6 +245,9 @@
 		return;
 	}
 
+	if (msg->remote_user != NULL)
+		g_free(msg->remote_user);
+
 	if (msg->body != NULL)
 		g_free(msg->body);
 
@@ -258,9 +260,6 @@
 	g_hash_table_destroy(msg->attr_table);
 	g_list_free(msg->attr_list);
 
-	if (msg->msnslp_ack_message)
-		msn_message_unref(msg->acked_msg);
-
 	g_free(msg);
 }
 
@@ -284,7 +283,8 @@
 
 	msg->ref_count--;
 
-	if (msg->ref_count == 0) {
+	if (msg->ref_count == 0) 
+	{
 		msn_message_destroy(msg);
 
 		return NULL;
@@ -294,7 +294,49 @@
 }
 
 char *
-msn_message_gen_payload(const MsnMessage *msg, size_t *ret_size)
+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;
@@ -349,9 +391,9 @@
 		MsnSlpFooter footer;
 
 		header.session_id = GUINT32_TO_LE(msg->msnslp_header.session_id);
-		header.id         = GUINT64_TO_LE(msg->msnslp_header.id);
+		header.id         = GUINT32_TO_LE(msg->msnslp_header.id);
 		header.offset     = GUINT64_TO_LE(msg->msnslp_header.offset);
-		header.total_size = GUINT32_TO_LE(msg->msnslp_header.total_size);
+		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);
@@ -364,6 +406,7 @@
 		if (body != NULL)
 		{
 			memcpy(n, body, body_len);
+
 			n += body_len;
 		}
 
@@ -383,10 +426,10 @@
 
 	if (ret_size != NULL)
 	{
-		*ret_size = n - base;
-
 		if (*ret_size > 1664)
 			*ret_size = 1664;
+
+		*ret_size = n - base;
 	}
 
 	return base;
@@ -452,10 +495,7 @@
 	if (msg->content_type != NULL)
 		g_free(msg->content_type);
 
-	if (type != NULL)
-		msg->content_type = g_strdup(type);
-	else
-		msg->content_type = NULL;
+	msg->content_type = (type != NULL) ? g_strdup(type) : NULL;
 }
 
 const char *
@@ -474,10 +514,7 @@
 	if (msg->charset != NULL)
 		g_free(msg->charset);
 
-	if (charset != NULL)
-		msg->charset = g_strdup(charset);
-	else
-		msg->charset = NULL;
+	msg->charset = (charset != NULL) ? g_strdup(charset) : NULL;
 }
 
 const char *
@@ -542,37 +579,135 @@
 msn_message_get_hashtable_from_body(const MsnMessage *msg)
 {
 	GHashTable *table;
-	char *body, *s, *c;
+	const char *body;
+	char **elems, **cur, **tokens;
 
 	g_return_val_if_fail(msg != NULL, NULL);
 
-	s = body = g_strdup(msn_message_get_bin_data(msg, NULL));
-
-	g_return_val_if_fail(body != NULL, NULL);
-
 	table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
 
-	while (*s != '\r' && *s != '\0')
+	body = msn_message_get_bin_data(msg, NULL);
+
+	g_return_val_if_fail(body != NULL, NULL);
+
+	elems = g_strsplit(body, "\r\n", 0);
+
+	for (cur = elems; *cur != NULL; cur++)
 	{
-		char *key, *value;
+		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;
+}
 
-		key = s;
+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);
 
-		GET_NEXT(s);
+	/* 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;
 
-		value = s;
+		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);
 
-		GET_NEXT_LINE(s);
+	if (msg->msnslp_message)
+	{
+		g_string_append_printf(str, "%u ", msg->msnslp_header.session_id);
+		g_string_append_printf(str, "%u ", msg->msnslp_header.id);
+		g_string_append_printf(str, "%llu ", msg->msnslp_header.offset);
+		g_string_append(str, "\r\n");
+		g_string_append_printf(str, "%llu ",
+							   msg->msnslp_header.total_size);
+		g_string_append_printf(str, "%u ", msg->msnslp_header.length);
+		g_string_append_printf(str, "%u ", msg->msnslp_header.flags);
+		g_string_append(str, "\r\n");
+		g_string_append_printf(str, "%u ", msg->msnslp_header.ack_id);
+		g_string_append_printf(str, "%u ", msg->msnslp_header.ack_sub_id);
+		g_string_append_printf(str, "%lld ", msg->msnslp_header.ack_size);
+		g_string_append(str, "\r\n");
 
-		if ((c = strchr(key, ':')) != NULL)
+		if (body != NULL)
 		{
-			*c = '\0';
-
-			g_hash_table_insert(table, g_strdup(key), g_strdup(value));
+			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");
+			}
+		}
+		
+		g_string_append_printf(str, "%u ", msg->msnslp_footer.value);
+		g_string_append(str, "\r\n");
+	}
+	else
+	{
+		if (body != NULL)
+		{
+			g_string_append_len(str, body, body_len);
+			g_string_append(str, "\r\n");
 		}
 	}
 
-	g_free(body);
-
-	return table;
+	gaim_debug_info("msn", "Message %s:\n{%s}\n", info, str->str);
+	
+	g_string_free(str, TRUE);
 }
--- a/src/protocols/msn/msg.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/msg.h	Sun Jun 06 02:39:08 2004 +0000
@@ -27,6 +27,17 @@
 #include "session.h"
 #include "user.h"
 
+#include "command.h"
+#include "transaction.h"
+
+typedef enum
+{
+	MSN_MSG_NORMAL,
+	MSN_MSG_SLP_SB,
+	MSN_MSG_SLP_DC
+
+} MsnMsgType;
+
 typedef struct
 {
 	guint32 session_id;
@@ -54,27 +65,30 @@
 {
 	size_t ref_count;           /**< The reference count.       */
 
+	MsnMsgType type;
+
 	gboolean msnslp_message;
-	gboolean msnslp_ack_message;
 
-	char *passport;
+	char *remote_user;
 	char flag;
 
 	char *content_type;
 	char *charset;
 	char *body;
-	size_t body_len;
+	gsize body_len;
 
 	MsnSlpHeader msnslp_header;
 	MsnSlpFooter msnslp_footer;
 
-	MsnMessage *acked_msg;
-
 	GHashTable *attr_table;
 	GList *attr_list;
-};
+
+	MsnCommand *cmd;
+	MsnTransaction *trans;
 
-#define MSN_MESSAGE(msg) ((MsnMessage *)(msg))
+	MsnTransCb ack_cb;
+	void *ack_data;
+};
 
 /**
  * Creates a new, empty message.
@@ -83,8 +97,6 @@
  */
 MsnMessage *msn_message_new(void);
 
-MsnMessage *msn_message_new_plain(const char *message);
-
 /**
  * Creates a new, empty MSNSLP message.
  *
@@ -92,6 +104,8 @@
  */
 MsnMessage *msn_message_new_msnslp(void);
 
+MsnMessage *msn_message_new_plain(const char *message);
+
 /**
  * Creates a MSNSLP ack message.
  *
@@ -102,7 +116,17 @@
 MsnMessage *msn_message_new_msnslp_ack(MsnMessage *acked_msg);
 
 /**
- * Parse the payload of a message.
+ * 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.
@@ -146,7 +170,7 @@
  *
  * @return The payload data of the message.
  */
-char *msn_message_gen_payload(const MsnMessage *msg, size_t *ret_size);
+char *msn_message_gen_payload(MsnMessage *msg, size_t *ret_size);
 
 /**
  * Sets the flag for an outgoing message.
@@ -165,6 +189,7 @@
  */
 char msn_message_get_flag(const MsnMessage *msg);
 
+#if 0
 /**
  * Sets the body of a message.
  *
@@ -181,7 +206,7 @@
  * @return The body of the message.
  */
 const char *msn_message_get_body(const MsnMessage *msg);
-
+#endif
 /**
  * Sets the binary content of the message.
  *
@@ -264,4 +289,13 @@
  */
 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);
+
+
 #endif /* _MSN_MSG_H_ */
--- a/src/protocols/msn/msn.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/msn.c	Sun Jun 06 02:39:08 2004 +0000
@@ -20,9 +20,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 #include <glib.h>
-#include "internal.h"
 
-#include "blist.h"
 #include "msn.h"
 #include "accountopt.h"
 #include "msg.h"
@@ -32,10 +30,13 @@
 #include "session.h"
 #include "state.h"
 #include "utils.h"
+#include "multi.h"
 #include "util.h"
 
+#include "switchboard.h"
 #include "notification.h"
-#include "switchboard.h"
+#include "sync.h"
+#include "slplink.h"
 
 #define BUDDY_ALIAS_MAXLEN 387
 
@@ -82,7 +83,7 @@
 	const char *alias;
 
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
+	cmdproc = session->notification->cmdproc;
 	account = gaim_connection_get_account(gc);
 
 	alias = (entry && *entry) ? entry : "";
@@ -91,7 +92,6 @@
 	{
 		gaim_notify_error(gc, NULL,
 						  _("Your new MSN friendly name is too long."), NULL);
-
 		return;
 	}
 
@@ -107,7 +107,7 @@
 	MsnSession *session;
 
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
+	cmdproc = session->notification->cmdproc;
 
 	if (entry == NULL || *entry == '\0')
 	{
@@ -153,17 +153,15 @@
 static void
 send_to_mobile(GaimConnection *gc, const char *who, const char *entry)
 {
+	MsnTransaction *trans;
 	MsnSession *session;
-	MsnServConn *servconn;
 	MsnCmdProc *cmdproc;
-	MsnTransaction *trans;
 	MsnPage *page;
 	char *payload;
 	size_t payload_len;
 
 	session = gc->proto_data;
-	servconn = session->notification_conn;
-	cmdproc = servconn->cmdproc;	
+	cmdproc = session->notification->cmdproc;
 
 	page = msn_page_new();
 	msn_page_set_body(page, entry);
@@ -262,13 +260,13 @@
 	gc = (GaimConnection *) action->context;
 
 	gaim_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, gc, 3,
-			_("Allow"), G_CALLBACK(enable_msn_pages_cb),
-			_("Disallow"), G_CALLBACK(disable_msn_pages_cb),
-			_("Cancel"), NULL);
+						_("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, gc, 3,
+						_("Allow"), G_CALLBACK(enable_msn_pages_cb),
+						_("Disallow"), G_CALLBACK(disable_msn_pages_cb),
+						_("Cancel"), NULL);
 }
 
 static void
@@ -286,17 +284,17 @@
 	gc = gaim_account_get_connection(buddy->account);
 
 	session = gc->proto_data;
-	user = msn_users_find_with_passport(session->users, buddy->name);
+	user = msn_userlist_find_user(session->userlist, buddy->name);
 
 	data = g_new0(MsnMobileData, 1);
 	data->gc = gc;
 	data->passport = buddy->name;
 
 	gaim_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),
-			data);
+					   NULL, TRUE, FALSE, NULL,
+					   _("Page"), G_CALLBACK(send_to_mobile_cb),
+					   _("Close"), G_CALLBACK(close_mobile_page_cb),
+					   data);
 }
 
 static void
@@ -306,33 +304,59 @@
 	GaimConnection *gc;
 
 	MsnSession *session;
-	MsnCmdProc *cmdproc;
 	MsnSwitchBoard *swboard;
-	MsnUser *user;
-	
+
 	g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
 
 	buddy = (GaimBuddy *) node;
 	gc = gaim_account_get_connection(buddy->account);
 
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
+
+	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");
+}
 
-	if ((swboard = msn_session_open_switchboard(session)) == NULL)
-		return;
+static void
+t_msn_xfer_init(GaimXfer *xfer)
+{
+	MsnSlpLink *slplink;
 
-	user = msn_user_new(session, buddy->name, NULL);
+	slplink = xfer->data;
 
-	msn_switchboard_set_user(swboard, user);
+	msn_slplink_request_ft(slplink, xfer);
+}
 
-	swboard->total_users = 1;
+static void
+show_send_file_cb(GaimBlistNode *node, gpointer ignored)
+{
+	GaimBuddy *buddy;
+	GaimConnection *gc;
+	MsnSession *session;
+	MsnSlpLink *slplink;
+	GaimXfer *xfer;
+
+	g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+	buddy = (GaimBuddy *) node;
 
-	session->last_chat_id++;
-	swboard->chat_id = session->last_chat_id;
-	swboard->chat = serv_got_joined_chat(gc, swboard->chat_id, "MSN Chat");
+	gc = gaim_account_get_connection(buddy->account);
+	session = gc->proto_data;
+
+	xfer = gaim_xfer_new(buddy->account, GAIM_XFER_SEND, buddy->name);
 
-	gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->chat),
-							gaim_account_get_username(buddy->account), NULL);
+	slplink = msn_session_get_slplink(session, buddy->name);
+
+	xfer->data = slplink;
+
+	gaim_xfer_set_init_fnc(xfer, t_msn_xfer_init);
+
+	gaim_xfer_request(xfer);
 }
 
 /**************************************************************************
@@ -460,6 +484,8 @@
 	GList *m = NULL;
 	GaimBlistNodeAction *act;
 
+	g_return_val_if_fail(buddy != NULL, NULL);
+
 	user = buddy->proto_data;
 
 	if (user != NULL)
@@ -470,6 +496,11 @@
 											 show_send_to_mobile_cb, NULL);
 			m = g_list_append(m, act);
 		}
+
+		act = gaim_blist_node_action_new(_("Send File"),
+											show_send_file_cb, NULL);
+
+		m = g_list_append(m, act);
 	}
 
 	if (g_ascii_strcasecmp(buddy->name,
@@ -522,7 +553,7 @@
 	{
 		http_method = TRUE;
 
-		gaim_debug(GAIM_DEBUG_INFO, "msn", "using http method\n");
+		gaim_debug_info("msn", "using http method\n");
 
 		host = "gateway.messenger.hotmail.com";
 		port   = 80;
@@ -533,15 +564,14 @@
 		port = gaim_account_get_int(account,    "port",   MSN_PORT);
 	}
 
-	session = msn_session_new(account, host, port);
-	session->http_method = http_method;
+	session = msn_session_new(account, host, port, http_method);
 	session->prpl = my_protocol;
 
 	if (session->http_method)
 		msn_http_session_init(session);
 
 	gc->proto_data = session;
-	gc->flags |= GAIM_CONNECTION_HTML | GAIM_CONNECTION_FORMATTING_WBFO | GAIM_CONNECTION_NO_BGCOLOR | GAIM_CONNECTION_NO_FONTSIZE | GAIM_CONNECTION_NO_URLDESC;
+	gc->flags |= GAIM_CONNECTION_HTML | GAIM_CONNECTION_FORMATTING_WBFO | GAIM_CONNECTION_NO_BGCOLOR | GAIM_CONNECTION_NO_FONTSIZE | GAIM_CONNECTION_NO_URLDESC;;
 
 	gaim_connection_update_progress(gc, _("Connecting"), 0, MSN_CONNECT_STEPS);
 
@@ -551,26 +581,22 @@
 	if (strcmp(username, gaim_account_get_username(account)))
 		gaim_account_set_username(account, username);
 
-	if (!msn_session_connect(session))
-	{
-		gaim_connection_error(gc, _("Unable to connect."));
-
-		return;
-	}
+	msn_session_connect(session);
 }
 
 static void
 msn_close(GaimConnection *gc)
 {
-	MsnSession *session = gc->proto_data;
+	MsnSession *session;
+
+	session = gc->proto_data;
 
-	if (session != NULL)
-	{
-		if (session->http_method)
-			msn_http_session_uninit(session);
+	g_return_if_fail(session != NULL);
 
-		msn_session_destroy(session);
-	}
+	if (session->http_method)
+		msn_http_session_uninit(session);
+
+	msn_session_destroy(session);
 
 	gc->proto_data = NULL;
 }
@@ -588,40 +614,31 @@
 		MsnSession *session;
 		MsnSwitchBoard *swboard;
 		MsnMessage *msg;
-		MsnUser *user;
 		char *msgformat;
 		char *msgtext;
 
 		session = gc->proto_data;
-		swboard = msn_session_find_switch_with_passport(session, who);
-		user = msn_user_new(session, who, NULL);
+		swboard = msn_session_get_swboard(session, who);
 
 		msn_import_html(message, &msgformat, &msgtext);
-
 		msg = msn_message_new_plain(msgtext);
 		msn_message_set_attr(msg, "X-MMS-IM-Format", msgformat);
 
 		g_free(msgformat);
 		g_free(msgtext);
 
-		if (swboard != NULL)
+		swboard = msn_session_get_swboard(session, who);
+
+		if (!g_queue_is_empty(swboard->im_queue) ||
+			!swboard->user_joined)
 		{
-			msn_switchboard_send_msg(swboard, msg);
+			msn_switchboard_queue_msg(swboard, msg);
 		}
 		else
 		{
-			if ((swboard = msn_session_open_switchboard(session)) == NULL)
-			{
-				msn_message_destroy(msg);
-
-				return 1;
-			}
-
-			msn_switchboard_set_user(swboard, user);
 			msn_switchboard_send_msg(swboard, msg);
 		}
 
-		msn_user_destroy(user);
 		msn_message_destroy(msg);
 	}
 	else
@@ -630,7 +647,7 @@
 		 * In MSN, you can't send messages to yourself, so
 		 * we'll fake like we received it ;)
 		 */
-		serv_got_typing_stopped(gc, (char *)who);
+		serv_got_typing_stopped(gc, who);
 		serv_got_im(gc, who, message, flags, time(NULL));
 	}
 
@@ -659,11 +676,14 @@
 		return MSN_TYPING_SEND_TIMEOUT;
 	}
 
-	swboard = msn_session_find_switch_with_passport(session, who);
+	swboard = msn_session_find_swboard(session, who);
 
 	if (swboard == NULL)
 		return 0;
 
+	if (!swboard->user_joined)
+		return 0;
+
 	msg = msn_message_new();
 	msn_message_set_content_type(msg, "text/x-msmsgscontrol");
 	msn_message_set_flag(msg, 'U');
@@ -682,7 +702,7 @@
 msn_set_away(GaimConnection *gc, const char *state, const char *msg)
 {
 	MsnSession *session;
-	const char *status;
+	int status;
 
 	session = gc->proto_data;
 
@@ -695,37 +715,37 @@
 	if (msg != NULL)
 	{
 		gc->away = g_strdup("");
-		status = "AWY";
+		status = MSN_AWAY;
 	}
 	else if (state)
 	{
 		gc->away = g_strdup("");
 
 		if (!strcmp(state, _("Away From Computer")))
-			status = "AWY";
+			status = MSN_AWAY;
 		else if (!strcmp(state, _("Be Right Back")))
-			status = "BRB";
+			status = MSN_BRB;
 		else if (!strcmp(state, _("Busy")))
-			status = "BSY";
+			status = MSN_BUSY;
 		else if (!strcmp(state, _("On The Phone")))
-			status = "PHN";
+			status = MSN_PHONE;
 		else if (!strcmp(state, _("Out To Lunch")))
-			status = "LUN";
+			status = MSN_LUNCH;
 		else if (!strcmp(state, _("Hidden")))
-			status = "HDN";
+			status = MSN_HIDDEN;
 		else
 		{
 			g_free(gc->away);
 			gc->away = NULL;
-			status = "NLN";
+			status = MSN_ONLINE;
 		}
 	}
 	else if (gc->is_idle)
-		status = "IDL";
+		status = MSN_IDLE;
 	else
-		status = "NLN";
+		status = MSN_ONLINE;
 
-	msn_session_change_status(session, status);
+	msn_change_status(session, status);
 }
 
 static void
@@ -738,228 +758,120 @@
 	if (gc->away != NULL)
 		return;
 
-	msn_session_change_status(session, (idle ? "IDL" : "NLN"));
+	msn_change_status(session, (idle ? MSN_IDLE : MSN_ONLINE));
 }
 
 static void
 msn_add_buddy(GaimConnection *gc, const char *name, GaimGroup *group)
 {
 	MsnSession *session;
-	MsnCmdProc *cmdproc;
-	MsnGroup *msn_group = NULL;
+	MsnUserList *userlist;
 	const char *who;
-	GSList *l;
 
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
+	userlist = session->userlist;
 	who = msn_normalize(gc->account, name);
 
-	if (strchr(who, ' '))
-	{
-		/* This is a broken blist entry. */
-		return;
-	}
-
 	if (group != NULL)
-		msn_group = msn_groups_find_with_name(session->groups, group->name);
-
-	/* We should check if the user isn't alredy there. */
-	for (l = session->lists.forward; l != NULL; l = l->next)
-	{
-		MsnUser *user = l->data;
+		gaim_debug_info("msn", "msn_add_buddy: %s, %s\n", who, group->name);
+	else
+		gaim_debug_info("msn", "msn_add_buddy: %s\n", who);
 
-		if (!gaim_utf8_strcasecmp(who, msn_user_get_passport(user)))
-		{
-			if (group == NULL)
-				break;
-			else if (msn_group != NULL)
-			{
-				/* Now we should check if it's in the group. */
-				if (g_list_find(user->group_ids,
-								GINT_TO_POINTER(msn_group->id)))
-					break;
-			}
-		}
+#if 0
+	/* Which is the max? */
+	if (session->fl_users_count >= 150)
+	{
+		gaim_debug_info("msn", "Too many buddies\n");
+		/* Buddy list full */
+		/* TODO: gaim should be notifyied of this */
+		return;
 	}
-
-	if (l != NULL)
-		return;
-
-	if (msn_group != NULL)
-	{
-		msn_cmdproc_send(cmdproc, "ADD", "FL %s %s %d", who, who,
-						 msn_group_get_id(msn_group));
-	}
-	else
-	{
-		msn_cmdproc_send(cmdproc, "ADD", "FL %s %s", who, who);
-	}
+#endif
+	
+	msn_userlist_add_buddy(userlist, who, MSN_LIST_FL,
+						   group ? group->name : NULL);
 }
 
 static void
 msn_rem_buddy(GaimConnection *gc, const char *who, const char *group_name)
 {
 	MsnSession *session;
-	MsnCmdProc *cmdproc;
-	MsnGroup *group;
-	GSList *l;
-
+	MsnUserList *userlist;
+	
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
-
-	if (strchr(who, ' '))
-	{
-		/* This is a broken blist entry. */
-		return;
-	}
-
-	group = msn_groups_find_with_name(session->groups, group_name);
-
-	/* We should check if the user is there. */
-	for (l = session->lists.forward; l != NULL; l = l->next)
-	{
-		MsnUser *user = l->data;
+	userlist = session->userlist;
 
-		if (!gaim_utf8_strcasecmp(who, msn_user_get_passport(user)))
-		{
-			if (group_name == NULL)
-				break;
-			else if (group != NULL)
-			{
-				/* Now we should check if it's in the group. */
-				if (g_list_find(user->group_ids,
-								GINT_TO_POINTER(group->id)))
-					break;
-			}
-		}
-	}
-
-	if (l == NULL)
-		return;
-
-	if (group == NULL)
-	{
-		msn_cmdproc_send(cmdproc, "REM", "FL %s", who);
-	}
-	else
-	{
-		msn_cmdproc_send(cmdproc, "REM", "FL %s %d", who,
-						 msn_group_get_id(group));
-	}
+	msn_userlist_rem_buddy(userlist, who, MSN_LIST_FL, group_name);
 }
 
 static void
 msn_add_permit(GaimConnection *gc, const char *who)
 {
 	MsnSession *session;
-	MsnCmdProc *cmdproc;
+	MsnUserList *userlist;
+	MsnUser *user;
 
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
-
-	if (!strchr(who, '@'))
-	{
-		char buf[MSN_BUF_LEN];
-
-		g_snprintf(buf, sizeof(buf),
-				   _("An MSN screen name must be in the form \"user@server.com\". "
-					 "Perhaps you meant %s@hotmail.com. No changes were made "
-					 "to your allow list."), who);
-
-		gaim_notify_error(gc, NULL, _("Invalid MSN screen name"), buf);
-		gaim_privacy_permit_remove(gc->account, who, TRUE);
+	userlist = session->userlist;
+	user = msn_userlist_find_user(userlist, who);
 
-		return;
-	}
-
-	if (g_slist_find_custom(gc->account->deny, who, (GCompareFunc)strcasecmp))
-	{
-		gaim_debug_info("msn", "Moving %s from BL to AL\n", who);
-		gaim_privacy_deny_remove(gc->account, who, TRUE);
-
-		msn_cmdproc_send(cmdproc, "REM", "BL %s", who);
-
-		if (cmdproc->error)
-			return;
-	}
-
-	msn_cmdproc_send(cmdproc, "ADD", "AL %s %s", who, who);
+	if (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(GaimConnection *gc, const char *who)
 {
-	MsnCmdProc *cmdproc;
 	MsnSession *session;
-
+	MsnUserList *userlist;
+	MsnUser *user;
+	
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
-
-	if (!strchr(who, '@'))
-	{
-		char buf[MSN_BUF_LEN];
-		g_snprintf(buf, sizeof(buf),
-				   _("An MSN screen name must be in the form \"user@server.com\". "
-					 "Perhaps you meant %s@hotmail.com. No changes were made "
-					 "to your block list."), who);
-
-		gaim_notify_error(gc, NULL, _("Invalid MSN screen name"), buf);
+	userlist = session->userlist;
+	user = msn_userlist_find_user(userlist, who);
 
-		gaim_privacy_deny_remove(gc->account, who, TRUE);
-
-		return;
-	}
+	if (user->list_op & MSN_LIST_AL_OP)
+		msn_userlist_rem_buddy(userlist, who, MSN_LIST_AL, NULL);
 
-	if (g_slist_find_custom(gc->account->permit, who, (GCompareFunc)strcmp))
-	{
-		gaim_debug(GAIM_DEBUG_INFO, "msn", "Moving %s from AL to BL\n", who);
-		gaim_privacy_permit_remove(gc->account, who, TRUE);
-
-		msn_cmdproc_send(cmdproc, "REM", "AL %s", who);
-
-		if (cmdproc->error)
-			return;
-	}
-
-	msn_cmdproc_send(cmdproc, "ADD", "BL %s %s", who, who);
+	msn_userlist_add_buddy(userlist, who, MSN_LIST_BL, NULL);
 }
 
 static void
 msn_rem_permit(GaimConnection *gc, const char *who)
 {
 	MsnSession *session;
-	MsnCmdProc *cmdproc;
-
+	MsnUserList *userlist;
+	MsnUser *user;
+	
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
-
-	msn_cmdproc_send(cmdproc, "REM", "AL %s", who);
+	userlist = session->userlist;
 
-	if (cmdproc->error)
-		return;
+	user = msn_userlist_find_user(userlist, who);
+	
+	msn_userlist_rem_buddy(userlist, who, MSN_LIST_AL, NULL);
 
-	gaim_privacy_deny_add(gc->account, who, TRUE);
-
-	msn_cmdproc_send(cmdproc, "ADD", "BL %s %s", who, who);
+	if (user->list_op & MSN_LIST_RL_OP)
+		msn_userlist_add_buddy(userlist, who, MSN_LIST_BL, NULL);
 }
 
-static void
+void
 msn_rem_deny(GaimConnection *gc, const char *who)
 {
 	MsnSession *session;
-	MsnCmdProc *cmdproc;
-
+	MsnUserList *userlist;
+	MsnUser *user;
+	
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
-
-	msn_cmdproc_send(cmdproc, "REM", "BL %s", who);
+	userlist = session->userlist;
 
-	if (cmdproc->error)
-		return;
+	user = msn_userlist_find_user(userlist, who);
 
-	gaim_privacy_permit_add(gc->account, who, TRUE);
+	msn_userlist_rem_buddy(userlist, who, MSN_LIST_BL, NULL);
 
-	msn_cmdproc_send(cmdproc, "ADD", "AL %s %s", who, who);
+	if (user->list_op & MSN_LIST_RL_OP)
+		msn_userlist_add_buddy(userlist, who, MSN_LIST_AL, NULL);
 }
 
 static void
@@ -968,11 +880,10 @@
 	GaimAccount *account;
 	MsnSession *session;
 	MsnCmdProc *cmdproc;
-	GSList *s, *t = NULL;
 
 	account = gaim_connection_get_account(gc);
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
+	cmdproc = session->notification->cmdproc;
 
 	if (account->perm_deny == GAIM_PRIVACY_ALLOW_ALL ||
 		account->perm_deny == GAIM_PRIVACY_DENY_USERS)
@@ -983,107 +894,6 @@
 	{
 		msn_cmdproc_send(cmdproc, "BLP", "%s", "BL");
 	}
-
-	if (cmdproc->error)
-		return;
-
-	/*
-	 * This is safe because we'll always come here after we've gotten
-	 * the list off the server, and data is never removed. So if the
-	 * lengths are equal we don't know about anyone locally and so
-	 * there's no sense in going through them all.
-	 */
-	if (g_slist_length(gc->account->permit) ==
-		g_slist_length(session->lists.allow))
-	{
-		g_slist_free(session->lists.allow);
-		session->lists.allow = NULL;
-	}
-
-	if (g_slist_length(gc->account->deny) ==
-		g_slist_length(session->lists.block))
-	{
-		g_slist_free(session->lists.block);
-		session->lists.block = NULL;
-	}
-
-	if (session->lists.allow == NULL && session->lists.block == NULL)
-		return;
-
-	if (session->lists.allow != NULL)
-	{
-		for (s = g_slist_nth(gc->account->permit,
-							 g_slist_length(session->lists.allow));
-			 s != NULL;
-			 s = s->next)
-		{
-			char *who = s->data;
-
-			if (!strchr(who, '@'))
-			{
-				t = g_slist_append(t, who);
-				continue;
-			}
-
-			if (g_slist_find(session->lists.block, who))
-			{
-				t = g_slist_append(t, who);
-				continue;
-			}
-
-			msn_cmdproc_send(cmdproc, "ADD", "AL %s %s", who, who);
-
-			if (cmdproc->error)
-				return;
-		}
-
-		for (; t != NULL; t = t->next)
-			gaim_privacy_permit_remove(gc->account, t->data, TRUE);
-
-		if (t != NULL)
-			g_slist_free(t);
-
-		t = NULL;
-		g_slist_free(session->lists.allow);
-		session->lists.allow = NULL;
-	}
-
-	if (session->lists.block)
-	{
-		for (s = g_slist_nth(gc->account->deny,
-							 g_slist_length(session->lists.block));
-			 s != NULL;
-			 s = s->next)
-		{
-			char *who = s->data;
-
-			if (!strchr(who, '@'))
-			{
-				t = g_slist_append(t, who);
-				continue;
-			}
-
-			if (g_slist_find(session->lists.block, who))
-			{
-				t = g_slist_append(t, who);
-				continue;
-			}
-
-			msn_cmdproc_send(cmdproc, "ADD", "BL %s %s", who, who);
-
-			if (cmdproc->error)
-				return;
-		}
-
-		for (; t != NULL; t = t->next)
-			gaim_privacy_deny_remove(gc->account, t->data, TRUE);
-
-		if (t != NULL)
-			g_slist_free(t);
-
-		g_slist_free(session->lists.block);
-		session->lists.block = NULL;
-	}
 }
 
 static void
@@ -1099,7 +909,7 @@
 	swboard = msn_session_find_switch_with_id(session, id);
 	g_return_if_fail(swboard != NULL);
 
-	cmdproc = swboard->cmdproc;
+	cmdproc = swboard->servconn->cmdproc;
 
 	msn_cmdproc_send(cmdproc, "CAL", "%s", who);
 }
@@ -1112,6 +922,7 @@
 	MsnCmdProc *cmdproc;
 
 	session = gc->proto_data;
+
 	swboard = msn_session_find_switch_with_id(session, id);
 	g_return_if_fail(swboard != NULL);
 
@@ -1140,20 +951,21 @@
 	if (swboard == NULL)
 		return -EINVAL;
 
-	msn_import_html(message, &msgformat, &msgtext);
-
-	msg = msn_message_new_plain(msgtext);
-	msn_message_set_attr(msg, "X-MMS-IM-Format", msgformat);
+	if (swboard->ready)
+	{
+		msn_import_html(message, &msgformat, &msgtext);
 
-	g_free(msgformat);
-	g_free(msgtext);
-
-	msn_switchboard_send_msg(swboard, msg);
+		msg = msn_message_new_plain(msgtext);
+		msn_message_set_attr(msg, "X-MMS-IM-Format", msgformat);
+		msn_switchboard_send_msg(swboard, msg);
+		msn_message_destroy(msg);
 
-	msn_message_destroy(msg);
+		g_free(msgformat);
+		g_free(msgtext);
 
-	serv_got_chat_in(gc, id, gaim_account_get_username(account), 0,
-					 message, time(NULL));
+		serv_got_chat_in(gc, id, gaim_account_get_username(account), 0,
+						 message, time(NULL));
+	}
 
 	return 0;
 }
@@ -1165,7 +977,7 @@
 	MsnCmdProc *cmdproc;
 
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
+	cmdproc = session->notification->cmdproc;
 
 	if (!session->http_method)
 		msn_cmdproc_send_quick(cmdproc, "PNG", NULL, NULL);
@@ -1176,68 +988,12 @@
 				const char *old_group_name, const char *new_group_name)
 {
 	MsnSession *session;
-	MsnCmdProc *cmdproc;
-	MsnGroup *old_group, *new_group;
-	MsnUser *user;
-	const char *friendly;
-
+	MsnUserList *userlist;
+	
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
-	old_group = msn_groups_find_with_name(session->groups, old_group_name);
-	new_group = msn_groups_find_with_name(session->groups, new_group_name);
-
-	user = msn_users_find_with_passport(session->users, who);
-
-	if ((friendly = msn_user_get_name(user)) == NULL)
-		friendly = msn_user_get_passport(user);
-
-	if (old_group != NULL)
-		msn_user_remove_group_id(user, msn_group_get_id(old_group));
-
-	if (new_group == NULL)
-	{
-		msn_cmdproc_send(cmdproc, "ADG", "%s 0",
-						 gaim_url_encode(new_group_name));
-
-		if (cmdproc->error)
-			return;
-
-		/* I hate this. So much. */
-		session->moving_buddy    = TRUE;
-		session->dest_group_name = g_strdup(new_group_name);
-		session->old_group       = old_group;
+	userlist = session->userlist;
 
-		session->moving_user =
-			msn_users_find_with_passport(session->users, who);
-
-		msn_user_ref(session->moving_user);
-	}
-	else
-	{
-		msn_cmdproc_send(cmdproc, "ADD", "FL %s %s %d",
-						 who, gaim_url_encode(friendly),
-						 msn_group_get_id(new_group));
-
-		if (cmdproc->error)
-			return;
-	}
-
-	if (old_group != NULL)
-	{
-		msn_cmdproc_send(cmdproc, "REM", "FL %s %d", who,
-						 msn_group_get_id(old_group));
-
-		if (cmdproc->error)
-			return;
-
-#if 0
-		if (msn_users_get_count(msn_group_get_users(old_group)) <= 0)
-		{
-			msn_cmdproc_send(cmdproc, "RMG", "%d",
-							 msn_group_get_id(old_group));
-		}
-#endif
-	}
+	msn_userlist_move_buddy(userlist, who, old_group_name, new_group_name);
 }
 
 static void
@@ -1246,23 +1002,19 @@
 {
 	MsnSession *session;
 	MsnCmdProc *cmdproc;
-	MsnGroup *old_group;
+	int old_gid;
 	const char *enc_new_group_name;
 
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
+	cmdproc = session->notification->cmdproc;
 	enc_new_group_name = gaim_url_encode(new_group_name);
 
-	if ((old_group = msn_groups_find_with_name(session->groups,
-											   old_group_name)) != NULL)
+	old_gid = msn_userlist_find_group_id(session->userlist, old_group_name);
+
+	if (old_gid >= 0)
 	{
-		msn_cmdproc_send(cmdproc, "REG", "%d %s 0",
-						 msn_group_get_id(old_group), enc_new_group_name);
-
-		if (cmdproc->error)
-			return;
-
-		msn_group_set_name(old_group, new_group_name);
+		msn_cmdproc_send(cmdproc, "REG", "%d %s 0", old_gid,
+						 enc_new_group_name);
 	}
 	else
 	{
@@ -1271,16 +1023,6 @@
 }
 
 static void
-msn_buddy_free(GaimBuddy *b)
-{
-	if (b->proto_data != NULL)
-	{
-		msn_user_destroy(b->proto_data);
-		b->proto_data = NULL;
-	}
-}
-
-static void
 msn_convo_closed(GaimConnection *gc, const char *who)
 {
 	MsnSession *session;
@@ -1289,13 +1031,21 @@
 
 	session = gc->proto_data;
 
-	swboard = msn_session_find_switch_with_passport(session, who);
-	g_return_if_fail(swboard != NULL);
+	swboard = msn_session_find_swboard(session, who);
+
+	/*
+	 * Don't perform an assertion here. It swboard is NULL, then the
+	 * switchboard was either closed by the other party, or the person
+	 * is talking to himself.
+	 */
+	if (swboard == NULL)
+		return;
 
 	cmdproc = swboard->servconn->cmdproc;
 
-	if (swboard->chat == NULL)
+	if (swboard->current_users == 1)
 	{
+		/* This must happen on both IM's and Chat's, right? */
 		GaimAccount *account;
 
 		account = gaim_connection_get_account(gc);
@@ -1318,7 +1068,7 @@
 
 	msn_user_set_buddy_icon(user, filename);
 
-	msn_session_change_status(session, session->away_state);
+	msn_change_status(session, session->state);
 }
 
 static void
@@ -1326,14 +1076,14 @@
 {
 	MsnSession *session;
 	MsnCmdProc *cmdproc;
-	MsnGroup *group;
+	int group_id;
 
 	session = gc->proto_data;
-	cmdproc = session->notification_conn->cmdproc;
+	cmdproc = session->notification->cmdproc;
 
-	if ((group = msn_groups_find_with_name(session->groups, name)) != NULL)
+	if ((group_id = msn_userlist_find_group_id(session->userlist, name)) >= 0)
 	{
-		msn_cmdproc_send(cmdproc, "RMG", "%d", msn_group_get_id(group));
+		msn_cmdproc_send(cmdproc, "RMG", "%d", group_id);
 	}
 }
 
@@ -1355,9 +1105,9 @@
 	if (url_text == NULL || strcmp(url_text, "") == 0)
 	{
 		gaim_notify_formatted(info_data->gc, NULL,
-							  _("Buddy Information"), NULL,
-							  _("<html><body><b>Error retrieving profile</b></body></html>"),
-							  NULL, NULL);
+				_("Buddy Information"), NULL,
+				_("<html><body><b>Error retrieving profile</b></body></html>"),
+				NULL, NULL);
 
 		return;
 	}
@@ -1644,6 +1394,7 @@
 {
 	msn_notification_init();
 	msn_switchboard_init();
+	msn_sync_init();
 
 	return TRUE;
 }
@@ -1652,6 +1403,7 @@
 {
 	msn_notification_end();
 	msn_switchboard_end();
+	msn_sync_end();
 
 	return TRUE;
 }
@@ -1686,7 +1438,7 @@
 static GaimPluginProtocolInfo prpl_info =
 {
 	GAIM_PRPL_API_VERSION,
-	OPT_PROTO_MAIL_CHECK /* | OPT_PROTO_BUDDY_ICON */,
+	OPT_PROTO_MAIL_CHECK | OPT_PROTO_BUDDY_ICON,
 	NULL,
 	NULL,
 	msn_list_icon,
@@ -1728,7 +1480,7 @@
 	NULL,
 	msn_group_buddy,
 	msn_rename_group,
-	msn_buddy_free,
+	NULL, /* msn_buddy_free */
 	msn_convo_closed,
 	msn_normalize,
 	msn_set_buddy_icon,
--- a/src/protocols/msn/msn.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/msn.h	Sun Jun 06 02:39:08 2004 +0000
@@ -36,11 +36,13 @@
 #include "proxy.h"
 #include "prpl.h"
 #include "request.h"
-#include "server.h"
+#include "servconn.h"
 #include "sha.h"
 #include "sslconn.h"
 #include "util.h"
 
+#include "ft.h"
+
 /* XXX */
 #include "gaim.h"
 
@@ -111,4 +113,6 @@
 	 (MSN_CLIENT_ID_RESERVED_2 <<  8) | \
 	 (MSN_CLIENT_ID_CAPABILITIES))
 
+void msn_request_buddy_icon(GaimConnection *gc, const char *who);
+
 #endif /* _MSN_H_ */
--- a/src/protocols/msn/nexus.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/nexus.c	Sun Jun 06 02:39:08 2004 +0000
@@ -65,7 +65,6 @@
 {
 	MsnNexus *nexus;
 	MsnSession *session;
-	GaimConnection *gc;
 	char *username, *password;
 	char *request_str;
 	char *buffer = NULL;
@@ -77,9 +76,6 @@
 	session = nexus->session;
 	g_return_if_fail(session != NULL);
 
-	gc = gaim_account_get_connection(session->account);
-	g_return_if_fail(gc != NULL);
-
 	username =
 		g_strdup(gaim_url_encode(gaim_account_get_username(session->account)));
 
@@ -118,7 +114,6 @@
 	if ((s = gaim_ssl_write(gsc, request_str, strlen(request_str))) <= 0)
 	{
 		g_free(request_str);
-		gaim_connection_error(gc, _("Unable to write to MSN Nexus server."));
 
 		return;
 	}
@@ -126,10 +121,7 @@
 	g_free(request_str);
 
 	if ((s = msn_ssl_read(gsc, &buffer)) <= 0)
-	{
-		gaim_connection_error(gc, _("Unable to write to MSN Nexus server."));
 		return;
-	}
 
 	gaim_ssl_close(gsc);
 
@@ -143,14 +135,10 @@
 		location = strstr(buffer, "Location: ");
 		if (location == NULL)
 		{
-			gaim_connection_error(gc,
-				_("MSN Nexus server returned invalid redirect information."));
-
 			g_free(buffer);
 
 			return;
 		}
-
 		location = strchr(location, ' ') + 1;
 
 		if ((c = strchr(location, '\r')) != NULL)
@@ -209,7 +197,6 @@
 	}
 	else if (strstr(buffer, "HTTP/1.1 200 OK"))
 	{
-		MsnCmdProc *cmdproc;
 		char *base, *c;
 		char *login_params;
 
@@ -230,18 +217,9 @@
 		}
 #endif
 
-		cmdproc = session->notification_conn->cmdproc;
 		base  = strstr(buffer, "Authentication-Info: ");
 
-		if (base == NULL)
-		{
-			gaim_debug(GAIM_DEBUG_ERROR, "msn",
-					   "Authentication information was not found. This did "
-					   "not just happen, but if it did, you're screwed. "
-					   "Report this.\n");
-
-			return;
-		}
+		g_return_if_fail(base != NULL);
 
 		base  = strstr(base, "from-PP='");
 		base += strlen("from-PP='");
@@ -249,7 +227,7 @@
 
 		login_params = g_strndup(base, c - base);
 
-		msn_cmdproc_send(cmdproc, "USR", "TWN S %s", login_params);
+		msn_got_login_params(session, login_params);
 
 		g_free(login_params);
 
--- a/src/protocols/msn/nexus.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/nexus.h	Sun Jun 06 02:39:08 2004 +0000
@@ -3,7 +3,7 @@
 
 typedef struct _MsnNexus MsnNexus;
 
-#include "session.h"
+#include "nexus.h"
 
 struct _MsnNexus
 {
--- a/src/protocols/msn/notification.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/notification.c	Sun Jun 06 02:39:08 2004 +0000
@@ -24,174 +24,29 @@
 #include "state.h"
 #include "error.h"
 #include "utils.h"
-
-typedef struct
-{
-	GaimConnection *gc;
-	MsnUser *user;
-
-} MsnPermitAdd;
-
-static MsnTable *cbs_table = NULL;
-
-/**************************************************************************
- * Utility functions
- **************************************************************************/
-static gboolean
-add_buddy(MsnCmdProc *cmdproc, MsnUser *user)
-{
-	MsnSession *session;
-	GaimAccount *account;
-	GaimConnection *gc;
-	GaimBuddy *b;
-	MsnGroup *group = NULL;
-	GaimGroup *g = NULL;
-	GList *l, *l2;
-	GSList *sl;
-	GSList *buddies;
-
-	session = cmdproc->session;
-	account = session->account;
-	gc = gaim_account_get_connection(account);
-	buddies = gaim_find_buddies(account, msn_user_get_passport(user));
-
-	for (l = msn_user_get_group_ids(user); l != NULL; l = l->next)
-	{
-		int group_id = GPOINTER_TO_INT(l->data);
-
-		if (group_id > -1)
-			group = msn_groups_find_with_id(session->groups, group_id);
-
-		if (group == NULL)
-		{
-			gaim_debug(GAIM_DEBUG_WARNING, "msn",
-					   "Group ID %d for user %s was not defined.\n",
-					   group_id, msn_user_get_passport(user));
-
-			/* Find a group that we can stick this guy into. Lamer. */
-			l2 = msn_groups_get_list(session->groups);
-
-			if (l2 != NULL)
-			{
-				group = l2->data;
-
-				msn_user_add_group_id(user, msn_group_get_id(group));
-			}
-		}
-
-		if (group == NULL ||
-			(g = gaim_find_group(msn_group_get_name(group))) == NULL)
-		{
-			gaim_debug(GAIM_DEBUG_ERROR, "msn",
-					   "Group '%s' appears in server-side "
-					   "buddy list, but not here!",
-					   msn_group_get_name(group));
-		}
-
-		if (group != NULL)
-			msn_group_add_user(group, user);
-
-		b = NULL;
-
-		for (sl = buddies; sl != NULL; sl = sl->next)
-		{
-			b = (GaimBuddy *)sl->data;
-
-			if (gaim_find_buddys_group(b) == g)
-				break;
-
-			b = NULL;
-		}
+#include "page.h"
 
-		if (b == NULL)
-		{
-			const char *passport, *friendly;
-
-			passport = msn_user_get_passport(user);
-
-			b = gaim_buddy_new(account, passport, NULL);
-
-			b->proto_data = user;
-
-			gaim_blist_add_buddy(b, NULL, g, NULL);
-
-			if ((friendly = msn_user_get_name(user)) != NULL)
-				serv_got_alias(gc, passport, friendly);
-		}
-		else
-			b->proto_data = user;
-	}
-
-	g_slist_free(buddies);
-
-	serv_got_alias(gc, (char *)msn_user_get_passport(user),
-				   (char *)msn_user_get_name(user));
-
-	return TRUE;
-}
-
-/**************************************************************************
- * Callbacks
- **************************************************************************/
-static void
-msn_accept_add_cb(MsnPermitAdd *pa)
-{
-	if (g_list_find(gaim_connections_get_all(), pa->gc) != NULL)
-	{
-		MsnSession *session;
-		MsnCmdProc *cmdproc;
-
-		session = pa->gc->proto_data;
-		cmdproc = session->notification_conn->cmdproc;
+#include "userlist.h"
+#include "sync.h"
 
-		msn_cmdproc_send(cmdproc, "ADD", "AL %s %s",
-						 msn_user_get_passport(pa->user),
-						 gaim_url_encode(msn_user_get_name(pa->user)));
-
-		if (cmdproc->error)
-			return;
-
-		gaim_privacy_permit_add(pa->gc->account,
-								msn_user_get_passport(pa->user), TRUE);
-		gaim_account_notify_added(pa->gc->account, NULL,
-								  msn_user_get_passport(pa->user),
-								  msn_user_get_name(pa->user), NULL);
-	}
-
-	msn_user_destroy(pa->user);
-	g_free(pa);
-}
+#define BUDDY_ALIAS_MAXLEN 388
 
-static void
-msn_cancel_add_cb(MsnPermitAdd *pa)
-{
-	if (g_list_find(gaim_connections_get_all(), pa->gc) != NULL)
-	{
-		MsnSession *session;
-		MsnCmdProc *cmdproc;
-
-		session = pa->gc->proto_data;
-		cmdproc = session->notification_conn->cmdproc;
-
-		msn_cmdproc_send(cmdproc, "ADD", "BL %s %s",
-						 msn_user_get_passport(pa->user),
-						 gaim_url_encode(msn_user_get_name(pa->user)));
-
-		if (cmdproc->error)
-			return;
-
-		gaim_privacy_deny_add(pa->gc->account,
-							  msn_user_get_passport(pa->user), TRUE);
-	}
-
-	msn_user_destroy(pa->user);
-	g_free(pa);
-}
+static MsnTable *cbs_table;
 
 /**************************************************************************
  * Login
  **************************************************************************/
 
+void
+msn_got_login_params(MsnSession *session, const char *login_params)
+{
+	MsnCmdProc *cmdproc;
+
+	cmdproc = session->notification->cmdproc;
+
+	msn_cmdproc_send(cmdproc, "USR", "TWN S %s", login_params);
+}
+
 static void
 cvr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
@@ -214,8 +69,7 @@
 
 	if (strcmp(cmd->params[1], "MD5"))
 	{
-		gaim_connection_error(gc, _("Unable to login using MD5"));
-
+		msn_cmdproc_show_error(cmdproc, MSN_ERROR_MISC);
 		return;
 	}
 
@@ -254,8 +108,6 @@
 
 		gaim_connection_set_display_name(gc, friendly);
 
-		session->syncing_lists = TRUE;
-
 		msn_cmdproc_send(cmdproc, "SYN", "%s", "0");
 
 		if (cmdproc->error)
@@ -272,6 +124,7 @@
 		session->nexus = msn_nexus_new(session);
 
 		/* Parse the challenge data. */
+
 		elems = g_strsplit(cmd->params[3], ",", 0);
 
 		for (cur = elems; *cur != NULL; cur++)
@@ -325,14 +178,12 @@
 {
 	MsnSession *session;
 	GaimAccount *account;
-	GaimConnection *gc;
 	gboolean protocol_supported = FALSE;
 	char proto_str[8];
 	size_t i;
 
 	session = cmdproc->session;
 	account = session->account;
-	gc = gaim_account_get_connection(account);
 
 	g_snprintf(proto_str, sizeof(proto_str), "MSNP%d", session->protocol_ver);
 
@@ -347,21 +198,13 @@
 
 	if (!protocol_supported)
 	{
-		gaim_connection_error(gc, _("Protocol not supported"));
-
+		msn_cmdproc_show_error(cmdproc, MSN_ERROR_MISC);
 		return;
 	}
 
-	if (session->protocol_ver >= 8)
-	{
-		msn_cmdproc_send(cmdproc, "CVR",
-						 "0x0409 winnt 5.1 i386 MSNMSGR 6.0.0602 MSMSGS %s",
-						 gaim_account_get_username(account));
-	}
-	else
-	{
-		msn_cmdproc_send(cmdproc, "INF", NULL, NULL);
-	}
+	msn_cmdproc_send(cmdproc, "CVR",
+					 "0x0409 winnt 5.1 i386 MSNMSGR 6.0.0602 MSMSGS %s",
+					 gaim_account_get_username(account));
 }
 
 /**************************************************************************
@@ -370,41 +213,28 @@
 static void
 out_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	GaimConnection *gc;
-
-	gc = cmdproc->session->account->gc;
-
 	if (!g_ascii_strcasecmp(cmd->params[0], "OTH"))
-	{
-		gc->wants_to_die = TRUE;
-		gaim_connection_error(gc,
-							  _("You have been disconnected. You have "
-								"signed on from another location."));
-	}
+		msn_cmdproc_show_error(cmdproc, MSN_ERROR_SIGNOTHER);
 	else if (!g_ascii_strcasecmp(cmd->params[0], "SSD"))
-	{
-		gaim_connection_error(gc,
-							  _("You have been disconnected. The MSN servers "
-								"are going down temporarily."));
-	}
+		msn_cmdproc_show_error(cmdproc, MSN_ERROR_SERVDOWN);
 }
 
 /**************************************************************************
  * Messages
  **************************************************************************/
 static void
-msg_cmd_post(MsnCmdProc *cmdproc, char *payload, size_t len)
+msg_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload,
+			 size_t len)
 {
 	MsnMessage *msg;
 
-	msg = msn_message_new();
+	msg = msn_message_new_from_cmd(cmdproc->session, cmd);
 
 	msn_message_parse_payload(msg, payload, len);
+	/* msn_message_show_readable(msg, "Notification", TRUE); */
 
-	msg->passport = cmdproc->temp;
+	msg->remote_user = g_strdup(cmd->params[0]);
 	msn_cmdproc_process_msg(cmdproc, msg);
-	g_free(cmdproc->temp);
-	cmdproc->temp = NULL;
 
 	msn_message_destroy(msg);
 }
@@ -412,9 +242,20 @@
 static void
 msg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	cmdproc->payload_cb  = msg_cmd_post;
-	cmdproc->servconn->payload_len = atoi(cmd->params[2]);
-	cmdproc->temp = g_strdup(cmd->params[0]);
+	/* 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);
+	}
 }
 
 /**************************************************************************
@@ -423,31 +264,18 @@
 static void
 chl_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	MsnSession *session;
 	MsnTransaction *trans;
 	char buf[33];
 	const char *challenge_resp;
-	const char *challenge_str;
 	md5_state_t st;
 	md5_byte_t di[16];
 	int i;
 
-	session = cmdproc->session;
-
 	md5_init(&st);
 	md5_append(&st, (const md5_byte_t *)cmd->params[1],
 			   strlen(cmd->params[1]));
 
-	if (session->protocol_ver >= 8)
-	{
-		challenge_resp = "VT6PX?UQTM4WM%YR";
-		challenge_str  = "PROD0038W!61ZTF9";
-	}
-	else
-	{
-		challenge_resp = "Q1P7W2E4J9R8U3S5";
-		challenge_str  = "msmsgs@msnmsgr.com";
-	}
+	challenge_resp = "VT6PX?UQTM4WM%YR";
 
 	md5_append(&st, (const md5_byte_t *)challenge_resp,
 			   strlen(challenge_resp));
@@ -456,7 +284,7 @@
 	for (i = 0; i < 16; i++)
 		g_snprintf(buf + (i*2), 3, "%02x", di[i]);
 
-	trans = msn_transaction_new("QRY", "%s 32", challenge_str);
+	trans = msn_transaction_new("QRY", "%s 32", "PROD0038W!61ZTF9");
 
 	msn_transaction_set_payload(trans, buf, 32);
 
@@ -471,57 +299,34 @@
 {
 	MsnSession *session;
 	MsnUser *user;
-	GaimAccount *account;
-	GaimConnection *gc;
-	MsnPermitAdd *pa;
-	GSList *sl;
-	const char *list, *passport, *group_id = NULL;
-	const char *friend;
-	char msg[MSN_BUF_LEN];
-
-	session = cmdproc->session;
-	account = session->account;
-	gc = gaim_account_get_connection(account);
+	const char *list;
+	const char *passport;
+	const char *friendly;
+	MsnListId list_id;
+	int group_id;
 
 	list     = cmd->params[1];
 	passport = cmd->params[3];
-	friend   = gaim_url_decode(cmd->params[4]);
-
-	if (cmd->param_count >= 6)
-		group_id = cmd->params[5];
-
-	if (!g_ascii_strcasecmp(list, "FL"))
-	{
-		user = msn_user_new(session, passport, NULL);
+	friendly = gaim_url_decode(cmd->params[4]);
 
-		if (group_id != NULL)
-			msn_user_add_group_id(user, atoi(group_id));
-
-		add_buddy(cmdproc, user);
+	session = cmdproc->session;
 
-		return;
-	}
-	else if (g_ascii_strcasecmp(list, "RL"))
-		return;
+	user = msn_userlist_find_user(session->userlist, passport);
 
-	for (sl = gc->account->permit; sl != NULL; sl = sl->next)
-		if (!gaim_utf8_strcasecmp(sl->data, passport))
-			return;
-
-	user = msn_user_new(session, passport, friend);
-	msn_user_set_name(user, friend);
+	if (user == NULL)
+	{
+		user = msn_user_new(session->userlist, passport, friendly);
+		msn_userlist_add_user(session->userlist, user);
+	}
 
-	pa       = g_new0(MsnPermitAdd, 1);
-	pa->user = user;
-	pa->gc   = gc;
+	list_id = msn_get_list_id(list);
+	
+	if (cmd->param_count >= 6)
+		group_id = atoi(cmd->params[5]);
+	else
+		group_id = -1;
 
-	g_snprintf(msg, sizeof(msg),
-			   _("The user %s (%s) wants to add %s to his or her buddy list."),
-			   passport, friend, gaim_account_get_username(account));
-
-	gaim_request_action(gc, NULL, msg, NULL, 0, pa, 2,
-						_("Authorize"), G_CALLBACK(msn_accept_add_cb),
-						_("Deny"), G_CALLBACK(msn_cancel_add_cb));
+	msn_got_add_user(session, user, list_id, group_id);
 }
 
 static void
@@ -585,6 +390,7 @@
 	g_strfreev(params);
 }
 
+
 static void
 adg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
@@ -599,98 +405,24 @@
 
 	group_name = gaim_url_decode(cmd->params[2]);
 
-	group = msn_group_new(session, group_id, group_name);
-
-	msn_groups_add(session->groups, group);
-}
-
-static void
-blp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
-{
-	GaimConnection *gc;
-	const char *list_name;
-
-	gc = cmdproc->session->account->gc;
-
-	if (cmdproc->session->protocol_ver >= 8)
-		list_name = cmd->params[0];
-	else
-		list_name = cmd->params[2];
+	group = msn_group_new(session->userlist, group_id, group_name);
 
-	if (!g_ascii_strcasecmp(list_name, "AL"))
+	/* There is a user that must me moved to this group */
+	if (cmd->trans->data)
 	{
-		/*
-		 * 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 = GAIM_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 = GAIM_PRIVACY_ALLOW_USERS;
-	}
-}
+		/* msn_userlist_move_buddy(); */
+		MsnUserList *userlist = cmdproc->session->userlist;
+		MsnMoveBuddy *data = cmd->trans->data;
 
-static void
-bpr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
-{
-	MsnSession *session;
-	GaimConnection *gc;
-	const char *passport, *type, *value;
-	GaimBuddy *b;
-	MsnUser *user;
-
-	session = cmdproc->session;
-	gc = session->account->gc;
-
-	if (cmd->param_count == 4)
-	{
-		passport = cmd->params[1];
-		type     = cmd->params[2];
-		value    = cmd->params[3];
-	}
-	else if (cmd->param_count == 2)
-	{
-		passport = msn_user_get_passport(session->last_user_added);
-		type     = cmd->params[0];
-		value    = cmd->params[1];
-	}
-	else
-		return;
+		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);
 
-	user = msn_users_find_with_passport(session->users, passport);
-
-	if (value != NULL)
-	{
-		if (!strcmp(type, "MOB"))
-		{
-			if (!strcmp(value, "Y") || !strcmp(value, "N"))
-			{
-				user->mobile = (!strcmp(value, "Y") ? TRUE : FALSE);
-
-				if ((b = gaim_find_buddy(gc->account, passport)) != NULL)
-				{
-					if (GAIM_BUDDY_IS_ONLINE(b))
-					{
-						serv_got_update(gc, (char *)passport,
-										1, 0, 0, 0, b->uc);
-					}
-				}
-			}
-		}
-		else if (!strcmp(type, "PHH"))
-			msn_user_set_home_phone(user, gaim_url_decode(value));
-		else if (!strcmp(type, "PHW"))
-			msn_user_set_work_phone(user, gaim_url_decode(value));
-		else if (!strcmp(type, "PHM"))
-			msn_user_set_mobile_phone(user, gaim_url_decode(value));
 	}
 }
 
@@ -712,7 +444,7 @@
 	MsnUser *user;
 	MsnObject *msnobj;
 	int status = 0;
-	const char *state, *passport, *friend;
+	const char *state, *passport, *friendly;
 	GaimBuddy *b;
 
 	session = cmdproc->session;
@@ -720,13 +452,14 @@
 
 	state    = cmd->params[1];
 	passport = cmd->params[2];
-	friend   = gaim_url_decode(cmd->params[3]);
+	friendly = gaim_url_decode(cmd->params[3]);
 
-	user = msn_users_find_with_passport(session->users, passport);
+	user = msn_userlist_find_user(session->userlist, passport);
 
-	serv_got_alias(gc, passport, friend);
+	/* serv_got_nick(gc, passport, friendly); */
+	serv_got_alias(gc, passport, friendly);
 
-	msn_user_set_name(user, friend);
+	msn_user_set_friendly_name(user, friendly);
 
 	if (session->protocol_ver >= 9 && cmd->param_count == 6)
 	{
@@ -750,373 +483,22 @@
 	else if (!g_ascii_strcasecmp(state, "LUN"))
 		status |= UC_UNAVAILABLE | (MSN_LUNCH << 1);
 
-	serv_got_update(gc, (char *)passport, 1, 0, 0, 0, status);
+	serv_got_update(gc, passport, 1, 0, 0, 0, status);
+}
+
+static void
+ipg_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len)
+{
+#if 0
+	gaim_debug_misc("msn", "Incoming Page: {%s}\n", payload);
+#endif
 }
 
 static void
 ipg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	cmdproc->payload_cb = NULL;
 	cmdproc->servconn->payload_len = atoi(cmd->params[0]);
-}
-
-static void
-lsg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
-{
-	MsnSession *session;
-	MsnGroup *group;
-	GaimGroup *g;
-	const char *name;
-	int num_groups, group_id;
-
-	session = cmdproc->session;
-
-	if (session->protocol_ver >= 8)
-	{
-		group_id = atoi(cmd->params[0]);
-		name = gaim_url_decode(cmd->params[1]);
-	}
-	else
-	{
-		num_groups = atoi(cmd->params[3]);
-		group_id   = atoi(cmd->params[4]);
-		name       = gaim_url_decode(cmd->params[5]);
-
-		if (num_groups == 0)
-			return;
-
-		if (!strcmp(name, "~"))
-			name = _("Buddies");
-	}
-
-	group = msn_group_new(session, group_id, name);
-
-	msn_groups_add(session->groups, group);
-
-	if ((g = gaim_find_group(name)) == NULL)
-	{
-		g = gaim_group_new(name);
-		gaim_blist_add_group(g, NULL);
-	}
-}
-
-static void
-lst_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
-{
-	MsnSession *session;
-	GaimAccount *account;
-	GaimConnection *gc;
-	const char *passport = NULL;
-	const char *friend = NULL;
-
-	session = cmdproc->session;
-	account = session->account;
-	gc = gaim_account_get_connection(account);
-
-	if (session->protocol_ver >= 8)
-	{
-		const char *group_nums;
-		int list_op;
-
-		passport   = cmd->params[0];
-		friend     = gaim_url_decode(cmd->params[1]);
-		list_op    = atoi(cmd->params[2]);
-		group_nums = cmd->params[3];
-
-		if (list_op & MSN_LIST_FL_OP)
-		{
-			MsnUser *user;
-			char **c;
-			char **tokens;
-
-			user = msn_user_new(session, passport, friend);
-
-			/* Ensure we have a friendly name set. */
-			msn_user_set_name(user, friend);
-
-			tokens = g_strsplit((group_nums ? group_nums : ""), ",", -1);
-
-			gaim_debug_misc("msn", "Fetching group IDs from '%s'\n",
-							group_nums);
-			for (c = tokens; *c != NULL; c++)
-			{
-				gaim_debug_misc("msn", "Appending group ID %d\n", atoi(*c));
-				msn_user_add_group_id(user, atoi(*c));
-			}
-
-			g_strfreev(tokens);
-
-			session->lists.forward =
-				g_slist_append(session->lists.forward, user);
-
-			session->last_user_added = user;
-		}
-
-		if (list_op & MSN_LIST_AL_OP)
-		{
-			/* These are users who are allowed to see our status. */
-
-			if (g_slist_find_custom(account->deny, passport,
-									(GCompareFunc)strcmp))
-			{
-				gaim_privacy_deny_remove(gc->account, passport, TRUE);
-			}
-
-			gaim_privacy_permit_add(account, passport, TRUE);
-		}
-
-		if (list_op & MSN_LIST_BL_OP)
-		{
-			/* These are users who are not allowed to see our status. */
-			gaim_privacy_deny_add(account, passport, TRUE);
-		}
-
-		if (list_op & MSN_LIST_RL_OP)
-		{
-			/* These are users who have us on their contact list. */
-
-			gboolean new_entry = TRUE;
-
-			if (g_slist_find_custom(account->permit, passport,
-									(GCompareFunc)g_ascii_strcasecmp) ||
-				g_slist_find_custom(account->deny, passport,
-									(GCompareFunc)g_ascii_strcasecmp))
-			{
-				new_entry = FALSE;
-			}
-
-			if (new_entry)
-			{
-				MsnPermitAdd *pa;
-				char msg[MSN_BUF_LEN];
-
-				pa       = g_new0(MsnPermitAdd, 1);
-				pa->user = msn_user_new(session, passport, friend);
-				pa->gc   = gc;
-
-				/* Ensure we have a friendly name set. */
-				msn_user_set_name(pa->user, friend);
-
-				g_snprintf(msg, sizeof(msg),
-						   _("The user %s (%s) wants to add you to their "
-							 "buddy list."),
-						   msn_user_get_passport(pa->user),
-						   msn_user_get_name(pa->user));
-
-				gaim_request_action(gc, NULL, msg, NULL, 0, pa, 2,
-									_("Authorize"),
-									G_CALLBACK(msn_accept_add_cb),
-									_("Deny"),
-									G_CALLBACK(msn_cancel_add_cb));
-			}
-		}
-
-		session->num_users++;
-
-		if (session->num_users == session->total_users)
-		{
-			msn_user_set_buddy_icon(session->user,
-				gaim_account_get_buddy_icon(session->account));
-
-			if (!msn_session_change_status(session, "NLN"))
-				return;
-
-			gaim_connection_set_state(gc, GAIM_CONNECTED);
-			serv_finish_login(gc);
-
-			if (session->lists.allow == NULL)
-				session->lists.allow = g_slist_copy(account->permit);
-			else
-				session->lists.allow = g_slist_concat(session->lists.allow,
-													  account->permit);
-
-			if (session->lists.block == NULL)
-				session->lists.block = g_slist_copy(account->permit);
-			else
-				session->lists.block = g_slist_concat(session->lists.block,
-													  account->deny);
-
-			while (session->lists.forward != NULL)
-			{
-				MsnUser *user = session->lists.forward->data;
-				GSList *buddies;
-				GSList *sl;
-
-				session->lists.forward =
-					g_slist_remove(session->lists.forward, user);
-
-				add_buddy(cmdproc, user);
-
-				buddies = gaim_find_buddies(account,
-											msn_user_get_passport(user));
-
-				/* Find all occurrences of this buddy in the wrong place. */
-				for (sl = buddies; sl != NULL; sl = sl->next)
-				{
-					GaimBuddy *b = sl->data;
-
-					if (b->proto_data == NULL)
-					{
-						gaim_debug_warning("msn",
-							"Deleting misplaced user %s (%s) during sync "
-							"with server.\n",
-							b->name, gaim_find_buddys_group(b)->name);
-
-						gaim_blist_remove_buddy(b);
-					}
-				}
-
-				g_slist_free(buddies);
-			}
-
-			session->syncing_lists = FALSE;
-			session->lists_synced  = TRUE;
-		}
-	}
-	else
-	{
-		const char *list_name;
-		int user_num;
-		int num_users;
-
-		list_name = cmd->params[1];
-		user_num  = atoi(cmd->params[3]);
-		num_users = atoi(cmd->params[4]);
-
-		if (g_ascii_strcasecmp(list_name, "RL") &&
-			user_num == 0 && num_users == 0)
-		{
-			return; /* There are no users on this list. */
-		}
-
-		if (num_users > 0)
-		{
-			passport  = cmd->params[5];
-			friend    = gaim_url_decode(cmd->params[6]);
-		}
-
-		if (session->syncing_lists && session->lists_synced)
-			return;
-
-		if (!g_ascii_strcasecmp(list_name, "FL") && user_num != 0)
-		{
-			/* These are users on our contact list. */
-			MsnUser *user;
-
-			user = msn_user_new(session, passport, friend);
-
-			if (cmd->param_count == 8)
-				msn_user_add_group_id(user, atoi(cmd->params[7]));
-
-			session->lists.forward =
-				g_slist_append(session->lists.forward, user);
-		}
-		else if (!g_ascii_strcasecmp(list_name, "AL") && user_num != 0)
-		{
-			/* These are users who are allowed to see our status. */
-			if (g_slist_find_custom(gc->account->deny, passport,
-									(GCompareFunc)strcmp))
-			{
-				gaim_debug(GAIM_DEBUG_INFO, "msn",
-						   "Moving user from deny list to permit: %s (%s)\n",
-						   passport, friend);
-
-				gaim_privacy_deny_remove(gc->account, passport, TRUE);
-			}
-
-			gaim_privacy_permit_add(gc->account, passport, TRUE);
-		}
-		else if (!g_ascii_strcasecmp(list_name, "BL") && user_num != 0)
-		{
-			/* These are users who are not allowed to see our status. */
-			gaim_privacy_deny_add(gc->account, passport, TRUE);
-		}
-		else if (!g_ascii_strcasecmp(list_name, "RL"))
-		{
-			/* These are users who have us on their contact list. */
-			if (user_num > 0)
-			{
-				gboolean new_entry = TRUE;
-
-				if (g_slist_find_custom(gc->account->permit, passport,
-										(GCompareFunc)g_ascii_strcasecmp))
-				{
-					new_entry = FALSE;
-				}
-
-				if (g_slist_find_custom(gc->account->deny, passport,
-										(GCompareFunc)g_ascii_strcasecmp))
-				{
-					new_entry = FALSE;
-				}
-
-				if (new_entry)
-				{
-					MsnPermitAdd *pa;
-					char msg[MSN_BUF_LEN];
-
-					gaim_debug(GAIM_DEBUG_WARNING, "msn",
-							   "Unresolved MSN RL entry: %s\n", passport);
-
-					pa       = g_new0(MsnPermitAdd, 1);
-					pa->user = msn_user_new(session, passport, friend);
-					pa->gc   = gc;
-
-					msn_user_set_name(pa->user, friend);
-
-					g_snprintf(msg, sizeof(msg),
-							   _("The user %s (%s) wants to add you to their "
-								 "buddy list."),
-							   msn_user_get_passport(pa->user),
-							   msn_user_get_name(pa->user));
-
-					gaim_request_action(gc, NULL, msg, NULL, 0, pa, 2,
-										_("Authorize"),
-										G_CALLBACK(msn_accept_add_cb),
-										_("Deny"),
-										G_CALLBACK(msn_cancel_add_cb));
-				}
-			}
-
-			if (user_num != num_users)
-				return; /* This isn't the last one in the RL. */
-
-			/* Now we're at the last one, so we can do final work. */
-			if (!session->lists_synced)
-			{
-				if (!msn_session_change_status(session, "NLN"))
-					return;
-
-				gaim_connection_set_state(gc, GAIM_CONNECTED);
-				serv_finish_login(gc);
-			}
-
-			if (session->lists.allow == NULL)
-				session->lists.allow = g_slist_copy(gc->account->permit);
-			else
-				session->lists.allow = g_slist_concat(session->lists.allow,
-													  gc->account->permit);
-
-			if (session->lists.block == NULL)
-				session->lists.block = g_slist_copy(gc->account->deny);
-			else
-				session->lists.block = g_slist_concat(session->lists.block,
-													  gc->account->deny);
-
-			while (session->lists.forward != NULL)
-			{
-				MsnUser *user = session->lists.forward->data;
-
-				session->lists.forward =
-					g_slist_remove(session->lists.forward, user);
-
-				add_buddy(cmdproc, user);
-			}
-
-			session->syncing_lists = FALSE;
-			session->lists_synced  = TRUE;
-		}
-	}
+	cmdproc->last_cmd->payload_cb = ipg_cmd_post;
 }
 
 static void
@@ -1128,7 +510,7 @@
 	MsnObject *msnobj;
 	const char *state;
 	const char *passport;
-	const char *friend;
+	const char *friendly;
 	int status = 0;
 
 	session = cmdproc->session;
@@ -1136,13 +518,14 @@
 
 	state    = cmd->params[0];
 	passport = cmd->params[1];
-	friend   = gaim_url_decode(cmd->params[2]);
+	friendly = gaim_url_decode(cmd->params[2]);
 
-	user = msn_users_find_with_passport(session->users, passport);
+	user = msn_userlist_find_user(session->userlist, passport);
 
-	serv_got_alias(gc, passport, friend);
+	/* serv_got_nick(gc, passport, friendly); */
+	serv_got_alias(gc, passport, friendly);
 
-	msn_user_set_name(user, friend);
+	msn_user_set_friendly_name(user, friendly);
 
 	if (session->protocol_ver >= 9 && cmd->param_count == 5)
 	{
@@ -1167,9 +550,37 @@
 }
 
 static void
-not_cmd_post(MsnCmdProc *cmdproc, char *payload, size_t len)
+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;
+}
+
+
+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);
 	gaim_debug_misc("msn", "Notification: {%s}\n", payload);
 #endif
 }
@@ -1178,153 +589,76 @@
 not_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
 	cmdproc->servconn->payload_len = atoi(cmd->params[0]);
-	cmdproc->payload_cb = not_cmd_post;
-}
-
-static void
-prp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
-{
-	MsnSession *session;
-	const char *type, *value;
-
-	session = cmdproc->session;
-
-	type  = cmd->params[2];
-	value = cmd->params[3];
-
-	if (cmd->param_count == 4)
-	{
-		if (!strcmp(type, "PHH"))
-			msn_user_set_home_phone(session->user, gaim_url_decode(value));
-		else if (!strcmp(type, "PHW"))
-			msn_user_set_work_phone(session->user, gaim_url_decode(value));
-		else if (!strcmp(type, "PHM"))
-			msn_user_set_mobile_phone(session->user, gaim_url_decode(value));
-	}
+	cmdproc->last_cmd->payload_cb = not_cmd_post;
 }
 
 static void
 rea_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
+	/* TODO: This might be with us too */
+
 	MsnSession *session;
 	GaimConnection *gc;
-	const char *friend;
+	const char *friendly;
 
 	session = cmdproc->session;
 	gc = session->account->gc;
-	friend = gaim_url_decode(cmd->params[3]);
+	friendly = gaim_url_decode(cmd->params[3]);
 
-	gaim_connection_set_display_name(gc, friend);
+	gaim_connection_set_display_name(gc, friendly);
 }
 
 static void
 reg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
 	MsnSession *session;
-	MsnGroup *group;
 	int group_id;
 	const char *group_name;
 
 	session = cmdproc->session;
 	group_id = atoi(cmd->params[2]);
-
 	group_name = gaim_url_decode(cmd->params[3]);
 
-	group = msn_groups_find_with_id(session->groups, group_id);
-
-	gaim_debug(GAIM_DEBUG_INFO, "msn", "Renamed group %s to %s\n",
-			   msn_group_get_name(group), group_name);
-
-	if (group != NULL)
-		msn_group_set_name(group, group_name);
+	msn_userlist_rename_group_id(session->userlist, group_id, group_name);
 }
 
 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)
-	{
-		MsnUser *user;
-		int group_id = atoi(cmd->params[4]);
-
-		user = msn_users_find_with_passport(session->users, passport);
-
-		msn_user_remove_group_id(user, group_id);
-	}
-
-	/* I hate this. */
-	/* shx: it won't be here for long. */
-	if (session->moving_buddy)
-	{
-		MsnGroup *group, *old_group;
-		const char *friendly;
-
-		group = msn_groups_find_with_name(session->groups,
-										  session->dest_group_name);
-
-		old_group = session->old_group;
-
-		session->moving_buddy = FALSE;
-		session->old_group    = NULL;
-
-		if (group == NULL)
-		{
-			gaim_debug_error("msn",
-							 "Still don't have a group ID for %s while moving %s!\n",
-							 session->dest_group_name, passport);
+		group_id = atoi(cmd->params[4]);
+	else
+		group_id = -1;
 
-			g_free(session->dest_group_name);
-			session->dest_group_name = NULL;
-
-			return;
-		}
-
-		g_free(session->dest_group_name);
-		session->dest_group_name = NULL;
-
-		if ((friendly = msn_user_get_name(session->moving_user)) == NULL)
-			friendly = passport;
-
-		msn_cmdproc_send(cmdproc, "ADD", "FL %s %s %d",
-						 passport, gaim_url_encode(friendly),
-						 msn_group_get_id(group));
-
-		if (cmdproc->error)
-			return;
-
-		if (old_group != NULL)
-			msn_group_remove_user(old_group, session->moving_user);
-
-		msn_user_unref(session->moving_user);
-
-		session->moving_user = NULL;
-
-		if (old_group != NULL &&
-			msn_users_get_count(msn_group_get_users(old_group)) <= 0)
-		{
-			msn_cmdproc_send(cmdproc, "RMG", "%s", "%d",
-							 msn_group_get_id(old_group));
-		}
-	}
+	msn_got_rem_user(session, user, list_id, group_id);
 }
 
 static void
 rmg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
 	MsnSession *session;
-	MsnGroup *group;
+	int group_id;
 
 	session = cmdproc->session;
-	group = msn_groups_find_with_id(session->groups, atoi(cmd->params[2]));
+	group_id = atoi(cmd->params[2]);
 
-	if (group != NULL)
-		msn_groups_remove(session->groups, group);
+	msn_userlist_remove_group_id(session->userlist, group_id);
 }
 
 static void
@@ -1332,44 +666,28 @@
 {
 	MsnSession *session;
 	GaimConnection *gc;
+	int total_users;
 
 	session = cmdproc->session;
 	gc = gaim_account_get_connection(session->account);
-
-	if (session->protocol_ver >= 8)
-	{
-		if (cmd->param_count == 2)
-		{
-			char *buf;
-
-			/*
-			 * 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.
-			 */
-			buf = g_strdup_printf(
-				_("Your MSN buddy list for %s is temporarily unavailable. "
-				"Please wait and try again."),
-				gaim_account_get_username(session->account));
+	total_users  = atoi(cmd->params[2]);
 
-			gaim_connection_error(gc, buf);
-
-			g_free(buf);
-
-			return;
-		}
+	if (total_users == 0)
+	{
+		gaim_connection_set_state(gc, GAIM_CONNECTED);
+		serv_finish_login(gc);
+	}
+	else
+	{
+		/* syn_table */
+		MsnSync *sync;
 
-		session->total_users  = atoi(cmd->params[2]);
-		session->total_groups = atoi(cmd->params[3]);
+		sync = msn_sync_new(session);
+		sync->total_users = total_users;
+		sync->old_cbs_table = cmdproc->cbs_table;
 
-		if (session->total_users == 0)
-		{
-			gaim_connection_set_state(gc, GAIM_CONNECTED);
-			serv_finish_login(gc);
-
-			session->syncing_lists = FALSE;
-			session->lists_synced  = TRUE;
-		}
+		session->sync = sync;
+		cmdproc->cbs_table = sync->cbs_table;
 	}
 }
 
@@ -1480,7 +798,9 @@
 			 */
 			char *tmp;
 
-			if ((tmp = g_strdup_printf("%s.html", session->passport_info.file)) != NULL)
+			if ((tmp =
+				 g_strdup_printf("%s.html", session->passport_info.file))
+				!= NULL)
 			{
 				if (rename(session->passport_info.file, tmp) == 0)
 				{
@@ -1501,46 +821,27 @@
 {
 	MsnSession *session;
 	MsnSwitchBoard *swboard;
-	MsnUser *user;
 	const char *session_id;
-	char *host, *c;
+	char *host;
 	int port;
 
 	session = cmdproc->session;
 	session_id = cmd->params[0];
 
-	host = g_strdup(cmd->params[1]);
-
-	if ((c = strchr(host, ':')) != NULL)
-	{
-		*c = '\0';
-		port = atoi(c + 1);
-	}
-	else
-		port = 1863;
+	msn_parse_socket(cmd->params[1], &host, &port);
 
 	if (session->http_method)
 		port = 80;
 
 	swboard = msn_switchboard_new(session);
 
-	user = msn_user_new(session, cmd->params[4], NULL);
-
 	msn_switchboard_set_invited(swboard, TRUE);
 	msn_switchboard_set_session_id(swboard, cmd->params[0]);
 	msn_switchboard_set_auth_key(swboard, cmd->params[3]);
-	msn_switchboard_set_user(swboard, user);
+	swboard->im_user = g_strdup(cmd->params[4]);
+	/* msn_switchboard_add_user(swboard, cmd->params[4]); */
 
-	if (!msn_switchboard_connect(swboard, host, port))
-	{
-		gaim_debug_error("msn",
-						 "Unable to connect to switchboard on %s, port %d\n",
-						 host, port);
-
-		g_free(host);
-
-		return;
-	}
+	msn_switchboard_connect(swboard, host, port);
 
 	g_free(host);
 }
@@ -1548,73 +849,41 @@
 static void
 xfr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	MsnSession *session;
-	MsnSwitchBoard *swboard;
-	GaimConnection *gc;
 	char *host;
-	char *c;
 	int port;
 
-	session = cmdproc->session;
-	gc = session->account->gc;
-
 	if (strcmp(cmd->params[1], "SB") && strcmp(cmd->params[1], "NS"))
 	{
-		gaim_connection_error(gc, _("Got invalid XFR"));
-
+		msn_cmdproc_show_error(cmdproc, MSN_ERROR_MISC);
 		return;
 	}
 
-	host = g_strdup(cmd->params[2]);
-
-	if ((c = strchr(host, ':')) != NULL)
-	{
-		*c = '\0';
-		port = atoi(c + 1);
-	}
-	else
-		port = 1863;
+	msn_parse_socket(cmd->params[2], &host, &port);
 
 	if (!strcmp(cmd->params[1], "SB"))
 	{
-		swboard = msn_session_find_unused_switch(session);
-
-		if (swboard == NULL)
-		{
-			gaim_debug_error("msn",
-							 "Received an XFR SB request when there's no unused "
-							 "switchboards!\n");
-			return;
-		}
-
-		msn_switchboard_set_auth_key(swboard, cmd->params[4]);
+		gaim_debug_error("msn", "This shouldn't be handled here.\n");
+#if 0
+		swboard = cmd->trans->data;
 
-		if (session->http_method)
-			port = 80;
-
-		if (!msn_switchboard_connect(swboard, host, port))
+		if (swboard != NULL)
 		{
-			gaim_debug_error("msn",
-							 "Unable to connect to switchboard on %s, port %d\n",
-							 host, port);
+			msn_switchboard_set_auth_key(swboard, cmd->params[4]);
 
-			g_free(host);
+			if (session->http_method)
+				port = 80;
 
-			return;
+			msn_switchboard_connect(swboard, host, port);
 		}
+#endif
 	}
 	else if (!strcmp(cmd->params[1], "NS"))
 	{
-		if (!msn_notification_connect(session->notification_conn, host,
-									  port))
-		{
-			gaim_connection_error(gc, _("Unable to transfer to "
-										"notification server"));
+		MsnSession *session;
 
-			g_free(host);
+		session = cmdproc->session;
 
-			return;
-		}
+		msn_notification_connect(session->notification, host, port);
 	}
 
 	g_free(host);
@@ -1628,16 +897,12 @@
 {
 	MsnSession *session;
 	const char *value;
-	const char *passport;
 
 	session = cmdproc->session;
-	passport = msg->passport;
 
-	if (strcmp(passport, "Hotmail"))
-	{
+	if (strcmp(msg->remote_user, "Hotmail"))
 		/* This isn't an official message. */
 		return;
-	}
 
 	if ((value = msn_message_get_attr(msg, "kv")) != NULL)
 		session->passport_info.kv = g_strdup(value);
@@ -1662,26 +927,24 @@
 	GaimConnection *gc;
 	GHashTable *table;
 	const char *unread;
-	const char *passport;
 
 	session = cmdproc->session;
 	gc = session->account->gc;
-	passport = msg->passport;
 
-	if (strcmp(passport, "Hotmail"))
-	{
+	if (strcmp(msg->remote_user, "Hotmail"))
 		/* This isn't an official message. */
 		return;
-	}
 
 	if (!gaim_account_get_check_mail(session->account))
 		return;
 
 	if (session->passport_info.file == NULL)
 	{
-		msn_cmdproc_send(cmdproc, "URL", "%s", "INBOX");
+		MsnTransaction *trans;
+		trans = msn_transaction_new("URL", "%s", "INBOX");
+		msn_transaction_queue_cmd(trans, msg->cmd);
 
-		msn_cmdproc_queue_message(cmdproc, "URL", msg);
+		msn_cmdproc_send_trans(cmdproc, trans);
 
 		return;
 	}
@@ -1694,20 +957,16 @@
 	{
 		int count = atoi(unread);
 
-		if (count != 0)
+		if (count > 0)
 		{
 			const char *passport;
-			const char *file;
-			gchar *url;
+			const char *url;
 
 			passport = msn_user_get_passport(session->user);
-			file = session->passport_info.file;
-			while (*file && *file == '/')
-				++file;
-			url = g_strconcat ("file:///", file, 0);
-			gaim_notify_emails(gc, count, FALSE, NULL, NULL,
-							   &passport, (const char **)&url, NULL, NULL);
-			g_free (url);
+			url = session->passport_info.file;
+
+			gaim_notify_emails(gc, atoi(unread), FALSE, NULL, NULL,
+							   &passport, &url, NULL, NULL);
 		}
 	}
 
@@ -1721,34 +980,32 @@
 	GaimConnection *gc;
 	GHashTable *table;
 	char *from, *subject, *tmp;
-	const char *passport;
 
 	session = cmdproc->session;
 	gc = session->account->gc;
-	passport = msg->passport;
 
-	from = subject = NULL;
-
-	if (strcmp(passport, "Hotmail"))
-	{
+	if (strcmp(msg->remote_user, "Hotmail"))
 		/* This isn't an official message. */
 		return;
-	}
 
 	if (!gaim_account_get_check_mail(session->account))
 		return;
 
 	if (session->passport_info.file == NULL)
 	{
-		msn_cmdproc_send(cmdproc, "URL", "%s", "INBOX");
+		MsnTransaction *trans;
+		trans = msn_transaction_new("URL", "%s", "INBOX");
+		msn_transaction_queue_cmd(trans, msg->cmd);
 
-		msn_cmdproc_queue_message(cmdproc, "URL", msg);
+		msn_cmdproc_send_trans(cmdproc, trans);
 
 		return;
 	}
 
 	table = msn_message_get_hashtable_from_body(msg);
 
+	from = subject = NULL;
+
 	tmp = g_hash_table_lookup(table, "From");
 	if (tmp != NULL)
 		from = gaim_mime_decode_field(tmp);
@@ -1777,15 +1034,10 @@
 {
 	GHashTable *table;
 	const char *type_s;
-	const char *passport;
 
-	passport = msg->passport;
-
-	if (strcmp(passport, "Hotmail"))
-	{
+	if (strcmp(msg->remote_user, "Hotmail"))
 		/* This isn't an official message. */
 		return;
-	}
 
 	table = msn_message_get_hashtable_from_body(msg);
 
@@ -1824,18 +1076,20 @@
 	g_hash_table_destroy(table);
 }
 
-static gboolean
+static void
 connect_cb(MsnServConn *servconn)
 {
+	MsnNotification *notification;
 	MsnCmdProc *cmdproc;
 	MsnSession *session;
 	GaimAccount *account;
 	GaimConnection *gc;
 	char **a, **c, *vers;
-	size_t i;
+	int i;
 
-	g_return_val_if_fail(servconn != NULL, FALSE);
+	g_return_if_fail(servconn != NULL);
 
+	notification = servconn->data;
 	cmdproc = servconn->cmdproc;
 	session = servconn->session;
 	account = session->account;
@@ -1857,34 +1111,63 @@
 	g_free(vers);
 	
 	if (cmdproc->error)
-		return FALSE;
+		return;
 
-	session->user = msn_user_new(session,
+	session->user = msn_user_new(session->userlist,
 								 gaim_account_get_username(account), NULL);
 
+#if 0
 	gaim_connection_update_progress(gc, _("Syncing with server"),
 									4, MSN_CONNECT_STEPS);
+#endif
+}
 
-	return TRUE;
+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)
+	{
+		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);
+	}
 }
 
 void
 msn_notification_init(void)
 {
+	/* TODO: check prp, blp */
+
 	cbs_table = msn_table_new();
 
-	/* Register the command callbacks. */
-
-	/* 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);
-
 	/* Syncronous */
-	/* msn_table_add_cmd(cbs_table, "CHG", "CHG", chg_cmd); */
+	msn_table_add_cmd(cbs_table, "CHG", "CHG", chg_cmd);
 	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);
@@ -1921,10 +1204,12 @@
 	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, NULL, "XFR", xfr_cmd); */
 
 	msn_table_add_error(cbs_table, "ADD", add_error);
+	/* msn_table_add_error(cbs_table, "REA", rea_error); */
 
-	/* Register the message type callbacks. */
 	msn_table_add_msg_type(cbs_table,
 						   "text/x-msmsgsprofile",
 						   profile_msg);
@@ -1945,29 +1230,56 @@
 	msn_table_destroy(cbs_table);
 }
 
-MsnServConn *
+MsnNotification *
 msn_notification_new(MsnSession *session)
 {
-	MsnServConn *notification;
-	MsnCmdProc *cmdproc;
+	MsnNotification *notification;
+	MsnServConn *servconn;
+
+	g_return_val_if_fail(session != NULL, NULL);
 
-	notification = msn_servconn_new(session, MSN_SERVER_NS);
-	cmdproc = notification->cmdproc;
+	notification = g_new0(MsnNotification, 1);
 
-	msn_servconn_set_connect_cb(notification, connect_cb);
+	notification->session = session;
+	notification->servconn = servconn = msn_servconn_new(session, MSN_SERVER_NS);
+	notification->cmdproc = servconn->cmdproc;
+	msn_servconn_set_connect_cb(servconn, connect_cb);
 
 	if (session->http_method)
-		notification->http_data->server_type = "NS";
+		servconn->http_data->server_type = "NS";
 
-	cmdproc->cbs_table  = cbs_table;
+	servconn->cmdproc->cbs_table = cbs_table;
 
 	return notification;
 }
 
+void
+msn_notification_destroy(MsnNotification *notification)
+{
+	msn_servconn_destroy(notification->servconn);
+
+	g_free(notification);
+}
+
 gboolean
-msn_notification_connect(MsnServConn *notification, const char *host, int port)
+msn_notification_connect(MsnNotification *notification, const char *host, int port)
 {
+	MsnServConn *servconn;
+
 	g_return_val_if_fail(notification != NULL, FALSE);
 
-	return msn_servconn_connect(notification, host, port);
+	servconn = notification->servconn;
+
+	return (notification->in_use = msn_servconn_connect(servconn, host, port));
 }
+
+void
+msn_notification_disconnect(MsnNotification *notification)
+{
+	g_return_if_fail(notification != NULL);
+
+	notification->in_use = FALSE;
+
+	if (notification->servconn->connected)
+		msn_servconn_disconnect(notification->servconn);
+}
--- a/src/protocols/msn/notification.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/notification.h	Sun Jun 06 02:39:08 2004 +0000
@@ -22,22 +22,38 @@
 #ifndef _MSN_NOTIFICATION_H_
 #define _MSN_NOTIFICATION_H_
 
+typedef struct _MsnNotification MsnNotification;
+
 #include "session.h"
 #include "servconn.h"
+#include "cmdproc.h"
 
-/**
- * Initialize the variables for notifiaction server creation.
- */
+struct _MsnNotification
+{
+	MsnSession *session;
+	MsnCmdProc *cmdproc;
+	MsnServConn *servconn;
+
+	gboolean in_use;
+};
+
+#include "state.h"
+
+void msn_notification_end(void);
 void msn_notification_init(void);
 
-/**
- * Destroy the variables for notification server creation.
- */
-void msn_notification_end(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);
 
-MsnServConn *msn_notification_new(MsnSession *session);
-gboolean msn_notification_connect(MsnServConn *notification,
-							  const char *host, int port);
-void msn_notification_disconnect(MsnServConn *notification);
+void msn_got_login_params(MsnSession *session, const char *login_params);
 
 #endif /* _MSN_NOTIFICATION_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/object.c	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,310 @@
+/**
+ * @file object.c MSNObject API
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include "object.h"
+
+#define GET_STRING_TAG(field, id) \
+	if ((tag = strstr(str, id "=\"")) != NULL) \
+	{ \
+		tag += strlen(id "=\""); \
+		c = strchr(tag, '"'); \
+		obj->field = g_strndup(tag, c - tag); \
+	}
+
+#define GET_INT_TAG(field, id) \
+	if ((tag = strstr(str, id "=\"")) != NULL) \
+	{ \
+		char buf[16]; \
+		tag += strlen(id "=\""); \
+		c = strchr(tag, '"'); \
+		strncpy(buf, tag, c - tag); \
+		buf[c - tag] = '\0'; \
+		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);
+	g_return_val_if_fail(!strncmp(str, "<msnobj ", 8), 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");
+
+	return obj;
+}
+
+void
+msn_object_destroy(MsnObject *obj)
+{
+	g_return_if_fail(obj != NULL);
+
+	if (obj->creator != NULL)
+		g_free(obj->creator);
+
+	if (obj->location != NULL)
+		g_free(obj->location);
+
+	if (obj->friendly != NULL)
+		g_free(obj->friendly);
+
+	if (obj->sha1d != NULL)
+		g_free(obj->sha1d);
+
+	if (obj->sha1c != NULL)
+		g_free(obj->sha1c);
+	
+	if (obj->local)
+		local_objs = g_list_remove(local_objs, obj);
+
+	g_free(obj);
+}
+
+char *
+msn_object_to_string(const MsnObject *obj)
+{
+	char *str;
+
+	g_return_val_if_fail(obj != NULL, NULL);
+
+	str = g_strdup_printf("<msnobj Creator=\"%s\" Size=\"%d\" Type=\"%d\" "
+						  "Location=\"%s\" Friendly=\"%s\" SHA1D=\"%s\" "
+						  "SHA1C=\"%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),
+						  msn_object_get_sha1c(obj));
+
+	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;
+}
+
+MsnObject *
+msn_object_find_local(const char *sha1c)
+{
+	GList *l;
+	
+	g_return_val_if_fail(sha1c != NULL, NULL);
+
+	for (l = local_objs; l != NULL; l = l->next)
+	{
+		MsnObject *local_obj = l->data;
+
+		if (!strcmp(msn_object_get_sha1c(local_obj), sha1c))
+			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_real_location(MsnObject *obj, const char *real_location)
+{
+	g_return_if_fail(obj != NULL);
+	
+	/* obj->local = TRUE; */
+
+	if (obj->real_location != NULL)
+		g_free(obj->real_location);
+
+	obj->real_location =
+		(real_location == NULL ? NULL : g_strdup(real_location));
+}
+
+const char *
+msn_object_get_real_location(const MsnObject *obj)
+{
+	MsnObject *local_obj;
+	
+	g_return_val_if_fail(obj != NULL, NULL);
+
+	local_obj = msn_object_find_local(msn_object_get_sha1c(obj));
+
+	if (local_obj != NULL)
+		return local_obj->real_location;
+	
+	return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/object.h	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,202 @@
+/**
+ * @file object.h MSNObject API
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _MSN_OBJECT_H_
+#define _MSN_OBJECT_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;
+	char *real_location;
+	char *location;
+	char *friendly;
+	char *sha1d;
+	char *sha1c;
+
+} MsnObject;
+
+/**
+ * Creates a MsnObject structure.
+ *
+ * @return A new MsnObject structure.
+ */
+MsnObject *msn_object_new();
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+void msn_object_set_local(MsnObject *obj);
+const char *msn_object_get_real_location(const MsnObject *obj);
+void msn_object_set_real_location(MsnObject *obj,
+								  const char *real_location);
+
+#endif /* _MSN_OBJECT_H_ */
--- a/src/protocols/msn/servconn.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/servconn.c	Sun Jun 06 02:39:08 2004 +0000
@@ -34,7 +34,7 @@
 
 	const char *names[] = { "Notification", "Switchboard" };
 	const char *name;
-
+	
 	gc = gaim_account_get_connection(servconn->session->account);
 	name = names[servconn->type];
 
@@ -58,12 +58,17 @@
 	}
 
 	if (servconn->type != MSN_SERVER_SB)
+	{
 		gaim_connection_error(gc, tmp);
+	}
 	else
 	{
-		GaimAccount *account = gaim_connection_get_account(gc);
-		char *primary = g_strdup_printf(_("MSN error for account %s"),
-										gaim_account_get_username(account));
+		GaimAccount *account;
+		char *primary;
+
+		account = gaim_connection_get_account(gc);
+		primary = g_strdup_printf(_("MSN error for account %s"),
+								  gaim_account_get_username(account));
 
 		gaim_notify_error(gc, NULL, primary, tmp);
 
@@ -116,7 +121,6 @@
 	}
 
 	servconn->num = session->servconns_count++;
-	session->servconns = g_list_append(session->servconns, servconn);
 
 	return servconn;
 }
@@ -124,8 +128,6 @@
 void
 msn_servconn_destroy(MsnServConn *servconn)
 {
-	MsnSession *session;
-
 	g_return_if_fail(servconn != NULL);
 
 	if (servconn->processing)
@@ -134,15 +136,18 @@
 		return;
 	}
 
-	session = servconn->session;
-
-	session->servconns = g_list_remove(session->servconns, servconn);
-
 	if (servconn->connected)
 		msn_servconn_disconnect(servconn);
 
+	if (servconn->http_data != NULL)
+		g_free(servconn->http_data);
+
+#if 0
+	if (servconn->rx_buf != NULL)
+		g_free(servconn->rx_buf);
+#endif
+
 	msn_cmdproc_destroy(servconn->cmdproc);
-
 	g_free(servconn);
 }
 
@@ -163,6 +168,9 @@
 
 	if (session->http_method)
 	{
+		if (servconn->http_data->gateway_host != NULL)
+			g_free(servconn->http_data->gateway_host);
+			
 		servconn->http_data->gateway_host = g_strdup(host);
 	}
 
@@ -170,21 +178,21 @@
 						   servconn);
 
 	if (r == 0)
+	{
 		servconn->connected = TRUE;
-
-	return servconn->connected;
+		servconn->cmdproc->ready = TRUE;
+		return TRUE;
+	}
+	else
+		return FALSE;
 }
 
 void
 msn_servconn_disconnect(MsnServConn *servconn)
 {
-	MsnSession *session;
-
 	g_return_if_fail(servconn != NULL);
 	g_return_if_fail(servconn->connected);
 
-	session = servconn->session;
-
 	if (servconn->inpa > 0)
 	{
 		gaim_input_remove(servconn->inpa);
@@ -206,33 +214,27 @@
 
 		if (servconn->http_data->timer)
 			gaim_timeout_remove(servconn->http_data->timer);
-
-		g_free(servconn->http_data);
-		servconn->http_data = NULL;
 	}
 
 	servconn->rx_len = 0;
 	servconn->payload_len = 0;
 
+	servconn->connected = FALSE;
+	servconn->cmdproc->ready = FALSE;
+
 	if (servconn->disconnect_cb != NULL)
 		servconn->disconnect_cb(servconn);
-
-	servconn->connected = FALSE;
 }
 
 void
-msn_servconn_set_connect_cb(MsnServConn *servconn,
-							gboolean (*connect_cb)(MsnServConn *servconn))
+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
-													 *servconn))
+msn_servconn_set_disconnect_cb(MsnServConn *servconn, void (*disconnect_cb)(MsnServConn *))
 {
 	g_return_if_fail(servconn != NULL);
 
@@ -256,17 +258,30 @@
 
 	g_return_val_if_fail(servconn != NULL, 0);
 
-	if (servconn->session->http_method)
+	if (servconn->http_data == NULL)
+	{
+		switch (servconn->type)
+		{
+			case MSN_SERVER_NS:
+			case MSN_SERVER_SB:
+				ret = write(servconn->fd, buf, len);
+				break;
+			case MSN_SERVER_DC:
+				ret = write(servconn->fd, &buf, sizeof(len));
+				ret = write(servconn->fd, buf, len);
+				break;
+			default:
+				ret = write(servconn->fd, buf, len);
+				break;
+		}
+	}
+	else
 	{
 		ret = msn_http_servconn_write(servconn, buf, len,
 									  servconn->http_data->server_type);
 	}
-	else
-	{
-		ret = write(servconn->fd, buf, len);
-	}
 
-	if (ret < 0)
+	if (ret == -1)
 	{
 		servconn->cmdproc->error = MSN_ERROR_WRITE;
 		failed_io(servconn);
@@ -288,7 +303,6 @@
 	session = servconn->session;
 
 	len = read(servconn->fd, buf, sizeof(buf) - 1);
-	buf[len] = '\0';
 
 	if (len <= 0)
 	{
@@ -299,8 +313,10 @@
 		return;
 	}
 
-	servconn->rx_buf = g_realloc(servconn->rx_buf, len + servconn->rx_len);
-	memcpy(servconn->rx_buf + servconn->rx_len, buf, len);
+	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;
 
 	if (session->http_method)
@@ -387,13 +403,13 @@
 		{
 			end = strstr(cur, "\r\n");
 
-			if (!end)
+			if (end == NULL)
 				/* The command is still not complete. */
 				break;
 
 			*end = '\0';
-			cur_len = end - cur + 2;
 			end += 2;
+			cur_len = end - cur;
 		}
 
 		servconn->rx_len -= cur_len;
@@ -407,11 +423,11 @@
 		{
 			msn_cmdproc_process_cmd_text(servconn->cmdproc, cur);
 		}
-	} while (servconn->connected && servconn->rx_len);
+	} while (servconn->connected && servconn->rx_len > 0);
 
 	if (servconn->connected)
 	{
-		if (servconn->rx_len)
+		if (servconn->rx_len > 0)
 			servconn->rx_buf = g_memdup(cur, servconn->rx_len);
 		else
 			servconn->rx_buf = NULL;
@@ -424,3 +440,89 @@
 
 	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)
+	{
+		gaim_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)
+	{
+		gaim_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
--- a/src/protocols/msn/servconn.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/servconn.h	Sun Jun 06 02:39:08 2004 +0000
@@ -24,13 +24,16 @@
 
 typedef struct _MsnServConn MsnServConn;
 
+#include "session.h"
 #include "cmdproc.h"
+
 #include "proxy.h"
-
-#include "msg.h"
 #include "httpmethod.h"
 
-#include "session.h"
+/*
+#include "msg.h"
+#include "history.h"
+*/
 
 typedef enum
 {
@@ -62,29 +65,27 @@
 	char *rx_buf;
 	int rx_len;
 
-	int payload_len;
+	size_t payload_len;
 
-	gboolean (*connect_cb)(MsnServConn *servconn);
-	void (*disconnect_cb)(MsnServConn *servconn);
-
+	void (*connect_cb)(MsnServConn *);
+/*	void (*failed_io_cb)(MsnServConn *); */
+	void (*disconnect_cb)(MsnServConn *);
+	void (*data_free_cb)(void *data);
 	void *data;
 };
 
 MsnServConn *msn_servconn_new(MsnSession *session, MsnServConnType type);
-
 void msn_servconn_destroy(MsnServConn *servconn);
 
-gboolean msn_servconn_connect(MsnServConn *servconn, const char *host,
-							  int port);
+gboolean msn_servconn_connect(MsnServConn *servconn, const char *host, int port);
 void msn_servconn_disconnect(MsnServConn *servconn);
 
 void msn_servconn_set_connect_cb(MsnServConn *servconn,
-		gboolean (*connect_cb)(MsnServConn *servconn));
-
+								 void (*connect_cb)(MsnServConn *));
 void msn_servconn_set_disconnect_cb(MsnServConn *servconn,
-		void (*disconnect_cb)(MsnServConn *servconn));
-
-size_t msn_servconn_write(MsnServConn *servconn, const char *buf,
-						  size_t size);
+									void (*disconnect_cb)(MsnServConn *));
+void msn_servconn_set_failed_io_cb(MsnServConn *servconn,
+								 void (*failed_io_cb)(MsnServConn *));
+size_t msn_servconn_write(MsnServConn *servconn, const char *buf, size_t size);
 
 #endif /* _MSN_SERVCONN_H_ */
--- a/src/protocols/msn/session.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/session.c	Sun Jun 06 02:39:08 2004 +0000
@@ -23,8 +23,11 @@
 #include "session.h"
 #include "notification.h"
 
+#include "slplink.h"
+
 MsnSession *
-msn_session_new(GaimAccount *account, const char *host, int port)
+msn_session_new(GaimAccount *account, const char *host, int port,
+				gboolean http_method)
 {
 	MsnSession *session;
 
@@ -35,17 +38,12 @@
 	session->account       = account;
 	session->dispatch_host = g_strdup(host);
 	session->dispatch_port = port;
-
-	session->away_state = NULL;
-
-	session->users  = msn_users_new();
-	session->groups = msn_groups_new();
+	session->http_method   = http_method;
 
-#ifdef HAVE_SSL
+	session->notification = msn_notification_new(session);
+	session->userlist = msn_userlist_new(session);
+
 	session->protocol_ver = 9;
-#else
-	session->protocol_ver = 7;
-#endif
 
 	return session;
 }
@@ -61,26 +59,16 @@
 	if (session->dispatch_host != NULL)
 		g_free(session->dispatch_host);
 
+	if (session->notification != NULL)
+		msn_notification_destroy(session->notification);
+
 	while (session->switches != NULL)
 		msn_switchboard_destroy(session->switches->data);
 
-	while (session->lists.forward)
-	{
-		MsnUser *user = (MsnUser *)session->lists.forward->data;
-
-		msn_user_destroy(user);
-
-		session->lists.forward = g_slist_remove(session->lists.forward, user);
-	}
+	while (session->slplinks != NULL)
+		msn_slplink_destroy(session->slplinks->data);
 
-	if (session->lists.allow != NULL)
-		g_slist_free(session->lists.allow);
-
-	if (session->lists.block != NULL)
-		g_slist_free(session->lists.block);
-
-	msn_groups_destroy(session->groups);
-	msn_users_destroy(session->users);
+	msn_userlist_destroy(session->userlist);
 
 	if (session->passport_info.kv != NULL)
 		g_free(session->passport_info.kv);
@@ -94,8 +82,8 @@
 	if (session->passport_info.file != NULL)
 		g_free(session->passport_info.file);
 
-	if (session->away_state != NULL)
-		g_free(session->away_state);
+	if (session->sync != NULL)
+		msn_sync_destroy(session->sync);
 
 	if (session->nexus != NULL)
 		msn_nexus_destroy(session->nexus);
@@ -111,9 +99,7 @@
 
 	session->connected = TRUE;
 
-	session->notification_conn = msn_notification_new(session);
-
-	if (msn_notification_connect(session->notification_conn,
+	if (msn_notification_connect(session->notification,
 								 session->dispatch_host,
 								 session->dispatch_port))
 	{
@@ -130,99 +116,31 @@
 	g_return_if_fail(session->connected);
 
 	while (session->switches != NULL)
-	{
-		MsnSwitchBoard *board = (MsnSwitchBoard *)session->switches->data;
-
-		msn_switchboard_destroy(board);
-	}
-
-	if (session->notification_conn != NULL)
-	{
-		msn_servconn_destroy(session->notification_conn);
-		session->notification_conn = NULL;
-	}
-}
+		msn_switchboard_destroy(session->switches->data);
 
-MsnSwitchBoard *
-msn_session_open_switchboard(MsnSession *session)
-{
-	MsnSwitchBoard *swboard;
-	MsnCmdProc *cmdproc;
-
-	g_return_val_if_fail(session != NULL, NULL);
-
-	cmdproc = session->notification_conn->cmdproc;
-
-	msn_cmdproc_send(cmdproc, "XFR", "%s", "SB");
-
-	if (cmdproc->error)
-		return NULL;
-
-	swboard = msn_switchboard_new(session);
-
-	return swboard;
+	if (session->notification != NULL)
+		msn_notification_disconnect(session->notification);
 }
 
-gboolean
-msn_session_change_status(MsnSession *session, const char *state)
-{
-	MsnCmdProc *cmdproc;
-	MsnUser *user;
-	MsnObject *msnobj;
-
-	g_return_val_if_fail(session != NULL, FALSE);
-	g_return_val_if_fail(state   != NULL, FALSE);
-
-	user = session->user;
-	msnobj = msn_user_get_object(user);
-
-	if (state != session->away_state)
-	{
-		if (session->away_state != NULL)
-			g_free(session->away_state);
-
-		session->away_state = g_strdup(state);
-	}
-
-	cmdproc = session->notification_conn->cmdproc;
-
-	if (msnobj == NULL)
-	{
-		msn_cmdproc_send(cmdproc, "CHG", "%s %d", state, MSN_CLIENT_ID);
-	}
-	else
-	{
-		char *msnobj_str = msn_object_to_string(msnobj);
-
-		msn_cmdproc_send(cmdproc, "CHG", "%s %d %s", state, MSN_CLIENT_ID,
-						 gaim_url_encode(msnobj_str));
-
-		g_free(msnobj_str);
-	}
-
-	return TRUE;
-}
+/* TODO: This must go away when conversation is redesigned */
 
 MsnSwitchBoard *
-msn_session_find_switch_with_passport(const MsnSession *session,
-									  const char *passport)
+msn_session_find_swboard(MsnSession *session, const char *username)
 {
 	GList *l;
-	MsnSwitchBoard *swboard;
 
 	g_return_val_if_fail(session  != NULL, NULL);
-	g_return_val_if_fail(passport != NULL, NULL);
+	g_return_val_if_fail(username != NULL, NULL);
 
 	for (l = session->switches; l != NULL; l = l->next)
 	{
-		swboard = (MsnSwitchBoard *)l->data;
+		MsnSwitchBoard *swboard;
+
+		swboard = l->data;
 
-		if (!swboard->hidden && !swboard->chat_id &&
-			!g_ascii_strcasecmp(passport,
-								msn_user_get_passport(swboard->user)))
-		{
-			return swboard;
-		}
+		if (swboard->im_user != NULL)
+			if (!strcmp(username, swboard->im_user))
+				return swboard;
 	}
 
 	return NULL;
@@ -232,14 +150,15 @@
 msn_session_find_switch_with_id(const MsnSession *session, int chat_id)
 {
 	GList *l;
-	MsnSwitchBoard *swboard;
 
 	g_return_val_if_fail(session != NULL, NULL);
-	g_return_val_if_fail(chat_id > 0, NULL);
+	g_return_val_if_fail(chat_id >= 0,    NULL);
 
 	for (l = session->switches; l != NULL; l = l->next)
 	{
-		swboard = (MsnSwitchBoard *)l->data;
+		MsnSwitchBoard *swboard;
+
+		swboard = l->data;
 
 		if (swboard->chat_id == chat_id)
 			return swboard;
@@ -249,20 +168,19 @@
 }
 
 MsnSwitchBoard *
-msn_session_find_unused_switch(const MsnSession *session)
+msn_session_get_swboard(MsnSession *session, const char *username)
 {
-	GList *l;
 	MsnSwitchBoard *swboard;
 
-	g_return_val_if_fail(session != NULL, NULL);
+	swboard = msn_session_find_swboard(session, username);
 
-	for (l = session->switches; l != NULL; l = l->next)
+	if (swboard == NULL)
 	{
-		swboard = (MsnSwitchBoard *)l->data;
-
-		if (!swboard->in_use)
-			return swboard;
+		swboard = msn_switchboard_new(session);
+		msn_switchboard_request(swboard);
+		msn_switchboard_request_add_user(swboard, username);
+		swboard->im_user = g_strdup(username);
 	}
 
-	return NULL;
+	return swboard;
 }
--- a/src/protocols/msn/session.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/session.h	Sun Jun 06 02:39:08 2004 +0000
@@ -24,19 +24,24 @@
 
 typedef struct _MsnSession MsnSession;
 
-#include "group.h"
-#include "nexus.h"
-#include "servconn.h"
 #include "sslconn.h"
+
+#include "notification.h"
 #include "switchboard.h"
 #include "user.h"
+#include "group.h"
 
+#include "cmdproc.h"
+#include "nexus.h"
+
+#include "userlist.h"
+#include "sync.h"
 
 struct _MsnSession
 {
 	GaimAccount *account;
 	MsnUser *user;
-	char *away_state;
+	int state;
 
 	guint protocol_ver;
 
@@ -45,28 +50,19 @@
 
 	gboolean connected;
 
-	MsnServConn *notification_conn;
-
+	MsnNotification *notification;
 	MsnNexus *nexus;
 
 	gboolean http_method;
 	gint http_poll_timer;
 
-	MsnUsers *users;
-	MsnGroups *groups;
+	MsnUserList *userlist;
 
 	int servconns_count;
-	GList *servconns;
 	GList *switches;
+	GList *directconns;
 
-	struct
-	{
-		GSList *forward;
-		GSList *reverse;
-		GSList *allow;
-		GSList *block;
-
-	} lists;
+	int conv_seq;
 
 	struct
 	{
@@ -81,40 +77,27 @@
 	} passport_info;
 
 	/* You have no idea how much I hate all that is below. */
+	/* shx: What? ;) */
+
 	GaimPlugin *prpl;
 
-	/* For MSNP8 and MSNP9. */
-	int num_users;
-	int total_users;
-	int num_groups;
-	int total_groups;
-	MsnUser *last_user_added;
+	MsnSync *sync;
 
-	/* For MSNP7 and lower. */
-	gboolean syncing_lists;
-	gboolean lists_synced;
-
-	/* For moving buddies from one group to another. Ugh. */
-	gboolean moving_buddy;
-	char *dest_group_name;
-	MsnUser *moving_user;
-	MsnGroup *old_group;
-
-	/* The last chat ID. */
-	int last_chat_id;
+	GList *slplinks;
 };
 
 /**
  * Creates an MSN session.
  *
  * @param account The account.
- * @param server  The dispatch server host.
+ * @param host    The dispatch server host.
  * @param port    The dispatch server port.
  *
  * @return The new MSN session.
  */
 MsnSession *msn_session_new(GaimAccount *account,
-							const char *host, int port);
+							const char *host, int port,
+							gboolean http_method);
 
 /**
  * Destroys an MSN session.
@@ -140,34 +123,6 @@
 void msn_session_disconnect(MsnSession *session);
 
 /**
- * Opens a new switchboard connection.
- *
- * @param session The MSN session.
- *
- * @return The new switchboard connection.
- */
-MsnSwitchBoard *msn_session_open_switchboard(MsnSession *session);
-
-/**
- * Changes the status of the user.
- *
- * @param session The MSN session.
- * @param state   The new state.
- */
-gboolean msn_session_change_status(MsnSession *session, const char *state);
-
-/**
- * Finds a switch with the given passport.
- *
- * @param session  The MSN session.
- * @param passport The passport to search for.
- *
- * @return The switchboard, if found.
- */
-MsnSwitchBoard *msn_session_find_switch_with_passport(
-		const MsnSession *session, const char *passport);
-
-/**
  * Finds a switchboard with the given chat ID.
  *
  * @param session The MSN session.
@@ -178,13 +133,9 @@
 MsnSwitchBoard *msn_session_find_switch_with_id(const MsnSession *session,
 												int chat_id);
 
-/**
- * Finds the first unused switchboard.
- *
- * @param session  The MSN session.
- *
- * @return The first unused, writable switchboard, if found.
- */
-MsnSwitchBoard *msn_session_find_unused_switch(const MsnSession *session);
+MsnSwitchBoard *msn_session_find_swboard(MsnSession *session,
+										 const char *username);
+MsnSwitchBoard *msn_session_get_swboard(MsnSession *session,
+										const char *username);
 
 #endif /* _MSN_SESSION_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/slp.c	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,822 @@
+/**
+ * @file msnslp.c MSNSLP support
+ *
+ * gaim
+ *
+ * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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"
+
+/* #include "slplink.h" */
+/* #include "directconn.h" */
+
+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);
+
+/**************************************************************************
+ * Util
+ **************************************************************************/
+
+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(GaimXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+	/* MsnSlpLink *slplink; */
+	char *content;
+
+	gaim_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);
+	gaim_xfer_add(xfer);
+	msn_slplink_unleash(slpcall->slplink);
+}
+
+void
+msn_xfer_cancel(GaimXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+	char *content;
+
+	slpcall = xfer->data;
+
+	if (gaim_xfer_get_status(xfer) == GAIM_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);
+		}
+	}
+}
+
+void
+msn_xfer_progress_cb(MsnSlpCall *slpcall, gsize total_length, gsize len, gsize offset)
+{
+	GaimXfer *xfer;
+
+	xfer = slpcall->xfer;	
+
+	xfer->bytes_sent = offset;
+	xfer->bytes_remaining = total_length - offset;
+
+	gaim_xfer_update_progress(xfer);
+}
+
+void
+msn_xfer_finish_cb(MsnSlpCall *slpcall,
+				   const char *body, long long size)
+{
+	if (size < 0)
+		gaim_xfer_cancel_remote(slpcall->xfer);
+	else
+		gaim_xfer_set_completed(slpcall->xfer, TRUE);
+}
+
+/**************************************************************************
+ * 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++)
+	{
+		gaim_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 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 DEBUG_SLP
+	slpmsg->info = "SLP 603 Decline";
+	slpmsg->text_body = TRUE;
+#endif
+
+	msn_slplink_queue_slpmsg(slplink, slpmsg);
+}
+
+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 */
+		MsnSlpSession *slpsession;
+		MsnSlpLink *slplink;
+		MsnSlpMessage *slpmsg;
+		MsnObject *obj;
+		char *msnobj_data;
+		const char *sha1c;
+		const char *file_name;
+		char *content;
+		gsize len;
+		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;
+
+		gaim_base64_decode(context, &msnobj_data, &len);
+		obj = msn_object_new_from_string(msnobj_data);
+		type = msn_object_get_type(obj);
+		sha1c = msn_object_get_sha1c(obj);
+		g_free(msnobj_data);
+		
+		if (!(type == MSN_OBJECT_USERTILE))
+		{
+			gaim_debug_error("msn", "Wrong object?\n");
+			msn_object_destroy(obj);
+			g_return_if_reached();
+		}
+		
+		file_name = msn_object_get_real_location(obj);
+
+		slpsession = msn_slplink_find_slp_session(slplink,
+												   slpcall->session_id);
+		
+		/* DATA PREP */
+		slpmsg = msn_slpmsg_new(slplink);
+		slpmsg->slpsession = slpsession;
+		slpmsg->session_id = slpsession->id;
+		msn_slpmsg_set_body(slpmsg, NULL, 4);
+#ifdef DEBUG_SLP
+		slpmsg->info = "SLP DATA PREP";
+#endif
+		msn_slplink_queue_slpmsg(slplink, slpmsg);
+
+		/* DATA */
+		slpmsg = msn_slpmsg_new(slplink);
+		slpmsg->slpsession = slpsession;		
+		slpmsg->flags = 0x20;
+#ifdef DEBUG_SLP
+		slpmsg->info = "SLP DATA";
+#endif
+		msn_slpmsg_open_file(slpmsg, file_name);
+		msn_slplink_queue_slpmsg(slplink, slpmsg);
+	}
+	else if (!strcmp(euf_guid, "5D3E02AB-6190-11D3-BBBB-00C04F795683"))
+	{
+		/* File Transfer */
+		GaimAccount *account;
+		GaimXfer *xfer;
+		char *bin;
+		gsize bin_len;
+		guint32 file_size;
+		char *file_name;
+
+		account = slpcall->slplink->session->account;
+
+		slpcall->cb = msn_xfer_finish_cb;
+		slpcall->progress_cb = msn_xfer_progress_cb;
+		slpcall->branch = g_strdup(branch);
+
+		xfer = gaim_xfer_new(account, GAIM_XFER_RECEIVE,
+							 slpcall->slplink->remote_user);
+
+		gaim_base64_decode(context, &bin, &bin_len);
+		file_size = *((gsize *)bin + 2);
+		file_name = g_utf16_to_utf8((const gunichar2 *)(bin + 20), -1,
+									NULL, NULL, NULL);
+
+		g_free(bin);
+
+		gaim_xfer_set_filename(xfer, file_name);
+		gaim_xfer_set_size(xfer, file_size);
+		gaim_xfer_set_init_fnc(xfer, msn_xfer_init);
+		gaim_xfer_set_request_denied_fnc(xfer, msn_xfer_cancel);
+		gaim_xfer_set_cancel_recv_fnc(xfer, msn_xfer_cancel);
+
+		slpcall->xfer = xfer;
+		xfer->data = slpcall;
+
+		gaim_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 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");
+
+		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 = gaim_prefs_get_string("/core/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 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? */
+		gaim_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, gsize len)
+{
+	MsnSlpCall *slpcall;
+
+	if (!strncmp(body, "INVITE", strlen("INVITE")))
+	{
+		char *branch;
+		char *content;
+		char *content_type;
+
+		slpcall = msn_slp_call_new(slplink);
+
+		/* From: <msnmsgr:buddy@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(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);
+
+		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')))
+			{
+				strncpy(temp, status, c - status);
+				temp[c - status] = '\0';
+			}
+			
+			gaim_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);
+
+	msn_slplink_process_msg(slplink, msg);
+}
+
+void
+got_emoticon(MsnSlpCall *slpcall,
+			 const char *data, long long size)
+{
+	gaim_debug_info("msn", "Got smiley: %s\n", slpcall->data_info);
+
+#if 0
+	GaimConversation *conv;
+	GaimConnection *gc = slpsession->swboard->servconn->session->account->gc;
+	serv_got_smiley(gc, info, data, size);
+#endif
+}
+
+void
+msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	MsnSession *session;
+	MsnSlpLink *slplink;
+	MsnObject *obj;
+	char **tokens;
+	char *smile;
+	const char *who;
+
+	session = cmdproc->servconn->session;
+
+	tokens = g_strsplit(msg->body, "\t", 2);
+
+	smile = tokens[0];
+	obj = msn_object_new_from_string(gaim_url_decode(tokens[1]));
+
+	who = msn_object_get_creator(obj);
+
+	slplink = msn_session_get_slplink(session, who);
+
+	msn_slplink_request_object(slplink, smile, got_emoticon, obj);
+
+	g_strfreev(tokens);
+}
+
+void
+got_user_display(MsnSlpCall *slpcall,
+				 const char *data, long long size)
+{
+	const char *info;
+	GaimAccount *account;
+	GSList *sl;
+
+	info = slpcall->data_info;
+	gaim_debug_info("msn", "Got User Display: %s\n", info);
+
+	account = slpcall->slplink->session->account;
+	
+	/* TODO: I think we need better buddy icon core functions. */
+	gaim_buddy_icons_set_for_user(account, slpcall->slplink->remote_user,
+								  (void *)data, size);
+
+	sl = gaim_find_buddies(account, slpcall->slplink->remote_user);
+
+	for (; sl != NULL; sl = sl->next)
+	{
+		GaimBuddy *buddy = (GaimBuddy *)sl->data;
+		gaim_blist_node_set_string((GaimBlistNode*)buddy, "icon_checksum", info);
+		gaim_blist_save();
+	}
+}
+
+static gboolean
+buddy_icon_cached(GaimConnection *gc, MsnObject *obj)
+{
+	GaimAccount *account;
+	GaimBuddy *buddy;
+	GSList *sl;
+	const char *old;
+	const char *new;
+
+	g_return_val_if_fail(obj != NULL, FALSE);
+
+	account = gaim_connection_get_account(gc);
+
+	sl = gaim_find_buddies(account, msn_object_get_creator(obj));
+
+	if (sl == NULL)
+	{
+		return FALSE;
+	}
+
+	buddy = (GaimBuddy *)sl->data;
+	
+	old = gaim_blist_node_get_string((GaimBlistNode *)buddy, "icon_checksum");
+	new = msn_object_get_sha1c(obj);
+
+	if (new == NULL)
+	{
+		return FALSE;
+	}
+
+	if (old != NULL && !strcmp(old, new))
+		return TRUE;
+
+	return FALSE;
+}
+
+void
+msn_request_buddy_icon(GaimConnection *gc, const char *passport)
+{
+	MsnSession *session;
+	MsnSlpLink *slplink;
+	MsnUser *user;
+	MsnObject *obj;
+	const char *info;
+
+	session = gc->proto_data;
+
+	g_return_if_fail(session->protocol_ver == 9);
+
+	slplink = msn_session_get_slplink(session, passport);
+
+	user = msn_userlist_find_user(session->userlist, passport);
+
+	obj = msn_user_get_object(user);
+
+	if (obj == NULL)
+		/* It seems the user has not set a msnobject */
+		return;
+
+	info = msn_object_get_sha1c(obj);
+
+	if (!buddy_icon_cached(gc, obj))
+		msn_slplink_request_object(slplink, info, got_user_display, obj);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/slp.h	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,43 @@
+/**
+ * @file slp.h MSNSLP support
+ *
+ * gaim
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _MSN_SLP_H_
+#define _MSN_SLP_H_
+
+#define DEBUG_SLP 1
+/* #define DEBUG_SLP_FILES 1 */
+
+#include "slpcall.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, gsize len);
+
+void send_bye(MsnSlpCall *slpcall, const char *type);
+
+void msn_xfer_finish_cb(MsnSlpCall *slpcall,
+						const char *body, long long size);
+
+void msn_xfer_cancel(GaimXfer *xfer);
+
+#endif /* _MSN_SLP_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/slpcall.c	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,216 @@
+/**
+ * @file slpcall.c SLP Call Functions
+ *
+ * gaim
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include "msn.h"
+#include "slpcall.h"
+#include "slpsession.h"
+
+#include "slp.h"
+
+/**************************************************************************
+ * Util
+ **************************************************************************/
+
+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);
+}
+
+/**************************************************************************
+ * SLP Call
+ **************************************************************************/
+
+MsnSlpCall *
+msn_slp_call_new(MsnSlpLink *slplink)
+{
+	MsnSlpCall *slpcall;
+
+	g_return_val_if_fail(slplink != NULL, NULL);
+
+	slpcall = g_new0(MsnSlpCall, 1);
+
+	slpcall->slplink = slplink;
+	slplink->slp_calls =
+		g_list_append(slplink->slp_calls, slpcall);
+
+	return 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_destroy(MsnSlpCall *slpcall)
+{
+	GList *e;
+
+	g_return_if_fail(slpcall != NULL);
+
+	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);
+
+	slpcall->slplink->slp_calls =
+		g_list_remove(slpcall->slplink->slp_calls, slpcall);
+
+	for (e = slpcall->slplink->slp_msgs; e != NULL; )
+	{
+		MsnSlpMessage *slpmsg = e->data;
+		e = e->next;
+
+		g_return_if_fail(slpmsg != NULL);
+
+		if (slpmsg->slpcall == slpcall)
+		{
+#if 1
+			msn_slpmsg_destroy(slpmsg);
+#else
+			slpmsg->wasted = TRUE;
+#endif
+		}
+	}
+
+	if (slpcall->cb != NULL)
+		slpcall->cb(slpcall, NULL, -1);
+
+	g_free(slpcall);
+}
+
+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;
+	char *branch;
+
+	g_return_if_fail(slpcall != NULL);
+	g_return_if_fail(context != NULL);
+
+	slplink = slpcall->slplink;
+
+	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, branch,
+								"application/x-msnmsgr-sessionreqbody", content);
+#ifdef 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);
+}
+
+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);
+}
+
+MsnSlpCall *
+msn_slp_process_msg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg)
+{
+	MsnSlpCall *slpcall;
+	const char *body;
+	gsize body_len;
+
+	slpcall = NULL;
+	body = slpmsg->buffer;
+	body_len = slpmsg->size;
+	
+	if (slpmsg->flags == 0x0)
+	{
+		slpcall = msn_slp_sip_recv(slplink, body, body_len);
+	}
+	else if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030)
+	{
+		slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->session_id);
+
+		if (slpcall != NULL)
+			slpcall->cb(slpcall, body, body_len);
+	}
+#if 0
+	else if (slpmsg->flags == 0x100)
+	{
+		slpcall = slplink->directconn->initial_call;
+
+		if (slpcall != NULL)
+			msn_slp_call_session_init(slpcall);
+	}
+#endif
+
+	return slpcall;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/slpcall.h	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,76 @@
+/**
+ * @file slpcall.h SLP Call functions
+ *
+ * gaim
+ *
+ * Copyright (C) 2003-2004 Christian Hammond <chipx86@gnupdate.org>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _MSN_SLPCALL_H_
+#define _MSN_SLPCALL_H_
+
+typedef struct _MsnSlpCall MsnSlpCall;
+
+typedef void (*MsnSlpCb)(MsnSlpCall *slpcall,
+						 const char *data, long long size);
+
+#include "slplink.h"
+#include "slpsession.h"
+
+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;
+
+	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;
+	gboolean wasted;
+	gboolean started;
+};
+
+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);
+
+#endif /* _MSN_SLPCALL_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/slplink.c	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,656 @@
+#include "msn.h"
+#include "slplink.h"
+
+#include "switchboard.h"
+#include "slp.h"
+
+void msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg);
+
+#ifdef 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 = fopen(tmp, "w");
+	pload = msn_message_gen_payload(msg, &pload_size);
+	fwrite(pload, 1, pload_size, tf);
+	fclose(tf);
+	g_free(tmp);
+}
+#endif
+
+MsnSlpLink *
+msn_slplink_new(MsnSession *session, const char *username)
+{
+	MsnSlpLink *slplink;
+
+	slplink = g_new0(MsnSlpLink, 1);
+
+	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;
+
+	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);
+
+	session->slplinks =
+		g_list_remove(session->slplinks, slplink);
+
+	g_free(slplink);
+}
+
+MsnSlpLink *
+msn_session_find_slplink(MsnSession *session, const char *who)
+{
+	MsnSlpLink *slplink;
+	GList *l;
+
+	for (l = session->slplinks; l != NULL; l = l->next)
+	{
+		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;
+
+	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;
+}
+
+MsnSlpCall *
+msn_slplink_find_slp_call(MsnSlpLink *slplink, const char *id)
+{
+	GList *l;
+	MsnSlpCall *slpcall;
+
+	for (l = slplink->slp_calls; l != NULL; l = l->next)
+	{
+		slpcall = l->data;
+
+		if (!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
+	{
+		MsnSwitchBoard *swboard;
+
+		swboard = msn_session_get_swboard(slplink->session, slplink->remote_user);
+
+		if (swboard == NULL)
+			return;
+
+		if (!g_queue_is_empty(swboard->im_queue) ||
+			!swboard->user_joined)
+		{
+			msn_switchboard_queue_msg(swboard, msg);
+		}
+		else
+		{
+			msn_switchboard_send_msg(swboard, msg);
+		}
+	}
+}
+
+void t_ack(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSlpMessage *slpmsg;
+	long long real_size;
+
+	slpmsg = cmd->trans->data;
+
+#if 0
+	if (slpmsg->wasted)
+	{
+		gaim_debug_info("msn", "slpmsg cancelled %p\n", slpmsg);
+
+		if (slpmsg->slpcall != NULL)
+		{
+			if (slpmsg->slpcall->cb != NULL)
+				slpmsg->slpcall->cb(slpmsg->slpcall, NULL, -1);
+
+			msn_slpcall_destroy(slpmsg->slpcall);
+		}
+
+		msn_slpmsg_destroy(slpmsg);
+	}
+#endif
+
+	real_size = (slpmsg->flags == 0x2) ? 0 : slpmsg->size;
+
+	if (slpmsg->offset < real_size)
+	{
+		msn_slplink_send_msgpart(slpmsg->slplink, slpmsg);
+	}
+	else
+	{
+		/* The whole message has been sent */
+
+		if ((slpmsg->slpcall != NULL) &&
+			(slpmsg->slpcall->cb != NULL))
+		{
+			slpmsg->slpcall->cb(slpmsg->slpcall, NULL, 0);
+		}
+
+		msn_slpmsg_destroy(slpmsg);
+	}
+}
+
+void
+msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg)
+{
+	MsnMessage *msg;
+	long long real_size;
+	size_t len = 0;
+
+	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 DEBUG_SLP
+	msn_message_show_readable(msg, slpmsg->info, slpmsg->text_body);
+#endif
+
+#ifdef DEBUG_SLP_FILES
+	debug_msg_to_file(msg, TRUE);
+#endif
+
+	msn_slplink_send_msg(slplink, msg);
+
+	if ((slpmsg->slpcall != NULL) &&
+		(slpmsg->slpcall->progress_cb != NULL))
+	{
+		slpmsg->slpcall->progress_cb(slpmsg->slpcall, slpmsg->size, len,
+									 slpmsg->offset);
+	}
+
+	slpmsg->offset += len;
+}
+
+void
+msn_slplink_release_msg(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;
+	}
+	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 = t_ack;
+	msg->ack_data = slpmsg;
+
+	msn_slplink_send_msgpart(slplink, slpmsg);
+}
+
+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_msg(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_msg(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 DEBUG_SLP
+	slpmsg->info = "SLP ACK";
+#endif
+
+	msn_slplink_send_slpmsg(slplink, slpmsg);
+}
+
+void
+send_file(MsnSlpSession *slpsession)
+{
+	MsnSlpCall *slpcall;
+	MsnSlpMessage *slpmsg;
+
+	slpcall = slpsession->slpcall;
+	slpmsg = msn_slpmsg_new(slpcall->slplink);
+	slpmsg->flags = 0x1000030;
+	slpmsg->slpsession = slpsession;
+#ifdef DEBUG_SLP
+	slpmsg->info = "SLP FILE";
+#endif
+	slpmsg->slpcall = slpcall;
+	msn_slpmsg_open_file(slpmsg, gaim_xfer_get_local_filename(slpcall->xfer));
+
+	gaim_xfer_add(slpcall->xfer);
+	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 DEBUG_SLP
+	msn_slpmsg_show(msg);
+#endif
+
+#ifdef DEBUG_SLP_FILES
+	debug_msg_to_file(msg, FALSE);
+#endif
+
+	if (msg->msnslp_header.total_size < msg->msnslp_header.length)
+	{
+		gaim_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;
+		slpmsg->buffer = g_malloc(slpmsg->size);
+
+		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)
+			{
+				GaimXfer *xfer;
+
+				xfer = slpmsg->slpcall->xfer;
+
+				if (xfer != NULL)
+				{
+					slpmsg->fp =
+						fopen(gaim_xfer_get_local_filename(slpmsg->slpcall->xfer), "w");
+				}
+			}
+		}
+	}
+	else
+	{
+		slpmsg = msn_slplink_message_find(slplink, msg->msnslp_header.id);
+	}
+
+	if (slpmsg != NULL)
+	{
+		if (slpmsg->fp)
+		{
+			/* fseek(slpmsg->fp, offset, SEEK_SET); */
+			len = fwrite(data, 1, len, slpmsg->fp);
+		}
+		else
+		{
+			memcpy(slpmsg->buffer + offset, data, len);
+		}
+	}
+	else
+	{
+		gaim_debug_error("msn", "Couldn't find slpmsg\n");
+		g_return_if_reached();
+	}
+
+	if ((slpmsg->slpcall != NULL) &&
+		(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 id)
+{
+	GList *e;
+
+	for (e = slplink->slp_msgs; e != NULL; e = e->next)
+	{
+		MsnSlpMessage *slpmsg = e->data;
+
+		if (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
+
+char *
+gen_context(const char *file_name)
+{
+	struct stat st;
+	gsize size = 0;
+	MsnContextHeader header;
+	gchar *u8;
+	gchar *base, *n;
+	gunichar2 *uni;
+	glong uni_len;
+	gsize len;
+
+	if (stat(file_name, &st) == 0)
+		size = st.st_size;
+
+	u8 = g_locale_to_utf8(g_basename(file_name), -1, NULL, NULL, NULL);
+	uni = g_utf8_to_utf16(u8, -1, NULL, &uni_len, NULL);
+	g_free(u8);
+
+	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 = n = g_malloc(len + 1);
+
+	memcpy(n, &header, sizeof(MsnContextHeader));
+	n += sizeof(MsnContextHeader);
+
+	memset(n, 0x00, MAX_FILE_NAME_LEN);
+	memcpy(n, uni, uni_len * 2);
+	n += MAX_FILE_NAME_LEN;
+
+	memset(n, 0xFF, 4);
+	n += 4;
+
+	g_free(uni);
+
+	return gaim_base64_encode(base, len);
+}
+
+void
+msn_slplink_request_ft(MsnSlpLink *slplink, GaimXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+	char *context;
+	const char *fn;
+
+	fn = gaim_xfer_get_local_filename(xfer);
+
+	g_return_if_fail(slplink != NULL);
+	g_return_if_fail(fn != NULL);
+
+	slpcall = msn_slp_call_new(slplink);
+	msn_slp_call_init(slpcall, MSN_SLPCALL_DC);
+
+	slpcall->session_init_cb = send_file;
+	slpcall->progress_cb = msn_xfer_progress_cb;
+	slpcall->cb = msn_xfer_finish_cb;
+	slpcall->xfer = xfer;
+
+	gaim_xfer_set_cancel_send_fnc(xfer, msn_xfer_cancel);
+
+	xfer->data = slpcall;
+
+	context = gen_context(fn);
+
+	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,
+						   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 = gaim_base64_encode(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;
+
+	msn_slp_call_invite(slpcall, "A4268EEC-FEC5-49E5-95C3-F126696BDBF6", 1,
+						msnobj_base64);
+
+	g_free(msnobj_base64);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/slplink.h	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,63 @@
+#ifndef _MSN_SLPLINK_H_
+#define _MSN_SLPLINK_H_
+
+typedef struct _MsnSlpLink MsnSlpLink;
+
+#include "session.h"
+#include "directconn.h"
+#include "slpcall.h"
+#include "slpmsg.h"
+
+#include "ft.h"
+
+struct _MsnSlpLink
+{
+	MsnSession *session;
+
+	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);
+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_msg(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 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, GaimXfer *xfer);
+
+void msn_slplink_request_object(MsnSlpLink *slplink,
+								 const char *info,
+								 MsnSlpCb cb,
+								 const MsnObject *obj);
+
+MsnSlpCall *msn_slp_process_msg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg);
+
+#endif /* _MSN_SLPLINK_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/slpmsg.c	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,187 @@
+/**
+ * @file slpmsg.h SLP Message functions
+ *
+ * gaim
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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);
+
+	slpmsg->slplink = slplink;
+
+	slplink->slp_msgs =
+		g_list_append(slplink->slp_msgs, slpmsg);
+
+	return slpmsg;
+}
+
+void
+msn_slpmsg_destroy(MsnSlpMessage *slpmsg)
+{
+	MsnSlpLink *slplink;
+
+	slplink = slpmsg->slplink;
+
+	if (slpmsg->fp != NULL)
+		fclose(slpmsg->fp);
+	
+	if (slpmsg->buffer != NULL)
+		g_free(slpmsg->buffer);
+
+#ifdef DEBUG_SLP
+	/*
+	if (slpmsg->info != NULL)
+		g_free(slpmsg->info);
+	*/
+#endif
+
+	if (slpmsg->msg != NULL)
+	{
+		if (slpmsg->msg->trans != NULL)
+		{
+			slpmsg->msg->trans->callbacks = NULL;
+			slpmsg->msg->trans->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)
+{
+	if (body != NULL)
+		slpmsg->buffer = g_memdup(body, size);
+	else
+		slpmsg->buffer = g_new0(char, size);
+	
+	slpmsg->size = size;
+}
+
+void
+msn_slpmsg_open_file(MsnSlpMessage *slpmsg, const char *file_name)
+{
+	struct stat st;
+
+	slpmsg->fp = fopen(file_name, "r");
+
+	if (stat(file_name, &st) == 0)
+		slpmsg->size = st.st_size;
+}
+
+#ifdef DEBUG_SLP
+const 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:
+			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: %d\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;
+
+	g_free(body);
+
+	return slpmsg;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/slpmsg.h	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,82 @@
+/**
+ * @file slpmsg.h SLP Message functions
+ *
+ * gaim
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _MSN_SLPMSG_H_
+#define _MSN_SLPMSG_H_
+
+typedef struct _MsnSlpMessage MsnSlpMessage;
+
+#include "slpsession.h"
+#include "slpcall.h"
+#include "slplink.h"
+#include "session.h"
+#include "msg.h"
+
+#include "slp.h"
+
+struct _MsnSlpMessage
+{
+	MsnSlpSession *slpsession;
+	MsnSlpCall *slpcall;
+	MsnSlpLink *slplink;
+	MsnSession *session;
+
+	long session_id;
+	long id;
+	long ack_id;
+	long ack_sub_id;
+	long long ack_size;
+	long app_id;
+
+	gboolean sip;
+#if 0
+	gboolean wasted;
+#endif
+	long flags;
+
+	FILE *fp;
+	char *buffer;
+	long long offset;
+	long long size;
+
+	MsnMessage *msg; /* The temporary real message that will be sent */
+
+#ifdef DEBUG_SLP
+	char *info;
+	gboolean text_body;
+#endif
+};
+
+MsnSlpMessage *msn_slpmsg_new(MsnSlpLink *slplink);
+void msn_slpmsg_destroy(MsnSlpMessage *slpmsg);
+void msn_slpmsg_set_body(MsnSlpMessage *slpmsg, const char *body,
+						 long long size);
+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 DEBUG_SLP
+const void msn_slpmsg_show(MsnMessage *msg);
+#endif
+
+#endif /* _MSN_SLPMSG_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/slpsession.c	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,71 @@
+/**
+ * @file slpsession.h SLP Session functions
+ *
+ * gaim
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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);
+}
+
+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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/slpsession.h	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,44 @@
+/**
+ * @file slpsession.h SLP Session functions
+ *
+ * gaim
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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_ */
--- a/src/protocols/msn/state.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/state.c	Sun Jun 06 02:39:08 2004 +0000
@@ -36,6 +36,40 @@
 	N_("Available")
 };
 
+void
+msn_change_status(MsnSession *session, MsnAwayType state)
+{
+	MsnCmdProc *cmdproc;
+	MsnUser *user;
+	MsnObject *msnobj;
+	const char *state_text;
+
+	cmdproc = session->notification->cmdproc;
+	user = session->user;
+	state_text = msn_state_get_text(state);
+
+	g_return_if_fail(session != NULL);
+
+	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, gaim_url_encode(msnobj_str));
+
+		g_free(msnobj_str);
+	}
+}
+
 const char *
 msn_away_get_text(MsnAwayType type)
 {
@@ -43,3 +77,12 @@
 
 	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];
+}
--- a/src/protocols/msn/state.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/state.h	Sun Jun 06 02:39:08 2004 +0000
@@ -42,6 +42,14 @@
 #define MSN_AWAY_TYPE(x)   (((x) >> 1) & 0x0F)
 
 /**
+ * Changes the status of the user.
+ *
+ * @param session The MSN session.
+ * @param state   The new state.
+ */
+void msn_change_status(MsnSession *session, MsnAwayType state);
+
+/**
  * Returns the string representation of an away type.
  *
  * @param type The away type.
@@ -50,4 +58,6 @@
  */
 const char *msn_away_get_text(MsnAwayType type);
 
+const char *msn_state_get_text(MsnAwayType state);
+
 #endif /* _MSN_STATE_H_ */
--- a/src/protocols/msn/switchboard.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/switchboard.c	Sun Jun 06 02:39:08 2004 +0000
@@ -20,12 +20,14 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 #include "msn.h"
-#include "msnslp.h"
 #include "prefs.h"
 #include "switchboard.h"
+#include "notification.h"
 #include "utils.h"
 
-static MsnTable *cbs_table = NULL;
+#include "error.h"
+
+static MsnTable *cbs_table;
 
 /**************************************************************************
  * Utility functions
@@ -45,6 +47,90 @@
 	msn_message_destroy(msg);
 }
 
+void
+msn_switchboard_add_user(MsnSwitchBoard *swboard, const char *user)
+{
+	MsnCmdProc *cmdproc;
+	GaimAccount *account;
+	
+	g_return_if_fail(swboard != NULL);
+
+	cmdproc = swboard->servconn->cmdproc;
+	account = swboard->servconn->session->account;
+
+	swboard->users = g_list_prepend(swboard->users, g_strdup(user));
+	swboard->current_users++;
+
+	/* gaim_debug_info("msn", "user=[%s], total=%d\n", user,
+	 * swboard->current_users); */
+
+	if (swboard->current_users > 1 || swboard->total_users > 1)
+	{
+		if (swboard->conv == NULL ||
+			gaim_conversation_get_type(swboard->conv) != GAIM_CONV_CHAT)
+		{
+			GList *l;
+
+			/* gaim_debug_info("msn", "[chat] Switching to chat.\n"); */
+
+			if (swboard->conv != NULL)
+				gaim_conversation_destroy(swboard->conv);
+
+			cmdproc->session->conv_seq++;
+			swboard->chat_id = cmdproc->session->conv_seq;
+
+			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;
+
+				/* gaim_debug_info("msn", "[chat] Adding [%s].\n",
+				 * tmp_user); */
+
+				gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->conv),
+										tmp_user, NULL);
+			}
+
+			if (!swboard->invited)
+			{
+				/* gaim_debug_info("msn", "[chat] "
+								"Not invited, so we add im_user [%s].\n",
+								swboard->im_user); */
+
+				gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->conv),
+										swboard->im_user, NULL);
+			}
+
+			/* gaim_debug_info("msn", "[chat] We add ourselves.\n"); */
+
+			gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->conv),
+									gaim_account_get_username(account),
+									NULL);
+
+			g_free(swboard->im_user);
+			swboard->im_user = NULL;
+		}
+		else if (gaim_conversation_get_type(swboard->conv) == GAIM_CONV_CHAT)
+		{
+			gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->conv), user, NULL);
+		}
+	}
+	else if (swboard->conv == NULL)
+	{
+		swboard->conv = gaim_find_conversation_with_account(user, account);
+	}
+	else
+	{
+		gaim_debug_warning("msn", "This should happen!"
+						   "(msn_switchboard_add_user)\n");
+	}
+}
+
 /**************************************************************************
  * Switchboard Commands
  **************************************************************************/
@@ -52,35 +138,28 @@
 ans_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
 	MsnSwitchBoard *swboard;
+#if 1
 	MsnSession *session;
 
+	session = cmdproc->session;
 	swboard = cmdproc->servconn->data;
-	session = cmdproc->session;
 
 	/* send_clientcaps(swboard); */
 
-	if (0 && session->protocol_ver >= 9)
+	if (session->protocol_ver >= 9)
 	{
-		MsnUser *local_user, *remote_user;
+		GList *l;	
 
-		remote_user = msn_user_new(session,
-				msn_user_get_passport(msn_switchboard_get_user(swboard)),
-				NULL);
-		local_user = msn_user_new(session,
-								  gaim_account_get_username(session->account),
-								  NULL);
-
-		if (msn_user_get_object(remote_user) != NULL)
+		/* But we alredy know the switchboard... */
+		/* What if there is more than one user? */
+		for (l = swboard->users; l != NULL; l = l->next)
 		{
-			swboard->slp_session = msn_slp_session_new(swboard, TRUE);
-
-			msn_slp_session_request_user_display(swboard->slp_session,
-					local_user, remote_user,
-					msn_user_get_object(remote_user));
+			msn_request_buddy_icon(session->account->gc, l->data);
 		}
 	}
+#endif
 
-	swboard->joined = TRUE;
+	swboard->ready = TRUE;
 }
 
 static void
@@ -88,17 +167,18 @@
 {
 	GaimAccount *account;
 	MsnSwitchBoard *swboard;
-	const char *user = cmd->params[0];
+	const char *user;
 
 	account = cmdproc->session->account;
 	swboard = cmdproc->servconn->data;
+	user = cmd->params[0];
 
 	if (swboard->hidden)
 		return;
 
-	if (swboard->chat != NULL)
+	if (swboard->current_users > 1)
 	{
-		gaim_conv_chat_remove_user(GAIM_CONV_CHAT(swboard->chat), user, NULL);
+		gaim_conv_chat_remove_user(GAIM_CONV_CHAT(swboard->conv), user, NULL);
 	}
 	else
 	{
@@ -138,7 +218,7 @@
 			g_free(str);
 		}
 
-		msn_switchboard_destroy(swboard);
+		msn_switchboard_disconnect(swboard);
 	}
 }
 
@@ -153,88 +233,56 @@
 	gc = account->gc;
 	swboard = cmdproc->servconn->data;
 
-	swboard->total_users = atoi(cmd->params[2]) + 1;
-
-	if (swboard->total_users > 2)
-	{
-		if (swboard->chat == NULL)
-		{
-			GaimConversation *conv;
-
-			conv = gaim_find_conversation_with_account(
-				msn_user_get_passport(swboard->user), account);
+	swboard->total_users = atoi(cmd->params[2]);
 
-			cmdproc->session->last_chat_id++;
-			swboard->chat_id = cmdproc->session->last_chat_id;
-			swboard->chat = serv_got_joined_chat(gc, swboard->chat_id,
-												 "MSN Chat");
-
-			gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->chat),
-									gaim_account_get_username(account), NULL);
-
-			gaim_conversation_destroy(conv);
-		}
-
-		gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->chat), cmd->params[3], NULL);
-	}
+	msn_switchboard_add_user(swboard, cmd->params[3]);
 }
 
 static void
 joi_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
+	MsnSession *session;
 	GaimAccount *account;
 	GaimConnection *gc;
 	MsnSwitchBoard *swboard;
 	const char *passport;
 
-	account = cmdproc->session->account;
-	gc = account->gc;
-	swboard = cmdproc->servconn->data;
 	passport = cmd->params[0];
 
-	if (swboard->total_users == 2 && swboard->chat == NULL)
-	{
-		GaimConversation *conv;
-
-		conv = gaim_find_conversation_with_account(
-			msn_user_get_passport(swboard->user), account);
+	session = cmdproc->session;
+	account = session->account;
+	gc = account->gc;
+	swboard = cmdproc->servconn->data;
 
-		cmdproc->session->last_chat_id++;
-		swboard->chat_id = cmdproc->session->last_chat_id;
-		swboard->chat = serv_got_joined_chat(gc, swboard->chat_id, "MSN Chat");
-		gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->chat),
-								msn_user_get_passport(swboard->user), NULL);
-		gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->chat),
-								gaim_account_get_username(account), NULL);
+	msn_switchboard_add_user(swboard, passport);
+
+	swboard->user_joined = TRUE;
 
-		msn_user_unref(swboard->user);
+	/* msn_cmdproc_process_queue(cmdproc); */
 
-		gaim_conversation_destroy(conv);
-	}
-
-	if (swboard->chat != NULL)
-		gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->chat), passport, NULL);
+	msn_switchboard_process_queue(swboard);
+	
+	send_clientcaps(swboard);
 
-	swboard->total_users++;
-
-	swboard->joined = TRUE;
-
-	msn_cmdproc_process_queue(cmdproc);
-
-	send_clientcaps(swboard);
+#if 1
+	if (session->protocol_ver >= 9)
+		/* But we alredy know the switchboard... */
+		msn_request_buddy_icon(gc, passport);
+#endif
 }
 
 static void
-msg_cmd_post(MsnCmdProc *cmdproc, char *payload, size_t len)
+msg_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len)
 {
-	MsnMessage *msg = msn_message_new();
+	MsnMessage *msg;
+
+	msg = msn_message_new_from_cmd(cmdproc->session, cmd);
 
 	msn_message_parse_payload(msg, payload, len);
+	/* msn_message_show_readable(msg, "SB RECV", FALSE); */
 
-	msg->passport = cmdproc->temp;
+	msg->remote_user = g_strdup(cmd->params[0]);
 	msn_cmdproc_process_msg(cmdproc, msg);
-	g_free(cmdproc->temp);
-	cmdproc->temp = NULL;
 
 	msn_message_destroy(msg);
 }
@@ -242,23 +290,35 @@
 static void
 msg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	cmdproc->payload_cb  = msg_cmd_post;
 	cmdproc->servconn->payload_len = atoi(cmd->params[2]);
-	cmdproc->temp = g_strdup(cmd->params[0]);
+	cmdproc->last_cmd->payload_cb = msg_cmd_post;
 }
 
 static void
 nak_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
 	/*
-	 * TODO: Investigate this, as it seems to occur frequently with
-	 *       the old prpl.
-	 *
-	 * shx: This will only happend in the new protocol when we ask for it
-	 *      in the message flags.
+	gaim_notify_error(cmdproc->session->account->gc, NULL,
+					  _("A MSN message may not have been received."), NULL);
 	 */
-	gaim_notify_error(cmdproc->servconn->session->account->gc, NULL,
-					  _("An MSN message may not have been received."), NULL);
+}
+
+static void
+ack_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+#if 0
+	MsnMessage *msg;
+	const char *body;
+
+	msg = msn_message_new();
+	msn_message_parse_payload(msg, cmd->trans->payload, cmd->trans->payload_len);
+
+	body = msn_message_get_body(msg);
+
+	gaim_debug_info("msn", "ACK: {%s}\n", body);
+
+	msn_message_destroy(msg);
+#endif
 }
 
 static void
@@ -267,16 +327,13 @@
 	GaimConnection *gc;
 	MsnSwitchBoard *swboard;
 
-	gc = cmdproc->servconn->session->account->gc;
+	gc = cmdproc->session->account->gc;
 	swboard = cmdproc->servconn->data;
 
-	if (swboard->chat != NULL)
-	{
-		serv_got_chat_left(gc,
-			gaim_conv_chat_get_id(GAIM_CONV_CHAT(swboard->chat)));
-	}
+	if (swboard->current_users > 1)
+		serv_got_chat_left(gc, swboard->chat_id);
 
-	msn_switchboard_destroy(swboard);
+	msn_switchboard_disconnect(swboard);
 }
 
 static void
@@ -286,8 +343,20 @@
 
 	swboard = cmdproc->servconn->data;
 
-	msn_cmdproc_send(swboard->cmdproc, "CAL", "%s",
-					 msn_user_get_passport(swboard->user));
+#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);
 }
 
 /**************************************************************************
@@ -314,7 +383,7 @@
 	body_enc = gaim_escape_html(body_str);
 	g_free(body_str);
 
-	passport = msg->passport;
+	passport = msg->remote_user;
 
 	if (!strcmp(passport, "messenger@microsoft.com") &&
 		strstr(body, "immediate security update"))
@@ -346,11 +415,10 @@
 		body_final = body_enc;
 	}
 
-	if (swboard->chat != NULL)
+	if (swboard->current_users > 1)
 	{
-		serv_got_chat_in(gc,
-						 gaim_conv_chat_get_id(GAIM_CONV_CHAT(swboard->chat)),
-						 passport, 0, body_final, time(NULL));
+		serv_got_chat_in(gc, swboard->chat_id, passport, 0, body_final,
+						 time(NULL));
 	}
 	else
 		serv_got_im(gc, passport, body_final, 0, time(NULL));
@@ -363,14 +431,14 @@
 {
 	GaimConnection *gc;
 	MsnSwitchBoard *swboard;
+	const char *value;
 	char *passport;
-	const char *value;
 
 	gc = cmdproc->session->account->gc;
 	swboard = cmdproc->servconn->data;
-	passport = msg->passport;
+	passport = msg->remote_user;
 
-	if (swboard->chat == NULL &&
+	if (swboard->current_users == 1 &&
 		(value = msn_message_get_attr(msg, "TypingUser")) != NULL)
 	{
 		serv_got_typing(gc, passport, MSN_TYPING_RECV_TIMEOUT,
@@ -388,10 +456,10 @@
 	GHashTable *clientcaps;
 	const char *value;
 
+	char *passport = msg->sender;
+
 	session = cmdproc->session;
-	swboard = cmdproc->servconn->data;
-
-	user = msn_user_new(session, msg->passport, NULL);
+	swboard = cmdproc->servconn->swboard;
 
 	clientcaps = msn_message_get_hashtable_from_body(msg);
 #endif
@@ -403,7 +471,7 @@
 	MsnCmdProc *cmdproc;
 	MsnTransaction *trans;
 	char *payload;
-	size_t payload_len;
+	gsize payload_len;
 
 	g_return_if_fail(swboard != NULL);
 	g_return_if_fail(msg     != NULL);
@@ -412,22 +480,56 @@
 
 	payload = msn_message_gen_payload(msg, &payload_len);
 
+	/* msn_message_show_readable(msg, "SB SEND", FALSE); */
+
 	trans = msn_transaction_new("MSG", "%c %d", msn_message_get_flag(msg),
 								payload_len);
 
+	if (msg->ack_cb != NULL)
+		msn_transaction_add_cb(trans, "ACK", msg->ack_cb, msg->ack_data);
+
 	trans->payload = payload;
 	trans->payload_len = payload_len;
 
-	if (!g_queue_is_empty(cmdproc->txqueue) || !swboard->joined)
-		msn_cmdproc_queue_trans(cmdproc, trans);
-	else
-		msn_cmdproc_send_trans(cmdproc, trans);
+	msg->trans = trans;
+
+	msn_cmdproc_send_trans(cmdproc, trans);
+}
+
+void
+msn_switchboard_queue_msg(MsnSwitchBoard *swboard, MsnMessage *msg)
+{
+	g_return_if_fail(swboard != NULL);
+	g_return_if_fail(msg     != NULL);
+
+	gaim_debug_info("msn", "Appending message to queue.\n");
+
+	g_queue_push_tail(swboard->im_queue, msg);
+
+	msn_message_ref(msg);
+}
+
+void
+msn_switchboard_process_queue(MsnSwitchBoard *swboard)
+{
+	MsnMessage *msg;
+
+	g_return_if_fail(swboard != NULL);
+
+	gaim_debug_info("msn", "Processing queue\n");
+
+	while ((msg = g_queue_pop_head(swboard->im_queue)) != NULL)
+	{
+		gaim_debug_info("msn", "Sending message\n");
+		msn_switchboard_send_msg(swboard, msg);
+		msn_message_unref(msg);
+	}
 }
 
 /**************************************************************************
  * Connect stuff
  **************************************************************************/
-static gboolean
+static void
 connect_cb(MsnServConn *servconn)
 {
 	MsnSwitchBoard *swboard;
@@ -435,15 +537,15 @@
 	GaimAccount *account;
 
 	cmdproc = servconn->cmdproc;
-	g_return_val_if_fail(cmdproc != NULL, FALSE);
+	g_return_if_fail(cmdproc != NULL);
+
+	cmdproc->ready = TRUE;
 
 	account = servconn->session->account;
 	swboard = servconn->data;
-	g_return_val_if_fail(swboard != NULL, FALSE);
+	g_return_if_fail(swboard != NULL);
 
-	/* swboard->user_joined = TRUE; */
-
-	swboard->in_use = TRUE;
+	swboard->user_joined = TRUE;
 
 	if (msn_switchboard_is_invited(swboard))
 	{
@@ -457,11 +559,6 @@
 						 gaim_account_get_username(account),
 						 swboard->auth_key);
 	}
-
-	if (swboard->cmdproc->error)
-		return FALSE;
-
-	return TRUE;
 }
 
 static void
@@ -470,9 +567,9 @@
 	MsnSwitchBoard *swboard;
 
 	swboard = servconn->data;
+	g_return_if_fail(swboard != NULL);
 
-	if (!swboard->destroying)
-		msn_switchboard_destroy(swboard);
+	msn_switchboard_destroy(swboard);
 }
 
 void
@@ -480,23 +577,26 @@
 {
 	cbs_table = msn_table_new();
 
-	/* Register the command callbacks. */
-	msn_table_add_cmd(cbs_table, NULL, "ACK", NULL);
-
 	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, "CAL", "CAL", NULL);
-
 	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", NULL);
+	
 	/* Register the message type callbacks. */
 	msn_table_add_msg_type(cbs_table, "text/plain",
 						   plain_msg);
@@ -506,9 +606,13 @@
 						   clientcaps_msg);
 	msn_table_add_msg_type(cbs_table, "text/x-clientinfo",
 						   clientcaps_msg);
-#if 0
 	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);
+#if 0
+	msn_table_add_msg_type(cbs_table, "text/x-msmmsginvite",
+						   msn_invite_msg);
 #endif
 }
 
@@ -529,12 +633,15 @@
 
 	swboard = g_new0(MsnSwitchBoard, 1);
 
+	swboard->session = session;
 	swboard->servconn = servconn = msn_servconn_new(session, MSN_SERVER_SB);
-	cmdproc = swboard->cmdproc = servconn->cmdproc;
+	cmdproc = servconn->cmdproc;
 
 	msn_servconn_set_connect_cb(servconn, connect_cb);
 	msn_servconn_set_disconnect_cb(servconn, disconnect_cb);
 
+	swboard->im_queue = g_queue_new();
+
 	if (session->http_method)
 		servconn->http_data->server_type = "SB";
 
@@ -551,18 +658,18 @@
 msn_switchboard_destroy(MsnSwitchBoard *swboard)
 {
 	MsnSession *session;
+	MsnMessage *msg;
+	GList *l;
 
 	g_return_if_fail(swboard != NULL);
-	g_return_if_fail(!swboard->destroying);
+
+	if (swboard->destroying)
+		return;
 
 	swboard->destroying = TRUE;
-	session = swboard->servconn->session;
 
-	if (swboard->servconn->connected)
-		msn_switchboard_disconnect(swboard);
-
-	if (swboard->user != NULL)
-		msn_user_unref(swboard->user);
+	if (swboard->im_user != NULL)
+		g_free(swboard->im_user);
 
 	if (swboard->auth_key != NULL)
 		g_free(swboard->auth_key);
@@ -570,30 +677,77 @@
 	if (swboard->session_id != NULL)
 		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);
 
-	msn_servconn_destroy(swboard->servconn);
+	if (swboard->servconn != NULL)
+		msn_servconn_destroy(swboard->servconn);
+
+	while ((msg = g_queue_pop_head(swboard->im_queue)) != NULL)
+		msn_message_destroy(msg);
+
+	g_queue_free(swboard->im_queue);
 
 	g_free(swboard);
 }
 
+#if 0
 void
-msn_switchboard_set_user(MsnSwitchBoard *swboard, MsnUser *user)
+msn_switchboard_set_user(MsnSwitchBoard *swboard, const char *user)
 {
 	g_return_if_fail(swboard != NULL);
 
-	swboard->user = user;
+	if (swboard->user != NULL)
+		g_free(swboard->user);
 
-	msn_user_ref(user);
+	swboard->user = g_strdup(user);
 }
 
-MsnUser *
-msn_switchboard_get_user(const MsnSwitchBoard *swboard)
+const char *
+msn_switchboard_get_user(MsnSwitchBoard *swboard)
 {
 	g_return_val_if_fail(swboard != NULL, NULL);
 
 	return swboard->user;
 }
+#endif
+
+#if 0
+static void
+got_cal(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSwitchBoard *swboard;
+	const char *user;
+
+	swboard = cmdproc->servconn->data;
+
+	user = cmd->params[0];
+
+	msn_switchboard_add_user(swboard, user);
+}
+#endif
+
+void
+msn_switchboard_request_add_user(MsnSwitchBoard *swboard, const char *user)
+{
+	MsnTransaction *trans;
+	MsnCmdProc *cmdproc;
+
+	g_return_if_fail(swboard != NULL);
+
+	cmdproc = swboard->servconn->cmdproc;
+
+	trans = msn_transaction_new("CAL", "%s", user);
+	/* msn_transaction_add_cb(trans, "CAL", got_cal, NULL); */
+
+	if (swboard->ready)
+		msn_cmdproc_send_trans(cmdproc, trans);
+	else
+		msn_cmdproc_queue_trans(cmdproc, trans);
+}
 
 void
 msn_switchboard_set_auth_key(MsnSwitchBoard *swboard, const char *key)
@@ -605,7 +759,7 @@
 }
 
 const char *
-msn_switchboard_get_auth_key(const MsnSwitchBoard *swboard)
+msn_switchboard_get_auth_key(MsnSwitchBoard *swboard)
 {
 	g_return_val_if_fail(swboard != NULL, NULL);
 
@@ -625,7 +779,7 @@
 }
 
 const char *
-msn_switchboard_get_session_id(const MsnSwitchBoard *swboard)
+msn_switchboard_get_session_id(MsnSwitchBoard *swboard)
 {
 	g_return_val_if_fail(swboard != NULL, NULL);
 
@@ -641,7 +795,7 @@
 }
 
 gboolean
-msn_switchboard_is_invited(const MsnSwitchBoard *swboard)
+msn_switchboard_is_invited(MsnSwitchBoard *swboard)
 {
 	g_return_val_if_fail(swboard != NULL, FALSE);
 
@@ -653,19 +807,47 @@
 {
 	g_return_val_if_fail(swboard != NULL, FALSE);
 
-	if (msn_servconn_connect(swboard->servconn, host, port))
-		swboard->in_use = TRUE;
-
-	return swboard->in_use;
+	return msn_servconn_connect(swboard->servconn, host, port);
 }
 
 void
 msn_switchboard_disconnect(MsnSwitchBoard *swboard)
 {
 	g_return_if_fail(swboard != NULL);
-	g_return_if_fail(swboard->servconn->connected);
 
 	msn_servconn_disconnect(swboard->servconn);
+}
 
-	swboard->in_use = FALSE;
+static void
+got_swboard(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSwitchBoard *swboard;
+	swboard = cmd->trans->data;
+	char *host;
+	int port;
+
+	msn_switchboard_set_auth_key(swboard, cmd->params[4]);
+
+	msn_parse_socket(cmd->params[2], &host, &port);
+
+	if (swboard->session->http_method)
+		port = 80;
+
+	msn_switchboard_connect(swboard, host, port);
 }
+
+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("XFR", "%s", "SB");
+	msn_transaction_add_cb(trans, "XFR", got_swboard, swboard);
+
+	msn_cmdproc_send_trans(cmdproc, trans);
+}
--- a/src/protocols/msn/switchboard.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/switchboard.h	Sun Jun 06 02:39:08 2004 +0000
@@ -27,16 +27,15 @@
 #include "conversation.h"
 
 #include "msg.h"
-#include "msnslp.h"
+#include "user.h"
+
 #include "servconn.h"
-#include "cmdproc.h"
-#include "user.h"
 
 struct _MsnSwitchBoard
 {
+	MsnSession *session;
 	MsnServConn *servconn;
-	MsnCmdProc *cmdproc;
-	MsnUser *user;
+	char *im_user;
 
 	char *auth_key;
 	char *session_id;
@@ -44,21 +43,21 @@
 	gboolean invited;
 	gboolean destroying;
 
-	GaimConversation *chat;
-
-	gboolean in_use;
-	gboolean joined;
+	GaimConversation *conv;
 
-	int total_users;
+	gboolean ready; /* When it's actually usable */
+	/* gboolean in_use; */
 
-	gboolean msg;
-	int msglen;
+	int current_users;
+	int total_users;
+	GList *users;
 
 	int chat_id;
 
 	gboolean hidden;
 
-	MsnSlpSession *slp_session;
+	gboolean user_joined;
+	GQueue *im_queue;
 };
 
 /**
@@ -87,13 +86,14 @@
  */
 void msn_switchboard_destroy(MsnSwitchBoard *swboard);
 
+#if 0
 /**
  * Sets the user the switchboard is supposed to connect to.
  *
  * @param swboard The switchboard.
  * @param user    The user.
  */
-void msn_switchboard_set_user(MsnSwitchBoard *swboard, MsnUser *user);
+void msn_switchboard_set_user(MsnSwitchBoard *swboard, const char *user);
 
 /**
  * Returns the user the switchboard is supposed to connect to.
@@ -102,7 +102,8 @@
  *
  * @return The user.
  */
-MsnUser *msn_switchboard_get_user(const MsnSwitchBoard *swboard);
+const char *msn_switchboard_get_user(MsnSwitchBoard *swboard);
+#endif
 
 /**
  * Sets the auth key the switchboard must use when connecting.
@@ -119,7 +120,7 @@
  *
  * @return The auth key.
  */
-const char *msn_switchboard_get_auth_key(const MsnSwitchBoard *swboard);
+const char *msn_switchboard_get_auth_key(MsnSwitchBoard *swboard);
 
 /**
  * Sets the session ID the switchboard must use when connecting.
@@ -136,7 +137,7 @@
  *
  * @return The session ID.
  */
-const char *msn_switchboard_get_session_id(const MsnSwitchBoard *swboard);
+const char *msn_switchboard_get_session_id(MsnSwitchBoard *swboard);
 
 /**
  * Sets whether or not the user was invited to this switchboard.
@@ -153,7 +154,7 @@
  *
  * @return @c TRUE if invited, @c FALSE otherwise.
  */
-gboolean msn_switchboard_is_invited(const MsnSwitchBoard *swboard);
+gboolean msn_switchboard_is_invited(MsnSwitchBoard *swboard);
 
 /**
  * Connects to a switchboard.
@@ -166,21 +167,25 @@
  */
 gboolean msn_switchboard_connect(MsnSwitchBoard *swboard,
 								 const char *host, int port);
+void msn_switchboard_disconnect(MsnSwitchBoard *swboard);
+void msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg);
 
-/**
- * Disconnects from a switchboard.
- *
- * @param swboard The switchboard.
- */
-void msn_switchboard_disconnect(MsnSwitchBoard *swboard);
+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);
+void msn_switchboard_queue_msg(MsnSwitchBoard *swboard, MsnMessage *msg);
+void msn_switchboard_process_queue(MsnSwitchBoard *swboard);
 
 /**
- * Sends a message to a switchboard.
+ * Processes application/x-msnmsgrp2p messages.
  *
- * @param swboard The switchboard.
- * @param msg     The message to send.
+ * @param cmdproc The command processor.
+ * @param msg     The message.
  */
-void msn_switchboard_send_msg(MsnSwitchBoard *swboard,
-							  MsnMessage *msg);
+void msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
+void msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
+void msn_invite_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
 
 #endif /* _MSN_SWITCHBOARD_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/sync.c	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,227 @@
+#include "msn.h"
+#include "sync.h"
+#include "state.h"
+
+static MsnTable *cbs_table;
+
+static void
+blp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	GaimConnection *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 = GAIM_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 = GAIM_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, gaim_url_decode(value));
+		else if (!strcmp(type, "PHW"))
+			msn_user_set_work_phone(session->user, gaim_url_decode(value));
+		else if (!strcmp(type, "PHM"))
+			msn_user_set_mobile_phone(session->user, gaim_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;
+	MsnGroup *group;
+	GaimGroup *g;
+	const char *name;
+	int group_id;
+
+	group_id = atoi(cmd->params[0]);
+	name = gaim_url_decode(cmd->params[1]);
+
+	group = msn_group_new(session->userlist, group_id, name);
+
+	if ((g = gaim_find_group(name)) == NULL)
+	{
+		g = gaim_group_new(name);
+		gaim_blist_add_group(g, NULL);
+	}
+}
+
+static void
+lst_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	MsnSession *session = cmdproc->session;
+	GaimAccount *account = session->account;
+	GaimConnection *gc = gaim_account_get_connection(account);
+	char *passport = NULL;
+	const char *friend = NULL;
+	int list_op;
+	MsnUser *user;
+
+	passport   = cmd->params[0];
+	friend     = gaim_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_user_set_buddy_icon(session->user,
+								gaim_account_get_buddy_icon(session->account));
+
+		msn_change_status(session, MSN_ONLINE);
+
+		gaim_connection_set_state(gc, GAIM_CONNECTED);
+		serv_finish_login(gc);
+
+		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;
+
+	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, gaim_url_decode(value));
+		else if (!strcmp(type, "PHW"))
+			msn_user_set_work_phone(user, gaim_url_decode(value));
+		else if (!strcmp(type, "PHM"))
+			msn_user_set_mobile_phone(user, gaim_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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/sync.h	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,29 @@
+#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_ */
--- a/src/protocols/msn/table.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/table.h	Sun Jun 06 02:39:08 2004 +0000
@@ -28,7 +28,6 @@
 #include "transaction.h"
 #include "msg.h"
 
-typedef void (*MsnTransCb)(MsnCmdProc *cmdproc, MsnCommand *cmd);
 typedef void (*MsnErrorCb)(MsnCmdProc *cmdproc, MsnTransaction *trans,
 						   int error);
 typedef void (*MsnMsgCb)(MsnCmdProc *cmdproc, MsnMessage *msg);
--- a/src/protocols/msn/transaction.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/transaction.c	Sun Jun 06 02:39:08 2004 +0000
@@ -90,6 +90,47 @@
 }
 
 void
+msn_transaction_queue_cmd(MsnTransaction *trans, MsnCommand *cmd)
+{
+	gaim_debug_info("msn", "queueing command.\n");
+	trans->pendent_cmd = cmd;
+	msn_command_ref(cmd);
+}
+
+void
+msn_transaction_unqueue_cmd(MsnTransaction *trans, MsnCmdProc *cmdproc)
+{
+	gaim_debug_info("msn", "unqueueing command.\n");
+	MsnCommand *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)
 {
@@ -99,3 +140,26 @@
 	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, void *data)
+{
+	g_return_if_fail(trans  != NULL);
+	g_return_if_fail(answer != NULL);
+
+	if (trans->callbacks == NULL)
+		trans->callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+
+	g_hash_table_insert(trans->callbacks, answer, cb);
+
+	trans->data = data;
+}
--- a/src/protocols/msn/transaction.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/transaction.h	Sun Jun 06 02:39:08 2004 +0000
@@ -27,6 +27,8 @@
 #include "command.h"
 #include "cmdproc.h"
 
+typedef void (*MsnTransCb)(MsnCmdProc *cmdproc, MsnCommand *cmd);
+
 /**
  * A transaction. A command that will initiate the transaction.
  */
@@ -49,11 +51,15 @@
 
 MsnTransaction *msn_transaction_new(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 *data);
 
 #endif /* _MSN_TRANSACTION_H */
--- a/src/protocols/msn/user.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/user.c	Sun Jun 06 02:39:08 2004 +0000
@@ -23,22 +23,16 @@
 #include "user.h"
 
 MsnUser *
-msn_user_new(MsnSession *session, const char *passport, const char *name)
+msn_user_new(MsnUserList *userlist, const char *passport,
+			 const char *store_name)
 {
 	MsnUser *user;
 
-	user = msn_users_find_with_passport(session->users, passport);
-
-	if (user == NULL)
-	{
-		user = g_new0(MsnUser, 1);
+	user = g_new0(MsnUser, 1);
 
-		user->session = session;
+	user->userlist = userlist;
 
-		msn_user_set_passport(user, passport);
-
-		msn_users_add(session->users, user);
-	}
+	msn_user_set_passport(user, passport);
 
 	/*
 	 * XXX This seems to reset the friendly name from what it should be
@@ -49,8 +43,6 @@
 		msn_user_set_name(user, name);
 #endif
 
-	msn_user_ref(user);
-
 	return user;
 }
 
@@ -59,15 +51,6 @@
 {
 	g_return_if_fail(user != NULL);
 
-	if (user->ref_count > 0) {
-		msn_user_unref(user);
-
-		return;
-	}
-
-	if (user->session != NULL && user->session->users != NULL)
-		msn_users_remove(user->session->users, user);
-
 	if (user->clientcaps != NULL)
 		g_hash_table_destroy(user->clientcaps);
 
@@ -77,45 +60,27 @@
 	if (user->msnobj != NULL)
 		msn_object_destroy(user->msnobj);
 
-	if (user->passport != NULL) g_free(user->passport);
-	if (user->name     != NULL) g_free(user->name);
+	if (user->passport != NULL)
+		g_free(user->passport);
+
+	if (user->friendly_name != NULL)
+		g_free(user->friendly_name);
+
+	if (user->store_name != NULL)
+		g_free(user->store_name);
 
-	if (user->phone.home   != NULL) g_free(user->phone.home);
-	if (user->phone.work   != NULL) g_free(user->phone.work);
-	if (user->phone.mobile != NULL) g_free(user->phone.mobile);
+	if (user->phone.home != NULL)
+		g_free(user->phone.home);
+
+	if (user->phone.work != NULL)
+		g_free(user->phone.work);
+
+	if (user->phone.mobile != NULL)
+		g_free(user->phone.mobile);
 
 	g_free(user);
 }
 
-MsnUser *
-msn_user_ref(MsnUser *user)
-{
-	g_return_val_if_fail(user != NULL, NULL);
-
-	user->ref_count++;
-
-	return user;
-}
-
-MsnUser *
-msn_user_unref(MsnUser *user)
-{
-	g_return_val_if_fail(user != NULL, NULL);
-
-	if (user->ref_count <= 0)
-		return NULL;
-
-	user->ref_count--;
-
-	if (user->ref_count == 0) {
-		msn_user_destroy(user);
-
-		return NULL;
-	}
-
-	return user;
-}
-
 void
 msn_user_set_passport(MsnUser *user, const char *passport)
 {
@@ -128,14 +93,25 @@
 }
 
 void
-msn_user_set_name(MsnUser *user, const char *name)
+msn_user_set_friendly_name(MsnUser *user, const char *name)
 {
 	g_return_if_fail(user != NULL);
 
-	if (user->name != NULL)
-		g_free(user->name);
+	if (user->friendly_name != NULL)
+		g_free(user->friendly_name);
+
+	user->friendly_name = g_strdup(name);
+}
 
-	user->name = g_strdup(name);
+void
+msn_user_set_store_name(MsnUser *user, const char *name)
+{
+	g_return_if_fail(user != NULL);
+
+	if (user->store_name != NULL)
+		g_free(user->store_name);
+
+	user->store_name = g_strdup(name);
 }
 
 void
@@ -148,24 +124,29 @@
 	g_return_if_fail(user != NULL);
 
 	if (filename == NULL || stat(filename, &st) == -1)
+	{
 		msn_user_set_object(user, NULL);
+	}
 	else if ((fp = fopen(filename, "rb")) != NULL)
 	{
 		unsigned char *buf;
 		SHA_CTX ctx;
-		size_t len;
+		gsize len;
 		char *base64;
 		unsigned char digest[20];
 
 		if (msnobj == NULL)
 		{
-			msnobj = msn_object_new();
+			msnobj = msn_object_new(TRUE);
+			msn_object_set_local(msnobj);
 			msn_object_set_type(msnobj, MSN_OBJECT_USERTILE);
-			msn_object_set_location(msnobj, "TFR2C.tmp");
+			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_real_location(msnobj, filename);
 
 		buf = g_malloc(st.st_size);
 		len = fread(buf, 1, st.st_size, fp);
@@ -215,21 +196,38 @@
 }
 
 void
-msn_user_set_group_ids(MsnUser *user, GList *ids)
-{
-	g_return_if_fail(user != NULL);
-
-	user->group_ids = ids;
-}
-
-void
 msn_user_add_group_id(MsnUser *user, int id)
 {
+	MsnUserList *userlist;
+	GaimAccount *account;
+	GaimBuddy *b;
+	GaimGroup *g;
+	const char *passport;
+	const char *group_name;
+
 	g_return_if_fail(user != NULL);
 	g_return_if_fail(id > -1);
 
-	if (!g_list_find(user->group_ids, GINT_TO_POINTER(id)))
-		user->group_ids = g_list_append(user->group_ids, GINT_TO_POINTER(id));
+	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 = gaim_find_group(group_name);
+
+	b = gaim_find_buddy_in_group(account, passport, g);
+
+	if (b == NULL)
+	{
+		b = gaim_buddy_new(account, passport, NULL);
+
+		gaim_blist_add_buddy(b, NULL, g, NULL);
+	}
+
+	b->proto_data = user;
 }
 
 void
@@ -240,6 +238,7 @@
 
 	user->group_ids = g_list_remove(user->group_ids, GINT_TO_POINTER(id));
 }
+
 void
 msn_user_set_home_phone(MsnUser *user, const char *number)
 {
@@ -282,6 +281,19 @@
 		msn_object_destroy(user->msnobj);
 
 	user->msnobj = obj;
+
+	if (user->list_op & MSN_LIST_FL_OP)
+	{
+		/* TODO: I think we need better buddy icon core functions */
+		GaimAccount *account;
+		const char *username;
+
+		account  = user->userlist->session->account;
+		username = msn_object_get_creator(obj);;
+
+		if (gaim_find_conversation_with_account(username, account) != NULL)
+			msn_request_buddy_icon(account->gc, username);
+	}
 }
 
 void
@@ -305,19 +317,19 @@
 }
 
 const char *
-msn_user_get_name(const MsnUser *user)
+msn_user_get_friendly_name(const MsnUser *user)
 {
 	g_return_val_if_fail(user != NULL, NULL);
 
-	return user->name;
+	return user->friendly_name;
 }
 
-GList *
-msn_user_get_group_ids(const MsnUser *user)
+const char *
+msn_user_get_store_name(const MsnUser *user)
 {
 	g_return_val_if_fail(user != NULL, NULL);
 
-	return user->group_ids;
+	return user->store_name;
 }
 
 const char *
@@ -359,80 +371,3 @@
 
 	return user->clientcaps;
 }
-
-MsnUsers *
-msn_users_new(void)
-{
-	MsnUsers *users = g_new0(MsnUsers, 1);
-
-	return users;
-}
-
-void
-msn_users_destroy(MsnUsers *users)
-{
-	GList *l, *l_next = NULL;
-
-	g_return_if_fail(users != NULL);
-
-	for (l = users->users; l != NULL; l = l_next) {
-		l_next = l->next;
-
-		msn_user_destroy(l->data);
-
-		users->users = g_list_remove(users->users, l->data);
-	}
-
-	g_free(users);
-}
-
-void
-msn_users_add(MsnUsers *users, MsnUser *user)
-{
-	g_return_if_fail(users != NULL);
-	g_return_if_fail(user != NULL);
-
-	users->users = g_list_append(users->users, user);
-
-	users->count++;
-}
-
-void
-msn_users_remove(MsnUsers *users, MsnUser *user)
-{
-	g_return_if_fail(users != NULL);
-	g_return_if_fail(user  != NULL);
-
-	users->users = g_list_remove(users->users, user);
-
-	users->count--;
-}
-
-size_t
-msn_users_get_count(const MsnUsers *users)
-{
-	g_return_val_if_fail(users != NULL, 0);
-
-	return users->count;
-}
-
-MsnUser *
-msn_users_find_with_passport(MsnUsers *users, const char *passport)
-{
-	GList *l;
-
-	g_return_val_if_fail(users != NULL, NULL);
-	g_return_val_if_fail(passport != NULL, NULL);
-
-	for (l = users->users; l != NULL; l = l->next) {
-		MsnUser *user = (MsnUser *)l->data;
-
-		if (user->passport != NULL &&
-			!g_ascii_strcasecmp(passport, user->passport)) {
-
-			return user;
-		}
-	}
-
-	return NULL;
-}
--- a/src/protocols/msn/user.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/user.h	Sun Jun 06 02:39:08 2004 +0000
@@ -23,49 +23,44 @@
 #define _MSN_USER_H_
 
 typedef struct _MsnUser  MsnUser;
-typedef struct _MsnUsers MsnUsers;
 
 #include "session.h"
-#include "msnobject.h"
+#include "object.h"
+
+#include "userlist.h"
 
 /**
  * A user.
  */
 struct _MsnUser
 {
-	MsnSession *session;    /**< The MSN session.             */
+#if 0
+	MsnSession *session;    /**< The MSN session.               */
+#endif
+	MsnUserList *userlist;
 
-	char *passport;         /**< The passport account.        */
-	char *name;             /**< The friendly name.           */
+	char *passport;         /**< The passport account.          */
+	char *store_name;       /**< The name stored in the server. */
+	char *friendly_name;    /**< The friendly name.             */
 
 	struct
 	{
-		char *home;         /**< Home phone number.           */
-		char *work;         /**< Work phone number.           */
-		char *mobile;       /**< Mobile phone number.         */
+		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.   */
+	gboolean authorized;    /**< Authorized to add this user.   */
+	gboolean mobile;        /**< Signed up with MSN Mobile.     */
 
-	GList *group_ids;       /**< The group IDs.               */
-
-	size_t ref_count;       /**< The reference count.         */
-
-	MsnObject *msnobj;      /**< The user's MSN Object.       */
+	GList *group_ids;       /**< The group IDs.                 */
 
-	GHashTable *clientcaps; /**< The client's capabilities.   */
-};
+	MsnObject *msnobj;      /**< The user's MSN Object.         */
 
-/**
- * A collection of users.
- */
-struct _MsnUsers
-{
-	size_t count; /**< The number of users. */
+	GHashTable *clientcaps; /**< The client's capabilities.     */
 
-	GList *users; /**< The list of users.   */
+	int list_op;
 };
 
 /**************************************************************************/
@@ -76,14 +71,14 @@
 /**
  * Creates a new user structure.
  *
- * @param session  The MSN session.
- * @param passport The initial passport.
- * @param name     The initial friendly name.
+ * @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(MsnSession *session, const char *passport,
-					  const char *name);
+MsnUser *msn_user_new(MsnUserList *userlist, const char *passport,
+					  const char *store_name);
 
 /**
  * Destroys a user structure.
@@ -93,26 +88,6 @@
 void msn_user_destroy(MsnUser *user);
 
 /**
- * Increments the reference count on a user.
- *
- * @param user The user.
- *
- * @return @a user
- */
-MsnUser *msn_user_ref(MsnUser *user);
-
-/**
- * Decrements the reference count on a user.
- *
- * This will destroy the structure if the count hits 0.
- *
- * @param user The user.
- *
- * @return @a user, or @c NULL if the new count is 0.
- */
-MsnUser *msn_user_unref(MsnUser *user);
-
-/**
  * Sets the passport account for a user.
  *
  * @param user     The user.
@@ -126,7 +101,15 @@
  * @param user The user.
  * @param name The friendly name.
  */
-void msn_user_set_name(MsnUser *user, const char *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.
@@ -217,16 +200,16 @@
  *
  * @return The friendly name.
  */
-const char *msn_user_get_name(const MsnUser *user);
+const char *msn_user_get_friendly_name(const MsnUser *user);
 
 /**
- * Returns the group IDs for a user.
+ * Returns the store name for a user.
  *
  * @param user The user.
  *
- * @return The group IDs.
+ * @return The store name.
  */
-GList *msn_user_get_group_ids(const MsnUser *user);
+const char *msn_user_get_store_name(const MsnUser *user);
 
 /**
  * Returns the home phone number for a user.
@@ -275,60 +258,4 @@
 
 /*@}*/
 
-/**************************************************************************/
-/** @name User List API                                                   */
-/**************************************************************************/
-/*@{*/
-
-/**
- * Creates a new MsnUsers structure.
- *
- * @return A new MsnUsers structure.
- */
-MsnUsers *msn_users_new(void);
-
-/**
- * Destroys a users list.
- *
- * @param users The users list.
- */
-void msn_users_destroy(MsnUsers *users);
-
-/**
- * Adds a user to a users list.
- *
- * @param users The users list.
- * @param user  The user.
- */
-void msn_users_add(MsnUsers *users, MsnUser *user);
-
-/**
- * Removes a user from a users list.
- *
- * @param users The users list.
- * @param user  The user.
- */
-void msn_users_remove(MsnUsers *users, MsnUser *user);
-
-/**
- * Returns the number of users in a users list.
- *
- * @param users The users list.
- *
- * @return The number of users.
- */
-size_t msn_users_get_count(const MsnUsers *users);
-
-/**
- * Finds a user with the specified passport.
- *
- * @param users    A list of users.
- * @param passport The passport.
- *
- * @return The user if found, or @c NULL otherwise.
- */
-MsnUser *msn_users_find_with_passport(MsnUsers *users, const char *passport);
-
-/*@}*/
-
 #endif /* _MSN_USER_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/userlist.c	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,603 @@
+#include "msn.h"
+#include "userlist.h"
+
+const char *lists[] = { "FL", "AL", "BL", "RL" };
+
+typedef struct
+{
+	GaimConnection *gc;
+	char *who;
+
+} MsnPermitAdd;
+
+/**************************************************************************
+ * Callbacks
+ **************************************************************************/
+static void
+msn_accept_add_cb(MsnPermitAdd *pa)
+{
+	if (g_list_find(gaim_connections_get_all(), pa->gc) != NULL)
+	{
+		MsnSession *session = pa->gc->proto_data;
+		MsnUserList *userlist = session->userlist;
+
+		msn_userlist_add_buddy(userlist, pa->who, MSN_LIST_AL, NULL);
+
+		/* TODO: This ask for the alias, right? */
+		gaim_account_notify_added(pa->gc->account, NULL, pa->who, NULL, NULL);
+	}
+
+	g_free(pa->who);
+	g_free(pa);
+}
+
+static void
+msn_cancel_add_cb(MsnPermitAdd *pa)
+{
+	if (g_list_find(gaim_connections_get_all(), pa->gc) != NULL)
+	{
+		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);
+}
+
+static void
+got_new_entry(GaimConnection *gc, const char *passport,
+			  const char *friendly)
+{
+	MsnPermitAdd *pa;
+	char *msg;
+
+	pa      = g_new0(MsnPermitAdd, 1);
+	pa->who = g_strdup(passport);
+	pa->gc  = gc;
+
+	msg = g_strdup_printf(
+			   _("The user %s (%s) wants to add %s to his or her buddy list."),
+			   passport, friendly, gaim_account_get_username(gc->account));
+
+	gaim_request_action(gc, NULL, msg, NULL, 0, pa, 2,
+						_("Authorize"), G_CALLBACK(msn_accept_add_cb),
+						_("Deny"), G_CALLBACK(msn_cancel_add_cb));
+
+	g_free(msg);
+}
+
+/**************************************************************************
+ * 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);
+
+	if ((store_name = msn_user_get_store_name(user)) != NULL)
+		return gaim_url_encode(store_name);
+
+	return msn_user_get_passport(user);
+}
+
+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;
+
+	cmdproc = userlist->session->notification->cmdproc;
+	MsnMoveBuddy *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("ADG", "%s %d",
+								gaim_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)
+{
+	GaimAccount *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)
+	{
+		GaimConnection *gc = gaim_account_get_connection(account);
+
+		serv_got_alias(gc, passport, friendly);
+
+		if (group_id >= 0)
+		{
+			msn_user_add_group_id(user, group_id);
+			return;
+		}
+		else
+		{
+			/* session->sync->fl_users_count++; */
+		}
+	}
+	else if (list_id == MSN_LIST_AL)
+	{
+		gaim_privacy_permit_add(account, passport, TRUE);
+	}
+	else if (list_id == MSN_LIST_BL)
+	{
+		gaim_privacy_deny_add(account, passport, TRUE);
+	}
+	else if (list_id == MSN_LIST_RL)
+	{
+		GaimConnection *gc = gaim_account_get_connection(account);
+
+		gaim_debug_info("msn",
+						"%s has added you to his or her contact list.\n",
+						passport);
+
+		if (!(user->list_op & (MSN_LIST_AL_OP | MSN_LIST_BL_OP)))
+			got_new_entry(gc, passport, friendly);
+	}
+
+	user->list_op |= (1 << list_id);
+	/* gaim_user_add_list_id (user, list_id); */
+}
+
+void
+msn_got_rem_user(MsnSession *session, MsnUser *user,
+				 MsnListId list_id, int group_id)
+{
+	GaimAccount *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)
+	{
+		gaim_privacy_permit_remove(account, passport, TRUE);
+	}
+	else if (list_id == MSN_LIST_BL)
+	{
+		gaim_privacy_deny_remove(account, passport, TRUE);
+	}
+	else if (list_id == MSN_LIST_RL)
+	{
+		gaim_debug_info("msn",
+						"%s has removed you from his or her contact list.\n",
+						passport);
+	}
+
+	user->list_op &= ~(1 << list_id);
+	/* gaim_user_remove_list_id (user, list_id); */
+
+	if (user->list_op == 0)
+	{
+		gaim_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)
+{
+	GaimConnection *gc;
+	GaimAccount *account;
+	const char *passport;
+	const char *store;
+
+	account = session->account;
+	gc = gaim_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 */
+		/* serv_got_alias(gc, passport, store); */
+	}
+
+	if (list_op & MSN_LIST_AL_OP)
+	{
+		/* These are users who are allowed to see our status. */
+
+		if (g_slist_find_custom(account->deny, passport,
+								(GCompareFunc)strcmp))
+		{
+			gaim_privacy_deny_remove(gc->account, passport, TRUE);
+		}
+
+		gaim_privacy_permit_add(account, passport, TRUE);
+	}
+
+	if (list_op & MSN_LIST_BL_OP)
+	{
+		/* These are users who are not allowed to see our status. */
+
+		if (g_slist_find_custom(account->permit, passport,
+								(GCompareFunc)strcmp))
+		{
+			gaim_privacy_permit_remove(gc->account, passport, TRUE);
+		}
+
+		gaim_privacy_deny_add(account, passport, TRUE);
+	}
+
+	if (list_op & MSN_LIST_RL_OP)
+	{
+		/* These are users who have us on their contact list. */
+		/* TODO: what does store name is when this happens? */
+
+		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;
+
+	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);
+}
+
+void
+msn_userlist_add_user(MsnUserList *userlist, MsnUser *user)
+{
+	userlist->users = g_list_append(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);
+}
+
+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. */
+			gaim_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];
+		gaim_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 (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 alredy there. */
+	if (user_is_there(user, list_id, group_id))
+	{
+		list = lists[list_id];
+		gaim_debug_error("msn", "User '%s' is alredy 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_rem_buddy(userlist, who, MSN_LIST_FL, old_group_name);
+	msn_userlist_add_buddy(userlist, who, MSN_LIST_FL, new_group_name);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/userlist.h	Sun Jun 06 02:39:08 2004 +0000
@@ -0,0 +1,76 @@
+#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;
+
+	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_ */
--- a/src/protocols/msn/utils.c	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/utils.c	Sun Jun 06 02:39:08 2004 +0000
@@ -314,3 +314,24 @@
 	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;
+}
--- a/src/protocols/msn/utils.h	Sun Jun 06 02:16:08 2004 +0000
+++ b/src/protocols/msn/utils.h	Sun Jun 06 02:39:08 2004 +0000
@@ -44,4 +44,6 @@
  */
 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_ */