changeset 5309:e2e53316a21d

[gaim-migrate @ 5681] Announcing the new MSN prpl! It probably has some bugs, and for the time being, there is no file transfer. That's good though, because the current MSN file transfer is a little broken. I've had many corrupted files. I'll commit new file transfer code when it's written. I want this heavily tested before 0.63! If you use MSN, please talk to people on it. Let me know of any oddities, crashes, bugs, whatever. I'll fix things as I find them. committer: Tailor Script <tailor@pidgin.im>
author Christian Hammond <chipx86@chipx86.com>
date Tue, 06 May 2003 02:06:56 +0000
parents 6aa785e55d0f
children f4912a833ff6
files src/protocols/msn/Makefile.am src/protocols/msn/away.c src/protocols/msn/away.h src/protocols/msn/dispatch.c src/protocols/msn/dispatch.h src/protocols/msn/error.c src/protocols/msn/error.h src/protocols/msn/msg.c src/protocols/msn/msg.h src/protocols/msn/msn.c src/protocols/msn/msn.h src/protocols/msn/notification.c src/protocols/msn/notification.h src/protocols/msn/servconn.c src/protocols/msn/servconn.h src/protocols/msn/session.c src/protocols/msn/session.h src/protocols/msn/switchboard.c src/protocols/msn/switchboard.h src/protocols/msn/user.c src/protocols/msn/user.h src/protocols/msn/utils.c src/protocols/msn/utils.h
diffstat 23 files changed, 4971 insertions(+), 2154 deletions(-) [+]
line wrap: on
line diff
--- a/src/protocols/msn/Makefile.am	Tue May 06 00:34:54 2003 +0000
+++ b/src/protocols/msn/Makefile.am	Tue May 06 02:06:56 2003 +0000
@@ -3,13 +3,29 @@
 pkgdir = $(libdir)/gaim
 
 MSNSOURCES = \
-	ft.c \
+	away.c \
+	away.h \
+	dispatch.c \
+	dispatch.h \
+	error.c \
+	error.h \
+	md5.h \
 	msg.c \
 	msg.h \
 	msn.c \
 	msn.h \
+	notification.c \
+	notification.h \
+	servconn.c \
+	servconn.h \
+	session.c \
+	session.h \
 	switchboard.c \
-	switchboard.h
+	switchboard.h \
+	user.c \
+	user.h \
+	utils.c \
+	utils.h
 
 AM_CFLAGS = $(st)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/away.c	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,45 @@
+/**
+ * @file away.c Away functions
+ *
+ * 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 "away.h"
+
+static const char *away_text[] =
+{
+	N_("Available"),
+	N_("Available"),
+	N_("Busy"),
+	N_("Idle"),
+	N_("Be Right Back"),
+	N_("Away From Computer"),
+	N_("On The Phone"),
+	N_("Out To Lunch"),
+	N_("Available"),
+	N_("Available")
+};
+
+const char *
+msn_away_get_text(MsnAwayType type)
+{
+	g_return_val_if_fail(type >= 0 && type <= MSN_HIDDEN, NULL);
+
+	return away_text[type];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/away.h	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,51 @@
+/**
+ * @file away.h Away functions
+ *
+ * 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_AWAY_H_
+#define _MSN_AWAY_H_
+
+/**
+ * Away types.
+ */
+typedef enum
+{
+	MSN_ONLINE  = 1,
+	MSN_BUSY    = 2,
+	MSN_IDLE    = 3,
+	MSN_BRB     = 4,
+	MSN_AWAY    = 5,
+	MSN_PHONE   = 6,
+	MSN_LUNCH   = 7,
+	MSN_OFFLINE = 8,
+	MSN_HIDDEN  = 9
+
+} MsnAwayType;
+
+/**
+ * Returns the string representation of an away type.
+ *
+ * @param type The away type.
+ *
+ * @return The string representation of the away type.
+ */
+const char *msn_away_get_text(MsnAwayType type);
+
+#endif /* _MSN_AWAY_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/dispatch.c	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,228 @@
+/**
+ * @file dispatch.c Dispatch server functions
+ *
+ * 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 "dispatch.h"
+#include "notification.h"
+#include "error.h"
+
+static GHashTable *dispatch_commands = NULL;
+
+static gboolean
+__ver_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+	size_t i;
+	gboolean msnp5_found = FALSE;
+
+	for (i = 1; i < param_count; i++) {
+		if (!strcmp(params[i], "MSNP5")) {
+			msnp5_found = TRUE;
+			break;
+		}
+	}
+
+	if (!msnp5_found) {
+		hide_login_progress(gc, _("Protocol not supported"));
+		signoff(gc);
+
+		return FALSE;
+	}
+
+	if (!msn_servconn_send_command(servconn, "INF", NULL)) {
+		hide_login_progress(gc, _("Unable to request INF\n"));
+		signoff(gc);
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+__inf_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+	char outparams[MSN_BUF_LEN];
+
+	if (strcmp(params[1], "MD5")) {
+		hide_login_progress(gc, _("Unable to login using MD5"));
+		signoff(gc);
+
+		return FALSE;
+	}
+
+	g_snprintf(outparams, sizeof(outparams), "MD5 I %s", gc->username);
+
+	if (!msn_servconn_send_command(servconn, "USR", outparams)) {
+		hide_login_progress(gc, _("Unable to send USR\n"));
+		signoff(gc);
+
+		return FALSE;
+	}
+
+	set_login_progress(gc, 3, _("Requesting to send password"));
+
+	return TRUE;
+}
+
+static gboolean
+__xfr_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	MsnSession *session = servconn->session;
+	struct gaim_connection *gc = servconn->session->account->gc;
+	char *host;
+	int port;
+	char *c;
+
+	if (param_count < 2 || strcmp(params[1], "NS")) {
+		hide_login_progress(gc, _("Got invalid XFR\n"));
+		signoff(gc);
+
+		return FALSE;
+	}
+
+	host = g_strdup(params[2]);
+
+	if ((c = strchr(host, ':')) != NULL) {
+		*c = '\0';
+
+		port = atoi(c + 1);
+	}
+	else
+		port = 1863;
+
+	session->passport_info.sl = time(NULL);
+
+	/* Disconnect from here. */
+	msn_servconn_destroy(servconn);
+	session->dispatch_conn = NULL;
+
+	/* Now connect to the switchboard. */
+	session->notification_conn = msn_notification_new(session, host, port);
+
+	g_free(host);
+
+	if (!msn_servconn_connect(session->notification_conn)) {
+		hide_login_progress(gc, _("Unable to transfer"));
+		signoff(gc);
+	}
+
+	return FALSE;
+}
+
+static gboolean
+__unknown_cmd(MsnServConn *servconn, const char *command, const char **params,
+			  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+
+	if (isdigit(*command)) {
+		char buf[4];
+
+		strncpy(buf, command, 4);
+		buf[4] = '\0';
+
+		hide_login_progress(gc, (char *)msn_error_get_text(atoi(buf)));
+	}
+	else
+		hide_login_progress(gc, _("Unable to parse message."));
+
+	signoff(gc);
+
+	return FALSE;
+}
+
+static gboolean
+__connect_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnServConn *dispatch = data;
+	MsnSession *session = dispatch->session;
+	struct gaim_connection *gc = session->account->gc;
+
+	if (source == -1) {
+		hide_login_progress(session->account->gc, _("Unable to connect"));
+		signoff(session->account->gc);
+		return FALSE;
+	}
+
+	set_login_progress(gc, 1, _("Connecting"));
+
+	if (dispatch->fd != source)
+		dispatch->fd = source;
+
+	if (!msn_servconn_send_command(dispatch, "VER",
+								   "MSNP7 MSNP6 MSNP5 MSNP4 CVR0")) {
+		hide_login_progress(gc, _("Unable to write to server"));
+		signoff(gc);
+		return FALSE;
+	}
+
+	set_login_progress(session->account->gc, 2, _("Syncing with server"));
+
+	return TRUE;
+}
+
+static void
+__failed_read_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnServConn *dispatch = data;
+	struct gaim_connection *gc;
+
+	gc = dispatch->session->account->gc;
+
+	hide_login_progress(gc, _("Error reading from server"));
+	signoff(gc);
+}
+
+MsnServConn *
+msn_dispatch_new(MsnSession *session, const char *server, int port)
+{
+	MsnServConn *dispatch;
+
+	dispatch = msn_servconn_new(session);
+	
+	msn_servconn_set_server(dispatch, server, port);
+	msn_servconn_set_connect_cb(dispatch, __connect_cb);
+	msn_servconn_set_failed_read_cb(dispatch, __failed_read_cb);
+
+	if (dispatch_commands == NULL) {
+		/* Register the command callbacks. */
+		msn_servconn_register_command(dispatch, "VER",       __ver_cmd);
+		msn_servconn_register_command(dispatch, "INF",       __inf_cmd);
+		msn_servconn_register_command(dispatch, "XFR",       __xfr_cmd);
+		msn_servconn_register_command(dispatch, "_unknown_", __unknown_cmd);
+
+		/* Save this for future use. */
+		dispatch_commands = dispatch->commands;
+	}
+	else {
+		g_hash_table_destroy(dispatch->commands);
+
+		dispatch->commands = dispatch_commands;
+	}
+
+	return dispatch;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/dispatch.h	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,31 @@
+/**
+ * @file dispatch.h Dispatch server functions
+ *
+ * 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_DISPATCH_H_
+#define _MSN_DISPATCH_H_
+
+#include "session.h"
+#include "servconn.h"
+
+MsnServConn *msn_dispatch_new(MsnSession *session, const char *server,
+							  int port);
+
+#endif /* _MSN_DISPATCH_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/error.c	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,180 @@
+/**
+ * @file error.c Error functions
+ *
+ * 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"
+
+const char *
+msn_error_get_text(unsigned int type)
+{
+	static char msg[MSN_BUF_LEN];
+
+	switch (type) {
+		case 200:
+			g_snprintf(msg, sizeof(msg),
+					   _("Syntax Error (probably a Gaim bug)"));
+			break;
+		case 201:
+			g_snprintf(msg, sizeof(msg),
+					   _("Invalid Parameter (probably a Gaim bug)"));
+			break;
+		case 205:
+			g_snprintf(msg, sizeof(msg), _("Invalid User"));
+			break;
+		case 206:
+			g_snprintf(msg, sizeof(msg),
+					   _("Fully Qualified Domain Name missing"));
+			break;
+		case 207:
+			g_snprintf(msg, sizeof(msg), _("Already Login"));
+			break;
+		case 208:
+			g_snprintf(msg, sizeof(msg), _("Invalid Username"));
+			break;
+		case 209:
+			g_snprintf(msg, sizeof(msg), _("Invalid Friendly Name"));
+			break;
+		case 210:
+			g_snprintf(msg, sizeof(msg), _("List Full"));
+			break;
+		case 215:
+			g_snprintf(msg, sizeof(msg), _("Already there"));
+			break;
+		case 216:
+			g_snprintf(msg, sizeof(msg), _("Not on list"));
+			break;
+		case 217:
+			g_snprintf(msg, sizeof(msg), _("User is offline"));
+			break;
+		case 218:
+			g_snprintf(msg, sizeof(msg), _("Already in the mode"));
+			break;
+		case 219:
+			g_snprintf(msg, sizeof(msg), _("Already in opposite list"));
+			break;
+		case 231:
+			g_snprintf(msg, sizeof(msg),
+					   _("Tried to add a contact to a group "
+						 "that doesn't exist"));
+			break;
+		case 280:
+			g_snprintf(msg, sizeof(msg), _("Switchboard failed"));
+			break;
+		case 281:
+			g_snprintf(msg, sizeof(msg), _("Notify Transfer failed"));
+			break;
+
+		case 300:
+			g_snprintf(msg, sizeof(msg), _("Required fields missing"));
+			break;
+		case 302:
+			g_snprintf(msg, sizeof(msg), _("Not logged in"));
+			break;
+
+		case 500:
+			g_snprintf(msg, sizeof(msg), _("Internal server error"));
+			break;
+		case 501:
+			g_snprintf(msg, sizeof(msg), _("Database server error"));
+			break;
+		case 510:
+			g_snprintf(msg, sizeof(msg), _("File operation error"));
+			break;
+		case 520:
+			g_snprintf(msg, sizeof(msg), _("Memory allocation error"));
+			break;
+		case 540:
+			g_snprintf(msg, sizeof(msg), _("Wrong CHL value sent to server"));
+			break;
+
+		case 600:
+			g_snprintf(msg, sizeof(msg), _("Server busy"));
+			break;
+		case 601:
+			g_snprintf(msg, sizeof(msg), _("Server unavailable"));
+			break;
+		case 602:
+			g_snprintf(msg, sizeof(msg), _("Peer Notification server down"));
+			break;
+		case 603:
+			g_snprintf(msg, sizeof(msg), _("Database connect error"));
+			break;
+		case 604:
+			g_snprintf(msg, sizeof(msg),
+					   _("Server is going down (abandon ship)"));
+			break;
+
+		case 707:
+			g_snprintf(msg, sizeof(msg), _("Error creating connection"));
+			break;
+		case 710:
+			g_snprintf(msg, sizeof(msg),
+					   _("CVR parameters are either unknown or not allowed"));
+			break;
+		case 711:
+			g_snprintf(msg, sizeof(msg), _("Unable to write"));
+			break;
+		case 712:
+			g_snprintf(msg, sizeof(msg), _("Session overload"));
+			break;
+		case 713:
+			g_snprintf(msg, sizeof(msg), _("User is too active"));
+			break;
+		case 714:
+			g_snprintf(msg, sizeof(msg), _("Too many sessions"));
+			break;
+		case 715:
+			g_snprintf(msg, sizeof(msg), _("Not expected"));
+			break;
+		case 717:
+			g_snprintf(msg, sizeof(msg), _("Bad friend file"));
+			break;
+
+		case 911:
+			g_snprintf(msg, sizeof(msg), _("Authentication failed"));
+			break;
+		case 913:
+			g_snprintf(msg, sizeof(msg), _("Not allowed when offline"));
+			break;
+        case 920:
+			g_snprintf(msg, sizeof(msg), _("Not accepting new users"));
+			break;
+        case 924:
+			g_snprintf(msg, sizeof(msg),
+					   _("Passport account not yet verified"));
+			break;
+
+		default:
+			g_snprintf(msg, sizeof(msg), _("Unknown Error Code %d"), type);
+			break;
+	}
+
+	return msg;
+}
+
+void
+msn_error_handle(unsigned int type)
+{
+	const char *text;
+
+	text = msn_error_get_text(type);
+
+	do_error_dialog(text, NULL, GAIM_ERROR);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/error.h	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,41 @@
+/**
+ * @file error.h Error functions
+ *
+ * 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_ERROR_H_
+#define _MSN_ERROR_H_
+
+/**
+ * Returns the string representation of an error type.
+ *
+ * @param type The error type.
+ *
+ * @return The string representation of the error type.
+ */
+const char *msn_error_get_text(unsigned int type);
+
+/**
+ * Handles an error.
+ *
+ * @param type The error type.
+ */
+void msn_error_handle(unsigned int type);
+
+#endif /* _MSN_ERROR_H_ */
--- a/src/protocols/msn/msg.c	Tue May 06 00:34:54 2003 +0000
+++ b/src/protocols/msn/msg.c	Tue May 06 02:06:56 2003 +0000
@@ -3,7 +3,7 @@
  *
  * gaim
  *
- * Copyright (C) 2003, Christian Hammond <chipx86@gnupdate.org>
+ * 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
@@ -18,15 +18,502 @@
  * 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 "msg.h"
 
-int
-msn_write(int fd, void *data, int len)
+#define GET_NEXT(tmp) \
+	while (*(tmp) && *(tmp) != ' ' && *(tmp) != '\r') \
+		(tmp)++; \
+	*(tmp)++ = '\0'; \
+	if (*(tmp) == '\n') *(tmp)++; \
+	while (*(tmp) && *(tmp) == ' ') \
+		(tmp)++
+
+#define GET_NEXT_LINE(tmp) \
+	while (*(tmp) && *(tmp) != '\r') \
+		(tmp)++; \
+	*(tmp)++ = '\0'; \
+	if (*(tmp) == '\n') *(tmp)++
+
+/*
+ * "MIME-Version: 1.0\r\n" == 19
+ * "Content-Type: "        == 14
+ * "\r\n"                  ==  2
+ * "\r\n" before body      ==  2
+ *                           ----
+ *                            37
+ *  MATH PAYS OFF!!
+ */
+#define MSN_MESSAGE_BASE_SIZE 37
+
+MsnMessage *
+msn_message_new(void)
+{
+	MsnMessage *msg;
+
+	msg = g_new0(MsnMessage, 1);
+
+	msg->attr_table = g_hash_table_new_full(g_str_hash, g_str_equal,
+											g_free, g_free);
+	msg->size = MSN_MESSAGE_BASE_SIZE;
+
+	msn_message_set_attr(msg, "User-Agent", "Gaim/" VERSION);
+	msn_message_set_content_type(msg, "text/plain");
+	msn_message_set_charset(msg, "UTF-8");
+	msn_message_set_flag(msg, 'N');
+
+	return msg;
+}
+
+MsnMessage *
+msn_message_new_from_str(MsnSession *session, const char *str)
 {
-	gaim_debug(GAIM_DEBUG_MISC, "msn", "C: %s", (char *)data);
+	MsnMessage *msg;
+	char *tmp, *field1, *field2, *c;
+
+	g_return_val_if_fail(str != NULL, NULL);
+	g_return_val_if_fail(!g_ascii_strncasecmp(str, "MSG", 3), NULL);
+
+	msg = msn_message_new();
+
+	tmp = g_strdup(str);
+
+	GET_NEXT(tmp); /* Skip MSG */
+	field1 = tmp;
+
+	GET_NEXT(tmp); /* Skip the passport or TID */
+	field2 = tmp;
+
+	GET_NEXT(tmp); /* Skip the username or flag */
+	msg->size = atoi(tmp);
+
+	if (msg->size != strlen(strchr(str, '\n') + 1)) {
+		gaim_debug(GAIM_DEBUG_ERROR, "msn",
+				   "Message size (%d) and string length (%d) "
+				   "do not match!\n", msg->size, strlen(str));
+	}
+
+	/*
+	 * We're going to make sure this is incoming by checking field1.
+	 * If it has any non-numbers in it, it's incoming. Otherwise, outgoing.
+	 */
+	msg->incoming = FALSE;
+
+	for (c = field1; *c != '\0'; c++) {
+		if (*c < '0' || *c > '9') {
+			msg->incoming = TRUE;
+			break;
+		}
+	}
+
+	if (msg->incoming) {
+		msg->sender = msn_users_find_with_passport(session->users, field1);
+
+		gaim_debug(GAIM_DEBUG_MISC, "msn", "incoming message: %s, %s\n",
+				   field1, field2);
+		if (msg->sender == NULL)
+			msg->sender = msn_user_new(session, field1, field2);
+		else
+			msn_user_ref(msg->sender);
+	}
+	else {
+		msg->tid  = atoi(field1);
+		msg->flag = *field2;
+	}
+
+	/* Back to the parsination. */
+	while (*tmp != '\r') {
+		char *key, *value;
+
+		key = tmp;
+
+		GET_NEXT(tmp); /* Key */
+
+		value = tmp;
+
+		GET_NEXT_LINE(tmp); /* Value */
+
+		if ((c = strchr(key, ':')) != NULL)
+			*c = '\0';
+
+		if (!g_ascii_strcasecmp(key, "Content-Type")) {
+			char *charset;
+
+			if ((c = strchr(value, ';')) != NULL) {
+				if ((charset = strchr(c, '=')) != NULL) {
+					charset++;
+					msn_message_set_charset(msg, charset);
+				}
+
+				*c = '\0';
+			}
+
+			msn_message_set_content_type(msg, value);
+		}
+		else
+			msn_message_set_attr(msg, key, value);
+	}
+
+	/* "\r\n" */
+	tmp += 2;
+
+	/* Now we *should* be at the body. */
+	msn_message_set_body(msg, tmp);
+
+	/* Done! */
+
+	return msg;
+}
 
-	return write(fd, data, len);
+void
+msn_message_destroy(MsnMessage *msg)
+{
+	g_return_if_fail(msg != NULL);
+
+	if (msg->sender != NULL)
+		msn_user_unref(msg->sender);
+
+	if (msg->receiver != NULL)
+		msn_user_unref(msg->receiver);
+
+	if (msg->body != NULL)
+		g_free(msg->body);
+
+	if (msg->content_type != NULL)
+		g_free(msg->content_type);
+
+	if (msg->charset != NULL)
+		g_free(msg->charset);
+
+	g_hash_table_destroy(msg->attr_table);
+	g_list_free(msg->attr_list);
+
+	g_free(msg);
+}
+
+char *
+msn_message_build_string(const MsnMessage *msg)
+{
+	GList *l;
+	char *str;
+	char buf[MSN_BUF_LEN];
+	int len;
+
+	/*
+	 * Okay, how we do things here is just bad. I don't like writing to
+	 * a static buffer and then copying to the string. Unfortunately,
+	 * just trying to append to the string is causing issues.. Such as
+	 * the string you're appending to being erased. Ugh. So, this is
+	 * good enough for now.
+	 *
+	 *     -- ChipX86
+	 */
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	if (msn_message_is_incoming(msg)) {
+		MsnUser *sender = msn_message_get_sender(msg);
+
+		g_snprintf(buf, sizeof(buf), "MSG %s %s %d\r\n",
+				   msn_user_get_passport(sender), msn_user_get_name(sender),
+				   msg->size);
+	}
+	else {
+		g_snprintf(buf, sizeof(buf), "MSG %d %c %d\r\n",
+				   msn_message_get_transaction_id(msg),
+				   msn_message_get_flag(msg), msg->size);
+	}
+
+	len = strlen(buf) + msg->size + 1;
+
+	str = g_new0(char, len);
+
+	g_strlcpy(str, buf, len);
+
+	/* Standard header. */
+	if (msg->charset == NULL) {
+		g_snprintf(buf, sizeof(buf),
+				   "MIME-Version: 1.0\r\n"
+				   "Content-Type: %s\r\n",
+				   msg->content_type);
+	}
+	else {
+		g_snprintf(buf, sizeof(buf),
+				   "MIME-Version: 1.0\r\n"
+				   "Content-Type: %s; charset=%s\r\n",
+				   msg->content_type, msg->charset);
+	}
+
+	g_strlcat(str, buf, len);
+
+	for (l = msg->attr_list; l != NULL; l = l->next) {
+		const char *key = (char *)l->data;
+		const char *value;
+
+		value = msn_message_get_attr(msg, key);
+
+		g_snprintf(buf, sizeof(buf), "%s: %s\r\n", key, value);
+
+		g_strlcat(str, buf, len);
+	}
+
+	g_snprintf(buf, sizeof(buf), "\r\n%s", msn_message_get_body(msg));
+
+	g_strlcat(str, buf, len);
+
+	return str;
 }
 
+gboolean
+msn_message_is_outgoing(const MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, FALSE);
+
+	return !msg->incoming;
+}
+
+gboolean
+msn_message_is_incoming(const MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, FALSE);
+
+	return msg->incoming;
+}
+
+void
+msn_message_set_sender(MsnMessage *msg, MsnUser *user)
+{
+	g_return_if_fail(msg != NULL);
+	g_return_if_fail(user != NULL);
+
+	msg->sender = user;
+	
+	msn_user_ref(msg->sender);
+}
+
+MsnUser *
+msn_message_get_sender(const MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	return msg->sender;
+}
+
+void
+msn_message_set_receiver(MsnMessage *msg, MsnUser *user)
+{
+	g_return_if_fail(msg != NULL);
+	g_return_if_fail(user != NULL);
+
+	msg->receiver = user;
+	
+	msn_user_ref(msg->receiver);
+}
+
+MsnUser *
+msn_message_get_receiver(const MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	return msg->receiver;
+}
+
+void
+msn_message_set_transaction_id(MsnMessage *msg, unsigned int tid)
+{
+	g_return_if_fail(msg != NULL);
+	g_return_if_fail(tid > 0);
+
+	msg->tid = tid;
+}
+
+unsigned int
+msn_message_get_transaction_id(const MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, 0);
+
+	return msg->tid;
+}
+
+void
+msn_message_set_flag(MsnMessage *msg, char flag)
+{
+	g_return_if_fail(msg != NULL);
+	g_return_if_fail(flag != 0);
+
+	msg->flag = flag;
+}
+
+char
+msn_message_get_flag(const MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, 0);
+
+	return msg->flag;
+}
+
+void
+msn_message_set_body(MsnMessage *msg, const char *body)
+{
+	g_return_if_fail(msg != NULL);
+	g_return_if_fail(body != NULL);
+
+	if (msg->body != NULL) {
+		msg->size -= strlen(msg->body);
+		g_free(msg->body);
+	}
+
+	msg->body = g_strdup(body);
+
+	msg->size += strlen(body);
+}
+
+const char *
+msn_message_get_body(const MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	return msg->body;
+}
+
+void
+msn_message_set_content_type(MsnMessage *msg, const char *type)
+{
+	g_return_if_fail(msg != NULL);
+	g_return_if_fail(type != NULL);
+
+	if (msg->content_type != NULL) {
+		msg->size -= strlen(msg->content_type);
+		g_free(msg->content_type);
+	}
+
+	msg->content_type = g_strdup(type);
+
+	msg->size += strlen(type);
+}
+
+const char *
+msn_message_get_content_type(const MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	return msg->content_type;
+}
+
+void
+msn_message_set_charset(MsnMessage *msg, const char *charset)
+{
+	g_return_if_fail(msg != NULL);
+
+	if (msg->charset != NULL) {
+		msg->size -= strlen(msg->charset) + strlen("; charset=");
+		g_free(msg->charset);
+	}
+
+	if (charset != NULL) {
+		msg->charset = g_strdup(charset);
+
+		msg->size += strlen(charset) + strlen("; charset=");
+	}
+	else
+		msg->charset = NULL;
+}
+
+const char *
+msn_message_get_charset(const MsnMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	return msg->charset;
+}
+
+void
+msn_message_set_attr(MsnMessage *msg, const char *attr, const char *value)
+{
+	const char *temp;
+	char *new_attr;
+
+	g_return_if_fail(msg != NULL);
+	g_return_if_fail(attr != NULL);
+
+	temp = msn_message_get_attr(msg, attr);
+
+	if (value == NULL) {
+		if (temp != NULL) {
+			GList *l;
+
+			for (l = msg->attr_list; l != NULL; l = l->next) {
+				if (!g_ascii_strcasecmp(l->data, attr)) {
+					msg->attr_list = g_list_remove(msg->attr_list, l->data);
+
+					break;
+				}
+			}
+
+			g_hash_table_remove(msg->attr_table, attr);
+
+			msg->size -= strlen(temp) + strlen(attr) + 4;
+		}
+
+		return;
+	}
+
+	new_attr = g_strdup(attr);
+
+	g_hash_table_insert(msg->attr_table, new_attr, g_strdup(value));
+
+	if (temp == NULL) {
+		msg->attr_list = g_list_append(msg->attr_list, new_attr);
+		msg->size += strlen(attr) + 4;
+	}
+	else
+		msg->size -= strlen(temp);
+
+	msg->size += strlen(value);
+}
+
+const char *
+msn_message_get_attr(const MsnMessage *msg, const char *attr)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+	g_return_val_if_fail(attr != NULL, NULL);
+
+	return g_hash_table_lookup(msg->attr_table, attr);
+}
+
+GHashTable *
+msn_message_get_hashtable_from_body(const MsnMessage *msg)
+{
+	GHashTable *table;
+	char *body, *s, *c;
+
+	g_return_val_if_fail(msg != NULL, NULL);
+	g_return_val_if_fail(msn_message_get_body(msg) != NULL, NULL);
+
+	s = body = g_strdup(msn_message_get_body(msg));
+
+	table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+	while (*s != '\r') {
+		char *key, *value;
+
+		key = s;
+
+		GET_NEXT(s);
+
+		value = s;
+
+		GET_NEXT_LINE(s);
+
+		if ((c = strchr(key, ':')) != NULL) {
+			*c = '\0';
+
+			g_hash_table_insert(table, g_strdup(key), g_strdup(value));
+		}
+	}
+
+	g_free(body);
+
+	return table;
+}
+
--- a/src/protocols/msn/msg.h	Tue May 06 00:34:54 2003 +0000
+++ b/src/protocols/msn/msg.h	Tue May 06 02:06:56 2003 +0000
@@ -3,7 +3,7 @@
  *
  * gaim
  *
- * Copyright (C) 2003, Christian Hammond <chipx86@gnupdate.org>
+ * 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
@@ -18,20 +18,233 @@
  * 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_MSG_H_
 #define _MSN_MSG_H_
 
+typedef struct _MsnMessage MsnMessage;
+
+#include "session.h"
+#include "user.h"
+
 /**
- * Writes a message to the server.
+ * A message.
+ */
+struct _MsnMessage
+{
+	MsnUser *sender;
+	MsnUser *receiver;
+
+	unsigned int tid;
+	char flag;
+
+	gboolean incoming;
+
+	size_t size;
+
+	char *content_type;
+	char *charset;
+	char *body;
+
+	GHashTable *attr_table;
+	GList *attr_list;
+};
+
+/**
+ * Creates a new, empty message.
+ *
+ * @return A new message.
+ */
+MsnMessage *msn_message_new(void);
+
+/**
+ * Creates a new message based off a string.
+ *
+ * @param session The MSN session.
+ * @param str     The string.
+ *
+ * @return The new message.
+ */
+MsnMessage *msn_message_new_from_str(MsnSession *session, const char *str);
+
+/**
+ * Destroys a message.
+ */
+void msn_message_destroy(MsnMessage *msg);
+
+/**
+ * Converts a message to a string.
+ *
+ * @param msg The message.
+ *
+ * @return The string representation of a message.
+ */
+char *msn_message_build_string(const MsnMessage *msg);
+
+/**
+ * Returns TRUE if the message is outgoing.
+ *
+ * @param msg The message.
+ *
+ * @return @c TRUE if the message is outgoing, or @c FALSE otherwise.
+ */
+gboolean msn_message_is_outgoing(const MsnMessage *msg);
+
+/**
+ * Returns TRUE if the message is incoming.
+ *
+ * @param msg The message.
+ *
+ * @return @c TRUE if the message is incoming, or @c FALSE otherwise.
+ */
+gboolean msn_message_is_incoming(const MsnMessage *msg);
+
+/**
+ * Sets the message's sender.
+ *
+ * @param msg  The message.
+ * @param user The sender.
+ */
+void msn_message_set_sender(MsnMessage *msg, MsnUser *user);
+
+/**
+ * Returns the message's sender.
+ *
+ * @param msg The message.
+ *
+ * @return The sender.
+ */
+MsnUser *msn_message_get_sender(const MsnMessage *msg);
+
+/**
+ * Sets the message's receiver.
+ *
+ * @param msg  The message.
+ * @param user The receiver.
+ */
+void msn_message_set_receiver(MsnMessage *msg, MsnUser *user);
+
+/**
+ * Returns the message's receiver.
+ *
+ * @param msg The message.
  *
- * @param fd   The file descriptor.
- * @param data The data to write.
- * @param len  The length of the data
+ * @return The receiver.
+ */
+MsnUser *msn_message_get_receiver(const MsnMessage *msg);
+
+/**
+ * Sets the message transaction ID.
+ *
+ * @param msg The message.
+ * @param tid The transaction ID.
+ */
+void msn_message_set_transaction_id(MsnMessage *msg, unsigned int tid);
+
+/**
+ * Returns the message transaction ID.
+ *
+ * @param msg The message.
+ *
+ * @return The transaction ID.
+ */
+unsigned int msn_message_get_transaction_id(const MsnMessage *msg);
+
+/**
+ * Sets the flag for an outgoing message.
+ *
+ * @param msg  The message.
+ * @param flag The flag.
+ */
+void msn_message_set_flag(MsnMessage *msg, char flag);
+
+/**
+ * Returns the flag for an outgoing message.
+ *
+ * @param msg The message.
+ *
+ * @return The flag.
+ */
+char msn_message_get_flag(const MsnMessage *msg);
+
+/**
+ * Sets the body of a message.
+ *
+ * @param msg  The message.
+ * @param body The body of the message.
+ */
+void msn_message_set_body(MsnMessage *msg, const char *body);
+
+/**
+ * Returns the body of the message.
+ *
+ * @param msg The message.
+ *
+ * @return The body of the message.
+ */
+const char *msn_message_get_body(const MsnMessage *msg);
+
+/**
+ * Sets the content type in a message.
  *
- * @return The number of bytes written.
+ * @param msg  The message.
+ * @param type The content-type.
+ */
+void msn_message_set_content_type(MsnMessage *msg, const char *type);
+
+/**
+ * Returns the content type in a message.
+ *
+ * @param msg The message.
+ *
+ * @return The content-type.
+ */
+const char *msn_message_get_content_type(const MsnMessage *msg);
+
+/**
+ * Sets the charset in a message.
+ *
+ * @param msg     The message.
+ * @param charset The charset.
+ */
+void msn_message_set_charset(MsnMessage *msg, const char *charset);
+
+/**
+ * Returns the charset in a message.
+ *
+ * @param msg The message.
+ *
+ * @return The charset.
  */
-int msn_write(int fd, void *data, int len);
+const char *msn_message_get_charset(const MsnMessage *msg);
+
+/**
+ * Sets an attribute in a message.
+ *
+ * @param msg   The message.
+ * @param attr  The attribute name.
+ * @param value The attribute value.
+ */
+void msn_message_set_attr(MsnMessage *msg, const char *attr,
+						  const char *value);
+
+/**
+ * Returns an attribute from a message.
+ *
+ * @param msg  The message.
+ * @param attr The attribute.
+ *
+ * @return The value, or @c NULL if not found.
+ */
+const char *msn_message_get_attr(const MsnMessage *msg, const char *attr);
+
+/**
+ * Parses the body and returns it in the form of a hashtable.
+ *
+ * @param msg The message.
+ *
+ * @return The resulting hashtable.
+ */
+GHashTable *msn_message_get_hashtable_from_body(const MsnMessage *msg);
 
 #endif /* _MSN_MSG_H_ */
--- a/src/protocols/msn/msn.c	Tue May 06 00:34:54 2003 +0000
+++ b/src/protocols/msn/msn.c	Tue May 06 02:06:56 2003 +0000
@@ -3,7 +3,7 @@
  *
  * gaim
  *
- * Parts Copyright (C) 2003, Christian Hammond <chipx86@gnupdate.org>
+ * 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
@@ -18,13 +18,12 @@
  * 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"
-
-#ifdef _WIN32
-#include "win32dep.h"
-#endif
+#include "away.h"
+#include "msg.h"
+#include "session.h"
+#include "utils.h"
 
 #define BUDDY_ALIAS_MAXLEN 388
 
@@ -33,1320 +32,92 @@
 /* for win32 compatability */
 G_MODULE_IMPORT GSList *connections;
 
-static void msn_login_callback(gpointer, gint, GaimInputCondition);
-static void msn_login_xfr_connect(gpointer, gint, GaimInputCondition);
-
-static char *msn_normalize(const char *s)
-{
-	static char buf[BUF_LEN];
-
-	g_return_val_if_fail(s != NULL, NULL);
-
-	g_snprintf(buf, sizeof(buf), "%s%s", s, strchr(s, '@') ? "" : "@hotmail.com");
-
-	return buf;
-}
-
-char *
-handle_errcode(char *buf, gboolean show)
-{
-	int errcode;
-	static char msg[MSN_BUF_LEN];
-
-	buf[4] = 0;
-	errcode = atoi(buf);
-
-	switch (errcode) {
-		case 200:
-			g_snprintf(msg, sizeof(msg), _("Syntax Error (probably a Gaim bug)"));
-			break;
-		case 201:
-			g_snprintf(msg, sizeof(msg), _("Invalid Parameter (probably a Gaim bug)"));
-			break;
-		case 205:
-			g_snprintf(msg, sizeof(msg), _("Invalid User"));
-			break;
-		case 206:
-			g_snprintf(msg, sizeof(msg), _("Fully Qualified Domain Name missing"));
-			break;
-		case 207:
-			g_snprintf(msg, sizeof(msg), _("Already Login"));
-			break;
-		case 208:
-			g_snprintf(msg, sizeof(msg), _("Invalid Username"));
-			break;
-		case 209:
-			g_snprintf(msg, sizeof(msg), _("Invalid Friendly Name"));
-			break;
-		case 210:
-			g_snprintf(msg, sizeof(msg), _("List Full"));
-			break;
-		case 215:
-			g_snprintf(msg, sizeof(msg), _("Already there"));
-			break;
-		case 216:
-			g_snprintf(msg, sizeof(msg), _("Not on list"));
-			break;
-		case 217:
-			g_snprintf(msg, sizeof(msg), _("User is offline"));
-			break;
-		case 218:
-			g_snprintf(msg, sizeof(msg), _("Already in the mode"));
-			break;
-		case 219:
-			g_snprintf(msg, sizeof(msg), _("Already in opposite list"));
-			break;
-		case 280:
-			g_snprintf(msg, sizeof(msg), _("Switchboard failed"));
-			break;
-		case 281:
-			g_snprintf(msg, sizeof(msg), _("Notify Transfer failed"));
-			break;
-
-		case 300:
-			g_snprintf(msg, sizeof(msg), _("Required fields missing"));
-			break;
-		case 302:
-			g_snprintf(msg, sizeof(msg), _("Not logged in"));
-			break;
-
-		case 500:
-			g_snprintf(msg, sizeof(msg), _("Internal server error"));
-			break;
-		case 501:
-			g_snprintf(msg, sizeof(msg), _("Database server error"));
-			break;
-		case 510:
-			g_snprintf(msg, sizeof(msg), _("File operation error"));
-			break;
-		case 520:
-			g_snprintf(msg, sizeof(msg), _("Memory allocation error"));
-			break;
-
-		case 600:
-			g_snprintf(msg, sizeof(msg), _("Server busy"));
-			break;
-		case 601:
-			g_snprintf(msg, sizeof(msg), _("Server unavailable"));
-			break;
-		case 602:
-			g_snprintf(msg, sizeof(msg), _("Peer Notification server down"));
-			break;
-		case 603:
-			g_snprintf(msg, sizeof(msg), _("Database connect error"));
-			break;
-		case 604:
-			g_snprintf(msg, sizeof(msg), _("Server is going down (abandon ship)"));
-			break;
-
-		case 707:
-			g_snprintf(msg, sizeof(msg), _("Error creating connection"));
-			break;
-		case 711:
-			g_snprintf(msg, sizeof(msg), _("Unable to write"));
-			break;
-		case 712:
-			g_snprintf(msg, sizeof(msg), _("Session overload"));
-			break;
-		case 713:
-			g_snprintf(msg, sizeof(msg), _("User is too active"));
-			break;
-		case 714:
-			g_snprintf(msg, sizeof(msg), _("Too many sessions"));
-			break;
-		case 715:
-			g_snprintf(msg, sizeof(msg), _("Not expected"));
-			break;
-		case 717:
-			g_snprintf(msg, sizeof(msg), _("Bad friend file"));
-			break;
-
-		case 911:
-			g_snprintf(msg, sizeof(msg), _("Authentication failed"));
-			break;
-		case 913:
-			g_snprintf(msg, sizeof(msg), _("Not allowed when offline"));
-			break;
-	        case 920:
-			g_snprintf(msg, sizeof(msg), _("Not accepting new users"));
-			break;
-	        case 924:
-			g_snprintf(msg, sizeof(msg), _("User unverified"));
-			break;
-		default:
-			g_snprintf(msg, sizeof(msg), _("Unknown Error Code"));
-			break;
-	}
-
-	if (show)
-		do_error_dialog(msg, NULL, GAIM_ERROR);
-
-	return msg;
-}
-
-
-char *url_decode(const char *msg)
-{
-	static char buf[MSN_BUF_LEN];
-	int i, j = 0;
-	char *bum;
-
-	bzero(buf, sizeof(buf));
-	for (i = 0; i < strlen(msg); i++) {
-		char hex[3];
-		if (msg[i] != '%') {
-			buf[j++] = msg[i];
-			continue;
-		}
-		strncpy(hex, msg + ++i, 2); hex[2] = 0;
-		/* i is pointing to the start of the number */
-		i++; /* now it's at the end and at the start of the for loop
-			will be at the next character */
-		buf[j++] = strtol(hex, NULL, 16);
-	}
-	buf[j] = 0;
-
-	if(!g_utf8_validate(buf, -1, (const char **)&bum))
-			*bum = '\0';
-
-	return buf;
-}
-
-static char *url_encode(const char *msg)
-{
-	static char buf[MSN_BUF_LEN];
-	int i, j = 0;
-
-	bzero(buf, sizeof(buf));
-	for (i = 0; i < strlen(msg); i++) {
-		if (isalnum(msg[i]))
-			buf[j++] = msg[i];
-		else {
-			sprintf(buf + j, "%%%02x", (unsigned char)msg[i]);
-			j += 3;
-		}
-	}
-	buf[j] = 0;
-
-	return buf;
-}
-
-static void handle_hotmail(struct gaim_connection *gc, char *data)
-{
-	char login_url[2048];
-	char buf[MSN_BUF_LEN];
-	struct msn_data *md = gc->proto_data;
-
-	if (strchr(gc->username, '@') != strstr(gc->username, "@hotmail.com"))
-		/* We can only get Hotmail notification from hotmail users */
-		return;
-
-	if (!md->passport) {
-		g_snprintf(buf, sizeof(buf), "URL %u INBOX\r\n", ++md->trId);
-
-		if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-			return;
-		}
-	} else {
-		g_snprintf(login_url, sizeof(login_url), "%s", md->passport);
-
-		if (strstr(data, "Content-Type: text/x-msmsgsinitialemailnotification;")) {
-			char *x = strstr(data, "Inbox-Unread:");
-			if (!x) return;
-			x += strlen("Inbox-Unread: ");
-			connection_has_mail(gc, atoi(x), NULL, NULL, login_url);
-		} else if (strstr(data, "Content-Type: text/x-msmsgsemailnotification;")) {
-			char *from = strstr(data, "From:");
-			char *subject = strstr(data, "Subject:");
-			char *x;
-			if (!from || !subject) {
-				connection_has_mail(gc, 1, NULL, NULL, login_url);
-				return;
-			}
-			from += strlen("From: ");
-			x = strstr(from, "\r\n"); *x = 0;
-			subject += strlen("Subject: ");
-			x = strstr(subject, "\r\n"); *x = 0;
-			connection_has_mail(gc, -1, from, subject, login_url);
-		}
-	}
-}
-
-struct msn_add_permit {
-	struct gaim_connection *gc;
-	char *user;
-	char *friend;
-};
-
-static void msn_accept_add(struct msn_add_permit *map)
-{
-	if(g_slist_find(connections, map->gc)) {
-		struct msn_data *md = map->gc->proto_data;
-		char buf[MSN_BUF_LEN];
-
-		g_snprintf(buf, sizeof(buf), "ADD %u AL %s %s\r\n", ++md->trId, map->user, url_encode(map->friend));
-
-		if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-			hide_login_progress(map->gc, _("Write error"));
-			signoff(map->gc);
-			return;
-		}
-		gaim_privacy_permit_add(map->gc->account, map->user);
-		build_allow_list(); /* er. right. we'll need to have a thing for this in CUI too */
-		show_got_added(map->gc, NULL, map->user, map->friend, NULL);
-	}
-
-	g_free(map->user);
-	g_free(map->friend);
-	g_free(map);
-}
-
-static void msn_cancel_add(struct msn_add_permit *map)
-{
-	if(g_slist_find(connections, map->gc)) {
-		struct msn_data *md = map->gc->proto_data;
-		char buf[MSN_BUF_LEN];
-
-		g_snprintf(buf, sizeof(buf), "ADD %u BL %s %s\r\n", ++md->trId, map->user, url_encode(map->friend));
-		if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-			hide_login_progress(map->gc, _("Write error"));
-			signoff(map->gc);
-			return;
-		}
-		gaim_privacy_deny_add(map->gc->account, map->user);
-		build_block_list();
-	}
-
-	g_free(map->user);
-	g_free(map->friend);
-	g_free(map);
-}
-
-static int msn_process_main(struct gaim_connection *gc, char *buf)
-{
-	struct msn_data *md = gc->proto_data;
-	char sendbuf[MSN_BUF_LEN];
-
-	if (!g_ascii_strncasecmp(buf, "ADD", 3)) {
-		char *list, *user, *friend, *tmp = buf;
-		struct msn_add_permit *ap;
-		GSList *perm = gc->account->permit;
-		char msg[MSN_BUF_LEN];
-
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-		list = tmp;
-
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-		user = tmp;
-
-		GET_NEXT(tmp);
-		friend = url_decode(tmp);
-
-		if (g_ascii_strcasecmp(list, "RL"))
-			return 1;
-
-		while (perm) {
-			if (!gaim_utf8_strcasecmp(perm->data, user))
-				return 1;
-			perm = perm->next;
-		}
-
-		ap = g_new0(struct msn_add_permit, 1);
-		ap->user = g_strdup(user);
-		ap->friend = g_strdup(friend);
-		ap->gc = gc;
-
-		g_snprintf(msg, sizeof(msg), _("The user %s (%s) wants to add %s to his or her buddy list."),
-				ap->user, ap->friend, ap->gc->username);
-
-		//	do_ask_dialog(msg, NULL, ap, _("Authorize"), msn_accept_add, _("Deny"), msn_cancel_add, my_protocol->handle, FALSE);
-	} else if (!g_ascii_strncasecmp(buf, "BLP", 3)) {
-		char *type, *tmp = buf;
-
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-		type = tmp;
-
-		if (!g_ascii_strcasecmp(type, "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->permdeny = DENY_SOME;
-		} 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->permdeny = PERMIT_SOME;
-		}
-	} else if (!g_ascii_strncasecmp(buf, "BPR", 3)) {
-	} else if (!g_ascii_strncasecmp(buf, "CHG", 3)) {
-	} else if (!g_ascii_strncasecmp(buf, "CHL", 3)) {
-		char *hash = buf;
-		char buf2[MSN_BUF_LEN];
-		md5_state_t st;
-		md5_byte_t di[16];
-		int i;
-
-		GET_NEXT(hash);
-		GET_NEXT(hash);
-
-		md5_init(&st);
-		md5_append(&st, (const md5_byte_t *)hash, strlen(hash));
-		md5_append(&st, (const md5_byte_t *)"Q1P7W2E4J9R8U3S5", strlen("Q1P7W2E4J9R8U3S5"));
-		md5_finish(&st, di);
-
-		g_snprintf(sendbuf, sizeof(sendbuf), "QRY %u msmsgs@msnmsgr.com 32\r\n", ++md->trId);
-		for (i = 0; i < 16; i++) {
-			g_snprintf(buf2, sizeof(buf2), "%02x", di[i]);
-			strcat(sendbuf, buf2);
-		}
-
-		if (msn_write(md->fd, sendbuf, strlen(sendbuf)) < 0) {
-			hide_login_progress(gc, _("Unable to write to server"));
-			signoff(gc);
-		}
-
-	} else if (!g_ascii_strncasecmp(buf, "FLN", 3)) {
-		char *usr = buf;
-
-		GET_NEXT(usr);
-		serv_got_update(gc, usr, 0, 0, 0, 0, 0);
-	} else if (!g_ascii_strncasecmp(buf, "GTC", 3)) {
-	} else if (!g_ascii_strncasecmp(buf, "INF", 3)) {
-	} else if (!g_ascii_strncasecmp(buf, "ILN", 3)) {
-		char *state, *user, *friend, *tmp = buf;
-		int status = 0;
-
-		GET_NEXT(tmp);
-
-		GET_NEXT(tmp);
-		state = tmp;
-
-		GET_NEXT(tmp);
-		user = tmp;
-
-		GET_NEXT(tmp);
-		friend = url_decode(tmp);
-
-		serv_got_alias(gc, user, friend);
+static char *msn_normalize(const char *str);
 
-		if (!g_ascii_strcasecmp(state, "BSY")) {
-			status |= UC_UNAVAILABLE | (MSN_BUSY << 1);
-		} else if (!g_ascii_strcasecmp(state, "IDL")) {
-			status |= UC_UNAVAILABLE | (MSN_IDLE << 1);
-		} else if (!g_ascii_strcasecmp(state, "BRB")) {
-			status |= UC_UNAVAILABLE | (MSN_BRB << 1);
-		} else if (!g_ascii_strcasecmp(state, "AWY")) {
-			status |= UC_UNAVAILABLE | (MSN_AWAY << 1);
-		} else if (!g_ascii_strcasecmp(state, "PHN")) {
-			status |= UC_UNAVAILABLE | (MSN_PHONE << 1);
-		} else if (!g_ascii_strcasecmp(state, "LUN")) {
-			status |= UC_UNAVAILABLE | (MSN_LUNCH << 1);
-		}
-
-		serv_got_update(gc, user, 1, 0, 0, 0, status);
-	} else if (!g_ascii_strncasecmp(buf, "LST", 3)) {
-		char *which, *who, *friend, *tmp = buf;
-		struct msn_add_permit *ap; /* for any as yet undealt with buddies who've added you to their buddy list when you were off-line.  How dare they! */
-		GSList *perm = gc->account->permit; /* current permit list */
-		GSList *denyl = gc->account->deny;
-		char msg[MSN_BUF_LEN];
-		int new = 1;
-		int pos, tot;
-
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-		which = tmp;
-
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-		pos = strtol(tmp, NULL, 10);
-		GET_NEXT(tmp);
-		tot = strtol(tmp, NULL, 10);
-		GET_NEXT(tmp);
-		who = tmp;
-
-		GET_NEXT(tmp);
-		friend = url_decode(tmp);
-
-		if (!g_ascii_strcasecmp(which, "FL") && pos) {
-			struct msn_buddy *b = g_new0(struct msn_buddy, 1);
-			b->user = g_strdup(who);
-			b->friend = g_strdup(friend);
-			md->fl = g_slist_append(md->fl, b);
-		} else if (!g_ascii_strcasecmp(which, "AL") && pos) {
-			if (g_slist_find_custom(gc->account->deny, who,
-							(GCompareFunc)strcmp)) {
-				gaim_debug(GAIM_DEBUG_INFO, "msn",
-						   "moving from deny to permit: %s", who);
-				gaim_privacy_deny_remove(gc->account, who);
-			}
-			gaim_privacy_permit_add(gc->account, who);
-		} else if (!g_ascii_strcasecmp(which, "BL") && pos) {
-			gaim_privacy_deny_add(gc->account, who);
-		} else if (!g_ascii_strcasecmp(which, "RL")) {
-		    if (pos) {
-			while(perm) {
-				if(!g_ascii_strcasecmp(perm->data, who))
-					new = 0;
-				perm = perm->next;
-			}
-			while(denyl) {
-			  if(!g_ascii_strcasecmp(denyl->data, who))
-			    new = 0;
-			  denyl = denyl->next;
-			}
-			if(new) {
-				gaim_debug(GAIM_DEBUG_INFO, "msn",
-						   "Unresolved MSN RL entry\n");
-				ap = g_new0(struct msn_add_permit, 1);
-				ap->user = g_strdup(who);
-				ap->friend = g_strdup(friend);
-				ap->gc = gc;
-                         
-				g_snprintf(msg, sizeof(msg), _("The user %s (%s) wants to add you to their buddy list"),ap->user, ap->friend);
-				do_ask_dialog(msg, NULL, ap, _("Authorize"), msn_accept_add, _("Deny"), msn_cancel_add, my_protocol->handle, FALSE);
-			}
-		    }
-			
-			if (pos != tot) 
-				return 1; /* this isn't the last one in the RL, so return. */
-
-			g_snprintf(sendbuf, sizeof(sendbuf), "CHG %u NLN\r\n", ++md->trId);
-			if (msn_write(md->fd, sendbuf, strlen(sendbuf)) < 0) {
-				hide_login_progress(gc, _("Unable to write"));
-				signoff(gc);
-				return 0;
-			}
-
-			account_online(gc);
-			serv_finish_login(gc);
-
-			md->permit = g_slist_copy(gc->account->permit);
-			md->deny = g_slist_copy(gc->account->deny);
-
-			while (md->fl) {
-				struct msn_buddy *mb = md->fl->data;
-				struct buddy *b = gaim_find_buddy(gc->account, mb->user);
-				md->fl = g_slist_remove(md->fl, mb);
-				if(!b) {
-					struct group *g;
-					printf("I'm adding %s now..\n", mb->user);
-					if (!(g = gaim_find_group(_("Buddies")))) {
-						printf("How could I not exitst!?!\n");
-						g = gaim_group_new(_("Buddies"));
-						gaim_blist_add_group(g, NULL);
-					}
-					b = gaim_buddy_new(gc->account, mb->user, NULL);
-					gaim_blist_add_buddy(b,g,NULL);
-				}
-				serv_got_alias(gc, mb->user, mb->friend);
-				g_free(mb->user);
-				g_free(mb->friend);
-				g_free(mb);
-			}
-		}
-	} else if (!g_ascii_strncasecmp(buf, "MSG", 3)) {
-		char *user, *tmp = buf;
-		int length;
-
-		GET_NEXT(tmp);
-		user = tmp;
-
-		GET_NEXT(tmp);
-
-		GET_NEXT(tmp);
-		length = atoi(tmp);
-
-		md->msg = TRUE;
-		md->msguser = g_strdup(user);
-		md->msglen = length;
-	} else if (!g_ascii_strncasecmp(buf, "NLN", 3)) {
-		char *state, *user, *friend, *tmp = buf;
-		int status = 0;
-
-		GET_NEXT(tmp);
-		state = tmp;
-
-		GET_NEXT(tmp);
-		user = tmp;
-
-		GET_NEXT(tmp);
-		friend = url_decode(tmp);
-
-		serv_got_alias(gc, user, friend);
-
-		if (!g_ascii_strcasecmp(state, "BSY")) {
-			status |= UC_UNAVAILABLE | (MSN_BUSY << 1);
-		} else if (!g_ascii_strcasecmp(state, "IDL")) {
-			status |= UC_UNAVAILABLE | (MSN_IDLE << 1);
-		} else if (!g_ascii_strcasecmp(state, "BRB")) {
-			status |= UC_UNAVAILABLE | (MSN_BRB << 1);
-		} else if (!g_ascii_strcasecmp(state, "AWY")) {
-			status |= UC_UNAVAILABLE | (MSN_AWAY << 1);
-		} else if (!g_ascii_strcasecmp(state, "PHN")) {
-			status |= UC_UNAVAILABLE | (MSN_PHONE << 1);
-		} else if (!g_ascii_strcasecmp(state, "LUN")) {
-			status |= UC_UNAVAILABLE | (MSN_LUNCH << 1);
-		}
-
-		serv_got_update(gc, user, 1, 0, 0, 0, status);
-	} else if (!g_ascii_strncasecmp(buf, "OUT", 3)) {
-		char *tmp = buf;
-
-		GET_NEXT(tmp);
-		if (!g_ascii_strncasecmp(tmp, "OTH", 3)) {
-			hide_login_progress(gc, _("You have been disconnected. You have "
-						  "signed on from another location."));
-			signoff(gc);
-			return 0;
-		}
-	} else if (!g_ascii_strncasecmp(buf, "PRP", 3)) {
-	} else if (!g_ascii_strncasecmp(buf, "QNG", 3)) {
-	} else if (!g_ascii_strncasecmp(buf, "QRY", 3)) {
-	} else if (!g_ascii_strncasecmp(buf, "REA", 3)) {
-		char *friend, *tmp = buf;
-
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-
-		friend = url_decode(tmp);
-
-		g_snprintf(gc->displayname, sizeof(gc->displayname), "%s", friend);
-	} else if (!g_ascii_strncasecmp(buf, "REM", 3)) {
-	} else if (!g_ascii_strncasecmp(buf, "RNG", 3)) {
-		struct msn_switchboard *ms;
-		char *sessid, *ssaddr, *auth, *user;
-		int port, i = 0;
-		char *tmp = buf;
-
-		GET_NEXT(tmp);
-		sessid = tmp;
-
-		GET_NEXT(tmp);
-		ssaddr = tmp;
-
-		GET_NEXT(tmp);
-
-		GET_NEXT(tmp);
-		auth = tmp;
-
-		GET_NEXT(tmp);
-		user = tmp;
-		GET_NEXT(tmp);
+static void
+msn_act_id(gpointer data, char *entry)
+{
+	struct gaim_connection *gc = data;
+	MsnSession *session = gc->proto_data;
+	char outparams[MSN_BUF_LEN];
+	char *alias;
 
-		while (ssaddr[i] && ssaddr[i] != ':') i++;
-		if (ssaddr[i] == ':') {
-			char *x = &ssaddr[i + 1];
-			ssaddr[i] = 0;
-			port = atoi(x);
-		} else
-			port = 1863;
-
-		ms = g_new0(struct msn_switchboard, 1);
-		if (proxy_connect(gc->account, ssaddr, port, msn_rng_connect, ms) != 0) {
-			g_free(ms);
-			return 1;
-		}
-		ms->user = g_strdup(user);
-		ms->sessid = g_strdup(sessid);
-		ms->auth = g_strdup(auth);
-		ms->gc = gc;
-	} else if (!g_ascii_strncasecmp(buf, "URL", 3)) {
-		char *tmp = buf;
-		FILE *fd;
-		md5_state_t st;
-		md5_byte_t di[16];
-		int i;
-		char buf2[64];
-		char sendbuf[64];
-		char hippy[2048];
-		char *rru;
-		char *passport;
-
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-		rru = tmp;
-		GET_NEXT(tmp);
-		passport = tmp;
-		
-		g_snprintf(hippy, sizeof(hippy), "%s%lu%s", md->mspauth, time(NULL) - md->sl, gc->password);
-
-		md5_init(&st);
-		md5_append(&st, (const md5_byte_t *)hippy, strlen(hippy));
-		md5_finish(&st, di);
-
-		bzero(sendbuf, sizeof(sendbuf));
-		for (i = 0; i < 16; i++) {
-			g_snprintf(buf2, sizeof(buf2), "%02x", di[i]);
-			strcat(sendbuf, buf2);
-		}
-
-		if (md->passport) {
-			unlink(md->passport);
-			g_free(md->passport);
-		}
+	if (entry == NULL || *entry == '\0') 
+		alias = g_strdup("");
+	else
+		alias = g_strdup(entry);
 
-		if( (fd = gaim_mkstemp(&(md->passport))) == NULL ) {
-		  gaim_debug(GAIM_DEBUG_ERROR, "msn",
-					 "Error opening temp file: %s\n", strerror(errno)); 
-		}
-		else {
-			fputs("<html>\n"
-				"<head>\n"
-				"<noscript>\n"
-				"<meta http-equiv=Refresh content=\"0; url=http://www.hotmail.com\">\n"
-				"</noscript>\n"
-				"</head>\n\n", fd);
-		
-		        fprintf(fd, "<body onload=\"document.pform.submit(); \">\n");
-		        fprintf(fd, "<form name=\"pform\" action=\"%s\" method=\"POST\">\n\n", passport);
-		        fprintf(fd, "<input type=\"hidden\" name=\"mode\" value=\"ttl\">\n");
-		        fprintf(fd, "<input type=\"hidden\" name=\"login\" value=\"%s\">\n", gc->username);
-		        fprintf(fd, "<input type=\"hidden\" name=\"username\" value=\"%s\">\n", gc->username);
-			fprintf(fd, "<input type=\"hidden\" name=\"sid\" value=\"%s\">\n", md->sid);
-			fprintf(fd, "<input type=\"hidden\" name=\"kv\" value=\"%s\">\n", md->kv);
-			fprintf(fd, "<input type=\"hidden\" name=\"id\" value=\"2\">\n");
-			fprintf(fd, "<input type=\"hidden\" name=\"sl\" value=\"%ld\">\n", time(NULL) - md->sl);
-			fprintf(fd, "<input type=\"hidden\" name=\"rru\" value=\"%s\">\n", rru);
-			fprintf(fd, "<input type=\"hidden\" name=\"auth\" value=\"%s\">\n", md->mspauth);
-			fprintf(fd, "<input type=\"hidden\" name=\"creds\" value=\"%s\">\n", sendbuf); // Digest me
-			fprintf(fd, "<input type=\"hidden\" name=\"svc\" value=\"mail\">\n");
-			fprintf(fd, "<input type=\"hidden\" name=\"js\" value=\"yes\">\n");
-			fprintf(fd, "</form></body>\n");
-			fprintf(fd, "</html>\n");
-			if (fclose(fd)) {
-				gaim_debug(GAIM_DEBUG_ERROR, "msn",
-						   "Error closing temp file: %s\n", strerror(errno)); 
-				unlink(md->passport);
-				g_free(md->passport);
-			}
-			else {
-				/*
-				 * Renaming file with .html extension, so that
-				 * win32 open_url will work.
-				 */
-				char *tmp;
-				if ((tmp = g_strdup_printf("%s.html", md->passport)) != NULL) {
-					if (rename(md->passport, tmp) == 0) {
-						g_free(md->passport);
-						md->passport = tmp;
-					} else
-						g_free(tmp);
-				}
-			}
-		}
-	} else if (!g_ascii_strncasecmp(buf, "SYN", 3)) {
-	} else if (!g_ascii_strncasecmp(buf, "USR", 3)) {
-	} else if (!g_ascii_strncasecmp(buf, "XFR", 3)) {
-		char *host = strstr(buf, "SB");
-		int port;
-		int i = 0;
-		gboolean switchboard = TRUE;
-		char *tmp;
-
-		if (!host) {
-			host = strstr(buf, "NS");
-			if (!host) {
-				hide_login_progress(gc, _("Got invalid XFR\n"));
-				signoff(gc);
-				return 0;
-			}
-			switchboard = FALSE;
-		}
-
-		GET_NEXT(host);
-		while (host[i] && host[i] != ':') i++;
-		if (host[i] == ':') {
-			tmp = &host[i + 1];
-			host[i] = 0;
-			while (isdigit(*tmp)) tmp++;
-			*tmp++ = 0;
-			port = atoi(&host[i + 1]);
-		} else {
-			port = 1863;
-			tmp = host;
-			GET_NEXT(tmp);
-		}
-
-		if (switchboard) {
-			struct msn_switchboard *ms;
-
-			GET_NEXT(tmp);
-
-			ms = msn_switchboard_connect(gc, host, port);
-
-			if (ms == NULL)
-				return 1;
-
-			ms->auth = g_strdup(tmp);
-		} else {
-			close(md->fd);
-			gaim_input_remove(md->inpa);
-			md->inpa = 0;
-			if (proxy_connect(gc->account, host, port, msn_login_xfr_connect, gc) != 0) {
-				hide_login_progress(gc, _("Error transferring"));
-				signoff(gc);
-				return 0;
-			}
-		}
-	} else if (isdigit(*buf)) {
-		handle_errcode(buf, TRUE);
-	} else {
-		gaim_debug(GAIM_DEBUG_WARNING, "msn", "Unhandled message!\n");
-	}
-
-	return 1;
-}
-
-static void msn_process_main_msg(struct gaim_connection *gc, char *msg)
-{
-	struct msn_data *md = gc->proto_data;
-	char *skiphead;
-	char *content;
-
-	content = strstr(msg, "Content-Type: ");
-
-	if ((content) && (!g_ascii_strncasecmp(content, "Content-Type: text/x-msmsgsprofile",
-				strlen("Content-Type: text/x-msmsgsprofile")))) {
-
-		char *kv,*sid,*mspauth;
-
-		kv = strstr(msg, "kv: ");
-		sid = strstr(msg, "sid: ");
-		mspauth = strstr(msg, "MSPAuth: ");
-
-		if (kv) {
-			char *tmp;
-
-			kv += strlen("kv: ");
-			tmp = strstr(kv, "\r\n"); *tmp = 0;
-			md->kv = g_strdup(kv);
-		}
-
-		if (sid) {
-			char *tmp;
-
-			sid += strlen("sid: ");
-			tmp = strstr(sid, "\r\n"); *tmp = 0;
-			md->sid = g_strdup(sid);
-		}
-
-		if (mspauth) {
-			char *tmp;
-
-			mspauth += strlen("MSPAuth: ");
-			tmp = strstr(mspauth, "\r\n"); *tmp = 0;
-			md->mspauth = g_strdup(mspauth);
-		}
-
-	}
-
-
-
-	if (!g_ascii_strcasecmp(md->msguser, "hotmail")) {
-		handle_hotmail(gc, msg);
+	if (strlen(alias) >= BUDDY_ALIAS_MAXLEN) {
+		do_error_dialog(_("Your new MSN friendly name is too long."),
+						NULL, GAIM_ERROR);
 		return;
 	}
 
-
-	skiphead = strstr(msg, "\r\n\r\n");
-	if (!skiphead || !skiphead[4])
-		return;
-	skiphead += 4;
-	strip_linefeed(skiphead);
-
-	serv_got_im(gc, md->msguser, skiphead, 0, time(NULL), -1);
-}
-
-static void msn_callback(gpointer data, gint source, GaimInputCondition cond)
-{
-	struct gaim_connection *gc = data;
-	struct msn_data *md = gc->proto_data;
-	char buf[MSN_BUF_LEN];
-	int cont = 1;
-	int len;
-
-	len = read(md->fd, buf, sizeof(buf));
-	if (len <= 0) {
-		hide_login_progress_error(gc, _("Error reading from server"));
-		signoff(gc);
-		return;
-	}
-
-	md->rxqueue = g_realloc(md->rxqueue, len + md->rxlen);
-	memcpy(md->rxqueue + md->rxlen, buf, len);
-	md->rxlen += len;
-
-	while (cont) {
-		if (!md->rxlen)
-			return;
-
-		if (md->msg) {
-			char *msg;
-			if (md->msglen > md->rxlen)
-				return;
-			msg = md->rxqueue;
-			md->rxlen -= md->msglen;
-			if (md->rxlen) {
-				md->rxqueue = g_memdup(msg + md->msglen, md->rxlen);
-			} else {
-				md->rxqueue = NULL;
-				msg = g_realloc(msg, md->msglen + 1);
-			}
-			msg[md->msglen] = 0;
-			md->msglen = 0;
-			md->msg = FALSE;
-
-			msn_process_main_msg(gc, msg);
-
-			g_free(md->msguser);
-			g_free(msg);
-		} else {
-			char *end = md->rxqueue;
-			int cmdlen;
-			char *cmd;
-			int i = 0;
-
-			while (i + 1 < md->rxlen) {
-				if (*end == '\r' && end[1] == '\n')
-					break;
-				end++; i++;
-			}
-			if (i + 1 == md->rxlen)
-				return;
-
-			cmdlen = end - md->rxqueue + 2;
-			cmd = md->rxqueue;
-			md->rxlen -= cmdlen;
-			if (md->rxlen) {
-				md->rxqueue = g_memdup(cmd + cmdlen, md->rxlen);
-			} else {
-				md->rxqueue = NULL;
-				cmd = g_realloc(cmd, cmdlen + 1);
-			}
-			cmd[cmdlen] = 0;
-
-			gaim_debug(GAIM_DEBUG_MISC, "msn", "S: %s", cmd);
-			g_strchomp(cmd);
-			cont = msn_process_main(gc, cmd);
-
-			g_free(cmd);
-		}
-	}
-}
+	g_snprintf(outparams, sizeof(outparams), "%s %s",
+			   gc->username, msn_url_encode(alias));
 
-static void msn_login_xfr_connect(gpointer data, gint source, GaimInputCondition cond)
-{
-	struct gaim_connection *gc = data;
-	struct msn_data *md;
-	char buf[MSN_BUF_LEN];
-
-	if (!g_slist_find(connections, gc)) {
-		close(source);
-		return;
-	}
-
-	md = gc->proto_data;
-
-	if (md->fd != source)
-		md->fd = source;
-
-	if (md->fd == -1) {
-		hide_login_progress(gc, _("Unable to connect to Notification Server"));
-		signoff(gc);
-		return;
-	}
-
-	g_snprintf(buf, sizeof(buf), "VER %u MSNP5\r\n", ++md->trId);
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-		hide_login_progress(gc, _("Unable to talk to Notification Server"));
-		signoff(gc);
-		return;
-	}
-
-	md->inpa = gaim_input_add(md->fd, GAIM_INPUT_READ, msn_login_callback, gc);
-}
-
-static int msn_process_login(struct gaim_connection *gc, char *buf)
-{
-	struct msn_data *md = gc->proto_data;
-	char sendbuf[MSN_BUF_LEN];
-
-	if (!g_ascii_strncasecmp(buf, "VER", 3)) {
-		/* we got VER, check to see that MSNP5 is in the list, then send INF */
-		if (!strstr(buf, "MSNP5")) {
-			hide_login_progress(gc, _("Protocol not supported"));
-			signoff(gc);
-			return 0;
-		}
-
-		g_snprintf(sendbuf, sizeof(sendbuf), "INF %u\r\n", ++md->trId);
-		if (msn_write(md->fd, sendbuf, strlen(sendbuf)) < 0) {
-			hide_login_progress(gc, _("Unable to request INF\n"));
-			signoff(gc);
-			return 0;
-		}
-	} else if (!g_ascii_strncasecmp(buf, "INF", 3)) {
-		/* check to make sure we can use md5 */
-		if (!strstr(buf, "MD5")) {
-			hide_login_progress(gc, _("Unable to login using MD5"));
-			signoff(gc);
-			return 0;
-		}
-
-		g_snprintf(sendbuf, sizeof(sendbuf), "USR %u MD5 I %s\r\n", ++md->trId, gc->username);
-		if (msn_write(md->fd, sendbuf, strlen(sendbuf)) < 0) {
-			hide_login_progress(gc, _("Unable to send USR\n"));
-			signoff(gc);
-			return 0;
-		}
-
-		set_login_progress(gc, 3, _("Requesting to send password"));
-	} else if (!g_ascii_strncasecmp(buf, "USR", 3)) {
-		char *resp, *friend, *tmp = buf;
-
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-		resp = tmp;
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-		friend = url_decode(tmp);
-		GET_NEXT(tmp);
-
-		/* so here, we're either getting the challenge or the OK */
-		if (!g_ascii_strcasecmp(resp, "OK")) {
-			g_snprintf(gc->displayname, sizeof(gc->displayname), "%s", friend);
-
-			g_snprintf(sendbuf, sizeof(sendbuf), "SYN %u 0\r\n", ++md->trId);
-			if (msn_write(md->fd, sendbuf, strlen(sendbuf)) < 0) {
-				hide_login_progress(gc, _("Unable to write"));
-				signoff(gc);
-				return 0;
-			}
+	g_free(alias);
 
-			gaim_input_remove(md->inpa);
-			md->inpa = gaim_input_add(md->fd, GAIM_INPUT_READ, msn_callback, gc);
-			return 0;
-		} else if (!g_ascii_strcasecmp(resp, "MD5")) {
-			char buf2[MSN_BUF_LEN];
-			md5_state_t st;
-			md5_byte_t di[16];
-			int i;
-
-			g_snprintf(buf2, sizeof(buf2), "%s%s", friend, gc->password);
-
-			md5_init(&st);
-			md5_append(&st, (const md5_byte_t *)buf2, strlen(buf2));
-			md5_finish(&st, di);
-
-			g_snprintf(sendbuf, sizeof(sendbuf), "USR %u MD5 S ", ++md->trId);
-			for (i = 0; i < 16; i++) {
-				g_snprintf(buf2, sizeof(buf2), "%02x", di[i]);
-				strcat(sendbuf, buf2);
-			}
-			strcat(sendbuf, "\r\n");
-
-			if (msn_write(md->fd, sendbuf, strlen(sendbuf)) < 0) {
-				hide_login_progress(gc, _("Unable to send password"));
-				signoff(gc);
-				return 0;
-			}
-
-			set_login_progress(gc, 4, _("Password sent"));
-		}
-	} else if (!g_ascii_strncasecmp(buf, "XFR", 3)) {
-		char *host = strstr(buf, "NS");
-		int port;
-		int i = 0;
-
-		if (!host) {
-			hide_login_progress(gc, _("Got invalid XFR\n"));
-			signoff(gc);
-			return 0;
-		}
-
-		GET_NEXT(host);
-		while (host[i] && host[i] != ':') i++;
-		if (host[i] == ':') {
-			char *x = &host[i + 1];
-			host[i] = 0;
-			port = atoi(x);
-		} else
-			port = 1863;
-
-		close(md->fd);
-		gaim_input_remove(md->inpa);
-		md->inpa = 0;
-		md->fd = 0;
-		md->sl = time(NULL);
-		if (proxy_connect(gc->account, host, port, msn_login_xfr_connect, gc) != 0) {
-			hide_login_progress(gc, _("Unable to transfer"));
-			signoff(gc);
-		}
-		return 0;
-	} else {
-		if (isdigit(*buf))
-			hide_login_progress(gc, handle_errcode(buf, FALSE));
-		else
-			hide_login_progress(gc, _("Unable to parse message"));
-		signoff(gc);
-		return 0;
-	}
-
-	return 1;
-}
-
-static void msn_login_callback(gpointer data, gint source, GaimInputCondition cond)
-{
-	struct gaim_connection *gc = data;
-	struct msn_data *md = gc->proto_data;
-	char buf[MSN_BUF_LEN];
-	int cont = 1;
-	int len;
-
-	len = read(md->fd, buf, sizeof(buf));
-	if (len <= 0) {
-		hide_login_progress(gc, _("Error reading from server"));
-		signoff(gc);
-		return;
-	}
+	if (!msn_servconn_send_command(session->notification_conn,
+								   "REA", outparams)) {
 
-	md->rxqueue = g_realloc(md->rxqueue, len + md->rxlen);
-	memcpy(md->rxqueue + md->rxlen, buf, len);
-	md->rxlen += len;
-
-	while (cont) {
-		char *end = md->rxqueue;
-		int cmdlen;
-		char *cmd;
-		int i = 0;
-
-		if (!md->rxlen)
-			return;
-
-		while (i + 1 < md->rxlen) {
-			if (*end == '\r' && end[1] == '\n')
-				break;
-			end++; i++;
-		}
-		if (i + 1 == md->rxlen)
-			return;
-
-		cmdlen = end - md->rxqueue + 2;
-		cmd = md->rxqueue;
-		md->rxlen -= cmdlen;
-		if (md->rxlen) {
-			md->rxqueue = g_memdup(cmd + cmdlen, md->rxlen);
-		} else {
-			md->rxqueue = NULL;
-			cmd = g_realloc(cmd, cmdlen + 1);
-		}
-		cmd[cmdlen] = 0;
-
-		gaim_debug(GAIM_DEBUG_MISC, "msn", "S: %s", cmd);
-		g_strchomp(cmd);
-		cont = msn_process_login(gc, cmd);
-
-		g_free(cmd);
-	}
-}
-
-static void msn_login_connect(gpointer data, gint source, GaimInputCondition cond)
-{
-	struct gaim_connection *gc = data;
-	struct msn_data *md;
-	char buf[1024];
-
-	if (!g_slist_find(connections, gc)) {
-		close(source);
-		return;
-	}
-
-	md = gc->proto_data;
-
-	if (md->fd != source)
-		md->fd = source;
-
-	if (md->fd == -1) {
-		hide_login_progress(gc, _("Unable to connect"));
-		signoff(gc);
-		return;
-	}
-
-	g_snprintf(buf, sizeof(buf), "VER %u MSNP5\r\n", ++md->trId);
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-		hide_login_progress(gc, _("Unable to write to server"));
-		signoff(gc);
-		return;
-	}
-
-	md->inpa = gaim_input_add(md->fd, GAIM_INPUT_READ, msn_login_callback, gc);
-	set_login_progress(gc, 2,_("Synching with server"));
-}
-
-static void msn_login(struct gaim_account *account)
-{
-	struct gaim_connection *gc = new_gaim_conn(account);
-	gc->proto_data = g_new0(struct msn_data, 1);
-
-	set_login_progress(gc, 1, _("Connecting"));
-
-	g_snprintf(gc->username, sizeof(gc->username), "%s", msn_normalize(gc->username));
-
-	if (proxy_connect(account, account->proto_opt[USEROPT_MSNSERVER][0] ?
-				account->proto_opt[USEROPT_MSNSERVER] : MSN_SERVER,
-				account->proto_opt[USEROPT_MSNPORT][0] ?
-				atoi(account->proto_opt[USEROPT_MSNPORT]) : MSN_PORT,
-				msn_login_connect, gc) != 0) {
-		hide_login_progress(gc, _("Unable to connect"));
+		hide_login_progress(gc, _("Write error"));
 		signoff(gc);
 	}
 }
 
-static void msn_close(struct gaim_connection *gc)
+static void
+msn_show_set_friendly_name(struct gaim_connection *gc)
 {
-	struct msn_data *md = gc->proto_data;
-	close(md->fd);
-	if (md->inpa)
-		gaim_input_remove(md->inpa);
-	g_free(md->rxqueue);
-	if (md->msg)
-		g_free(md->msguser);
-	if (md->passport) {
-		unlink(md->passport);
-      		g_free(md->passport);
-	}
-	while (md->switches)
-		msn_kill_switch(md->switches->data);
-	while (md->fl) {
-		struct msn_buddy *tmp = md->fl->data;
-		md->fl = g_slist_remove(md->fl, tmp);
-		g_free(tmp->user);
-		g_free(tmp->friend);
-		g_free(tmp);
-	}
-	g_slist_free(md->permit);
-	g_slist_free(md->deny);
-	g_free(md);
+	do_prompt_dialog(_("Set Friendly Name:"), gc->displayname,
+					 gc, msn_act_id, NULL);
 }
 
-static int msn_send_typing(struct gaim_connection *gc, char *who, int typing) {
-	struct msn_switchboard *ms = msn_find_switch(gc, who);
-	char header[MSN_BUF_LEN] =   "MIME-Version: 1.0\r\n"
-				     "Content-Type: text/x-msmsgscontrol\r\n" 
-				     "TypingUser: ";
-	char buf [MSN_BUF_LEN];
-	if (!ms || !typing)
-		return 0;
-	g_snprintf(buf, sizeof(buf), "MSG %u N %d\r\n%s%s\r\n\r\n\r\n",
-		   ++ms->trId,
-		   strlen(header) + strlen("\r\n\r\n\r\n") + strlen(gc->username),
-		   header, gc->username);
-	if (msn_write(ms->fd, buf, strlen(buf)) < 0)
-	           msn_kill_switch(ms);
-	return MSN_TYPING_SEND_TIMEOUT;
+
+/**************************************************************************
+ * Protocol Plugin ops
+ **************************************************************************/
+
+static const char *
+msn_list_icon(struct gaim_account *a, struct buddy *b)
+{
+	return "msn";
 }
 
-static int msn_send_im(struct gaim_connection *gc, const char *who, const char *message, int len, int flags)
+static void
+msn_list_emblems(struct buddy *b, char **se, char **sw,
+				 char **nw, char **ne)
 {
-	struct msn_data *md = gc->proto_data;
-	struct msn_switchboard *ms = msn_find_switch(gc, who);
-	char buf[MSN_BUF_LEN];
-
-	if (ms) {
-		char *send;
-
-		if (ms->txqueue) {
-			gaim_debug(GAIM_DEBUG_INFO, "msn", "appending to queue\n");
-			ms->txqueue = g_slist_append(ms->txqueue, g_strdup(message));
-			return 1;
-		}
-
-		send = add_cr(message);
-		g_snprintf(buf, sizeof(buf), "MSG %u N %d\r\n%s%s", ++ms->trId,
-				strlen(MIME_HEADER) + strlen(send),
-				MIME_HEADER, send);
-		g_free(send);
-		if (msn_write(ms->fd, buf, strlen(buf)) < 0)
-			msn_kill_switch(ms);
-	} else if (strcmp(who, gc->username)) {
-		g_snprintf(buf, MSN_BUF_LEN, "XFR %u SB\r\n", ++md->trId);
-		if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-			hide_login_progress(gc, _("Write error"));
-			signoff(gc);
-			return 1;
-		}
-
-		ms = g_new0(struct msn_switchboard, 1);
-		md->switches = g_slist_append(md->switches, ms);
-		ms->user = g_strdup(who);
-		ms->txqueue = g_slist_append(ms->txqueue, g_strdup(message));
-		ms->gc = gc;
-		ms->fd = -1;
-	} else
-		/* in msn you can't send messages to yourself, so we'll fake like we received it ;) */
-		serv_got_im(gc, who, message, flags | IM_FLAG_GAIMUSER, time(NULL), -1);
-	return 1;
+	if (b->present == GAIM_BUDDY_OFFLINE)
+		*se = "offline";
+	else if ((b->uc >> 1) == 2 || (b->uc >> 1) == 6)
+		*se = "occupied";
+	else if (b->uc)
+		*se = "away";
 }
 
-static int msn_chat_send(struct gaim_connection *gc, int id, char *message)
+static char *
+msn_status_text(struct buddy *b)
 {
-	struct msn_switchboard *ms = msn_find_switch_by_id(gc, id);
-	char buf[MSN_BUF_LEN];
-	char *send;
-
-	if (!ms)
-		return -EINVAL;
+	if (b->uc & UC_UNAVAILABLE)
+		return g_strdup(msn_away_get_text(b->uc >> 1));
 
-	send = add_cr(message);
-	g_snprintf(buf, sizeof(buf), "MSG %u N %d\r\n%s%s", ++ms->trId,
-			strlen(MIME_HEADER) + strlen(send),
-			MIME_HEADER, send);
-	g_free(send);
-	if (msn_write(ms->fd, buf, strlen(buf)) < 0) {
-		msn_kill_switch(ms);
-		return 0;
-	}
-	serv_got_chat_in(gc, id, gc->username, 0, message, time(NULL));
-	return 0;
+	return NULL;
 }
 
-static void msn_chat_invite(struct gaim_connection *gc, int id, const char *msg, const char *who)
+static char *
+msn_tooltip_text(struct buddy *b)
 {
-	struct msn_switchboard *ms = msn_find_switch_by_id(gc, id);
-	char buf[MSN_BUF_LEN];
+	if (GAIM_BUDDY_IS_ONLINE(b)) {
+		return g_strdup_printf(_("<b>Status:</b> %s"),
+							   msn_away_get_text(b->uc >> 1));
+	}
 
-	if (!ms)
-		return;
-
-	g_snprintf(buf, sizeof(buf), "CAL %u %s\r\n", ++ms->trId, who);
-	if (msn_write(ms->fd, buf, strlen(buf)) < 0)
-		msn_kill_switch(ms);
+	return NULL;
 }
 
-static void msn_chat_leave(struct gaim_connection *gc, int id)
-{
-	struct msn_switchboard *ms = msn_find_switch_by_id(gc, id);
-	char buf[MSN_BUF_LEN];
-
-	if (!ms)
-		return;
-
-	g_snprintf(buf, sizeof(buf), "OUT\r\n");
-	if (msn_write(ms->fd, buf, strlen(buf)) < 0)
-		msn_kill_switch(ms);
-}
-
-static GList *msn_away_states(struct gaim_connection *gc)
+static GList *
+msn_away_states(struct gaim_connection *gc)
 {
 	GList *m = NULL;
 
@@ -1361,21 +132,185 @@
 	return m;
 }
 
-static void msn_set_away(struct gaim_connection *gc, char *state, char *msg)
+static GList *
+msn_actions(struct gaim_connection *gc)
+{
+	GList *m = NULL;
+	struct proto_actions_menu *pam;
+
+	pam = g_new0(struct proto_actions_menu, 1);
+	pam->label = _("Set Friendly Name");
+	pam->callback = msn_show_set_friendly_name;
+	pam->gc = gc;
+	m = g_list_append(m, pam);
+
+	return m;
+}
+
+static GList *
+msn_buddy_menu(struct gaim_connection *gc, const char *who)
+{
+	GList *m = NULL;
+
+	return m;
+}
+
+static void
+msn_login(struct gaim_account *account)
+{
+	struct gaim_connection *gc;
+	MsnSession *session;
+	const char *server;
+	int port;
+
+	server = (*account->proto_opt[USEROPT_MSNSERVER]
+			  ? account->proto_opt[USEROPT_MSNSERVER]
+			  : MSN_SERVER);
+	port = (*account->proto_opt[USEROPT_MSNPORT]
+			? atoi(account->proto_opt[USEROPT_MSNPORT])
+			: MSN_PORT);
+
+
+	gc = new_gaim_conn(account);
+
+	session = msn_session_new(account, server, port);
+	session->prpl = my_protocol;
+
+	gc->proto_data = session;
+
+	set_login_progress(gc, 1, _("Connecting"));
+
+	g_snprintf(gc->username, sizeof(gc->username), "%s",
+			   msn_normalize(gc->username));
+
+	if (!msn_session_connect(session)) {
+		hide_login_progress(gc, _("Unable to connect"));
+		signoff(gc);
+	}
+}
+
+static void
+msn_close(struct gaim_connection *gc)
+{
+	MsnSession *session = gc->proto_data;
+
+	msn_session_destroy(session);
+
+	gc->proto_data = NULL;
+}
+
+static int
+msn_send_im(struct gaim_connection *gc, const char *who, const char *message,
+			int len, int flags)
 {
-	struct msn_data *md = gc->proto_data;
-	char buf[MSN_BUF_LEN];
+	MsnSession *session = gc->proto_data;
+	MsnSwitchBoard *swboard;
+
+	swboard = msn_session_find_switch_with_passport(session, who);
+
+	if (g_ascii_strcasecmp(who, gc->username)) {
+		MsnMessage *msg;
+		MsnUser *user;
+
+		user = msn_user_new(session, who, NULL);
+
+		msg = msn_message_new();
+		msn_message_set_receiver(msg, user);
+		msn_message_set_attr(msg, "X-MMS-IM-Format",
+							 "FN=Arial; EF=; CO=0; PF=0");
+		msn_message_set_body(msg, message);
+
+		if (swboard != NULL) {
+			if (!msn_switchboard_send_msg(swboard, msg))
+				msn_switchboard_destroy(swboard);
+		}
+		else {
+			if ((swboard = msn_session_open_switchboard(session)) == NULL) {
+				msn_message_destroy(msg);
+
+				hide_login_progress(gc, _("Write error"));
+				signoff(gc);
+
+				return 1;
+			}
+
+			msn_switchboard_set_user(swboard, user);
+			msn_switchboard_send_msg(swboard, msg);
+		}
+
+		msn_user_destroy(user);
+		msn_message_destroy(msg);
+	}
+	else {
+		/*
+		 * 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_im(gc, who, message, flags | IM_FLAG_GAIMUSER,
+					time(NULL), -1);
+	}
+
+	return 1;
+}
+
+static int
+msn_send_typing(struct gaim_connection *gc, char *who, int typing)
+{
+	MsnSession *session = gc->proto_data;
+	MsnSwitchBoard *swboard;
+	MsnMessage *msg;
+	MsnUser *user;
+
+	if (!typing)
+		return 0;
+
+	if (!g_ascii_strcasecmp(who, gc->username)) {
+		/* We'll just fake it, since we're sending to ourself. */
+		serv_got_typing(gc, who, MSN_TYPING_RECV_TIMEOUT, TYPING);
+
+		return MSN_TYPING_SEND_TIMEOUT;
+	}
+
+	swboard = msn_session_find_switch_with_passport(session, who);
+
+	if (swboard == NULL)
+		return 0;
+
+	user = msn_user_new(session, who, NULL);
+
+	msg = msn_message_new();
+	msn_message_set_content_type(msg, "text/x-msmsgscontrol");
+	msn_message_set_receiver(msg, user);
+	msn_message_set_charset(msg, NULL);
+	msn_message_set_attr(msg, "TypingUser", gc->username);
+	msn_message_set_attr(msg, "User-Agent", NULL);
+	msn_message_set_body(msg, "\r\n");
+
+	if (!msn_switchboard_send_msg(swboard, msg))
+		msn_switchboard_destroy(swboard);
+
+	msn_user_destroy(user);
+
+	return MSN_TYPING_SEND_TIMEOUT;
+}
+
+static void
+msn_set_away(struct gaim_connection *gc, char *state, char *msg)
+{
+	MsnSession *session = gc->proto_data;
 	const char *away;
 
-	if (gc->away) {
+	if (gc->away != NULL) {
 		g_free(gc->away);
 		gc->away = NULL;
 	}
 
-	if (msg) {
+	if (msg != NULL) {
 		gc->away = g_strdup("");
 		away = "AWY";
-	} else if (state) {
+	}
+	else if (state) {
 		gc->away = g_strdup("");
 
 		if (!strcmp(state, _("Away From Computer")))
@@ -1395,415 +330,460 @@
 			gc->away = NULL;
 			away = "NLN";
 		}
-	} else if (gc->is_idle)
+	}
+	else if (gc->is_idle)
 		away = "IDL";
 	else
 		away = "NLN";
 
-	g_snprintf(buf, sizeof(buf), "CHG %u %s\r\n", ++md->trId, away);
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
+	if (!msn_servconn_send_command(session->notification_conn, "CHG", away)) {
+		hide_login_progress(gc, _("Write error"));
+		signoff(gc);
+	}
+}
+
+static void
+msn_set_idle(struct gaim_connection *gc, int idle)
+{
+	MsnSession *session = gc->proto_data;
+
+	if (gc->away != NULL)
+		return;
+
+	if (!msn_servconn_send_command(session->notification_conn, "CHG",
+								   (idle ? "IDL" : "NLN"))) {
+
 		hide_login_progress(gc, _("Write error"));
 		signoff(gc);
+	}
+}
+
+static void
+msn_add_buddy(struct gaim_connection *gc, const char *name)
+{
+	MsnSession *session = gc->proto_data;
+	char *who;
+	char outparams[MSN_BUF_LEN];
+	GSList *l;
+
+	who = msn_normalize(name);
+
+	if (strchr(who, ' ')) {
+		/* This is a broken blist entry. */
 		return;
 	}
+
+	for (l = session->lists.forward; l != NULL; l = l->next) {
+		MsnUser *user = l->data;
+
+		if (!gaim_utf8_strcasecmp(who, msn_user_get_passport(user)))
+			break;
+	}
+
+	if (l != NULL)
+		return;
+
+	g_snprintf(outparams, sizeof(outparams),
+			   "FL %s %s", who, who);
+
+	if (!msn_servconn_send_command(session->notification_conn,
+								   "ADD", outparams)) {
+		hide_login_progress(gc, _("Write error"));
+		signoff(gc);
+	}
+}
+
+static void
+msn_rem_buddy(struct gaim_connection *gc, char *who, char *group)
+{
+	MsnSession *session = gc->proto_data;
+	char outparams[MSN_BUF_LEN];
+
+	g_snprintf(outparams, sizeof(outparams), "FL %s", who);
+
+	if (!msn_servconn_send_command(session->notification_conn,
+								   "REM", outparams)) {
+
+		hide_login_progress(gc, _("Write error"));
+		signoff(gc);
+	}
 }
 
-static void msn_set_idle(struct gaim_connection *gc, int idle)
+static void
+msn_add_permit(struct gaim_connection *gc, const char *who)
 {
-	struct msn_data *md = gc->proto_data;
-	char buf[64];
+	MsnSession *session = gc->proto_data;
+	char buf[MSN_BUF_LEN];
+
+	if (!strchr(who, '@')) {
+		g_snprintf(buf, sizeof(buf), 
+			   _("An MSN screenname must be in the form \"user@server.com\". "
+			     "Perhaps you meant %s@hotmail.com. No changes were made "
+				 "to your allow list."), who);
+
+		do_error_dialog(_("Invalid MSN screenname"), buf, GAIM_ERROR);
+		gaim_privacy_permit_remove(gc->account, who);
+
+		return;
+	}
+
+	if (g_slist_find_custom(gc->account->deny, who, (GCompareFunc)strcmp)) {
+		gaim_debug(GAIM_DEBUG_INFO, "msn", "Moving %s from BL to AL\n", who);
+		gaim_privacy_deny_remove(gc->account, who);
+
+		g_snprintf(buf, sizeof(buf), "BL %s", who);
+
+		if (!msn_servconn_send_command(session->notification_conn,
+									   "REM", buf)) {
+
+			hide_login_progress(gc, _("Write error"));
+			signoff(gc);
+			return;
+		}
+	}
+
+	g_snprintf(buf, sizeof(buf), "AL %s %s", who, who);
 
-	if (gc->away)
+	if (!msn_servconn_send_command(session->notification_conn, "ADD", buf)) {
+		hide_login_progress(gc, _("Write error"));
+		signoff(gc);
+	}
+}
+
+static void
+msn_add_deny(struct gaim_connection *gc, const char *who)
+{
+	MsnSession *session = gc->proto_data;
+	char buf[MSN_BUF_LEN];
+
+	if (!strchr(who, '@')) {
+		g_snprintf(buf, sizeof(buf), 
+			   _("An MSN screenname must be in the form \"user@server.com\". "
+			     "Perhaps you meant %s@hotmail.com. No changes were made "
+				 "to your block list."), who);
+
+		do_error_dialog(_("Invalid MSN screenname"), buf, GAIM_ERROR);
+
+		gaim_privacy_deny_remove(gc->account, who);
+
 		return;
-	g_snprintf(buf, sizeof(buf),
-		idle ? "CHG %d IDL\r\n" : "CHG %u NLN\r\n", ++md->trId);
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
+	}
+
+	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);
+
+		g_snprintf(buf, sizeof(buf), "AL %s", who);
+
+		if (!msn_servconn_send_command(session->notification_conn,
+									   "REM", buf)) {
+
+			hide_login_progress(gc, _("Write error"));
+			signoff(gc);
+			return;
+		}
+	}
+
+	g_snprintf(buf, sizeof(buf), "BL %s %s", who, who);
+
+	if (!msn_servconn_send_command(session->notification_conn, "ADD", buf)) {
 		hide_login_progress(gc, _("Write error"));
 		signoff(gc);
 		return;
 	}
 }
 
-static const char *msn_list_icon(struct gaim_account *a, struct buddy *b)
-{
-	return "msn";
-}
-
-static void msn_list_emblems(struct buddy *b, char **se, char **sw, char **nw, char **ne)
-{
-	if (b->present == GAIM_BUDDY_OFFLINE)
-		*se = "offline";
-	else if (b->uc >> 1 == 2 || b->uc >> 1 == 6)
-		*se = "occupied";
-	else if (b->uc)
-		*se = "away";
-}
-
-static char *msn_get_away_text(int s)
+static void
+msn_rem_permit(struct gaim_connection *gc, const char *who)
 {
-	switch (s) {
-		case MSN_BUSY :
-			return _("Busy");
-		case MSN_BRB :
-			return _("Be Right Back");
-		case MSN_AWAY :
-			return _("Away From Computer");
-		case MSN_PHONE :
-			return _("On The Phone");
-		case MSN_LUNCH :
-			return _("Out To Lunch");
-		case MSN_IDLE :
-			return _("Idle");
-		default:
-			return _("Available");
-	}
-}
+	MsnSession *session = gc->proto_data;
+	char buf[MSN_BUF_LEN];
 
-static char *msn_status_text(struct buddy *b) {
-	if (b->uc & UC_UNAVAILABLE)
-		return g_strdup(msn_get_away_text(b->uc >> 1));
-	return NULL;
-}
+	g_snprintf(buf, sizeof(buf), "AL %s", who);
 
-static char *msn_tooltip_text(struct buddy *b) {
-	if (GAIM_BUDDY_IS_ONLINE(b))
-		return g_strdup_printf(_("<b>Status:</b> %s"), msn_get_away_text(b->uc >> 1));
-
-	return NULL;
-}
-
-static GList *msn_buddy_menu(struct gaim_connection *gc, const char *who)
-{
-	GList *m = NULL;
-
-	return m;
-}
+	if (!msn_servconn_send_command(session->notification_conn, "REM", buf)) {
+		hide_login_progress(gc, _("Write error"));
+		signoff(gc);
+		return;
+	}
 
-static void msn_add_buddy(struct gaim_connection *gc, const char *name)
-{
-	struct msn_data *md = gc->proto_data;
-	char *who = msn_normalize(name);
-	char buf[MSN_BUF_LEN];
-	GSList *l = md->fl;
-
-	if (who[0] == '@')
-		/* how did this happen? */
-		return;
+	gaim_privacy_deny_add(gc->account, who);
 
-	if (strchr(who, ' '))
-		/* This is a broken blist entry. */
-		return;
+	g_snprintf(buf, sizeof(buf), "BL %s %s", who, who);
 
-	while (l) {
-		struct msn_buddy *b = l->data;
-		if (!gaim_utf8_strcasecmp(who, b->user))
-			break;
-		l = l->next;
-	}
-	if (l)
-		return;
-
-	g_snprintf(buf, sizeof(buf), "ADD %u FL %s %s\r\n", ++md->trId, who, who);
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
+	if (!msn_servconn_send_command(session->notification_conn, "ADD", buf)) {
 		hide_login_progress(gc, _("Write error"));
 		signoff(gc);
 		return;
 	}
 }
 
-static void msn_rem_buddy(struct gaim_connection *gc, char *who, char *group)
+static void
+msn_rem_deny(struct gaim_connection *gc, const char *who)
 {
-	struct msn_data *md = gc->proto_data;
+	MsnSession *session = gc->proto_data;
 	char buf[MSN_BUF_LEN];
 
-	g_snprintf(buf, sizeof(buf), "REM %u FL %s\r\n", ++md->trId, who);
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-		hide_login_progress(gc, _("Write error"));
-		signoff(gc);
-		return;
-	}
-}
+	g_snprintf(buf, sizeof(buf), "BL %s", who);
 
-static void msn_act_id(gpointer data, char *entry)
-{
-	struct gaim_connection *gc = data;
-	struct msn_data *md = gc->proto_data;
-	char buf[MSN_BUF_LEN];
-	char *alias;
-
-	if (!entry || *entry == '\0') 
-		alias = g_strdup("");
-	else
-		alias = g_strdup(entry);
-	
-	if (strlen(alias) >= BUDDY_ALIAS_MAXLEN) {
-		do_error_dialog(_("New MSN friendly name too long."), NULL, GAIM_ERROR);
-		return;
-	}
-	
-	g_snprintf(buf, sizeof(buf), "REA %u %s %s\r\n", ++md->trId, gc->username, url_encode(alias));
-	g_free(alias);
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
+	if (!msn_servconn_send_command(session->notification_conn, "REM", buf)) {
 		hide_login_progress(gc, _("Write error"));
 		signoff(gc);
 		return;
 	}
-}
 
-static void msn_show_set_friendly_name(struct gaim_connection *gc)
-{
-	do_prompt_dialog(_("Set Friendly Name:"), gc->displayname, gc, msn_act_id, NULL);
-}
-
-static GList *msn_actions(struct gaim_connection *gc)
-{
-	GList *m = NULL;
-	struct proto_actions_menu *pam;
-
-	pam = g_new0(struct proto_actions_menu, 1);
-	pam->label = _("Set Friendly Name");
-	pam->callback = msn_show_set_friendly_name;
-	pam->gc = gc;
-	m = g_list_append(m, pam);
-
-	return m;
-}
+	gaim_privacy_permit_add(gc->account, who);
 
-static void msn_convo_closed(struct gaim_connection *gc, char *who)
-{
-	struct msn_switchboard *ms = msn_find_switch(gc, who);
-
-	if (ms) {
-		char sendbuf[256];
-
-		g_snprintf(sendbuf, sizeof(sendbuf), "BYE %s\r\n", gc->username);
-
-		msn_write(ms->fd, sendbuf, strlen(sendbuf));
+	g_snprintf(buf, sizeof(buf), "AL %s %s", who, who);
 
-		msn_kill_switch(ms);
-	}
-}
-
-static void msn_keepalive(struct gaim_connection *gc)
-{
-	struct msn_data *md = gc->proto_data;
-	char buf[MSN_BUF_LEN];
-
-	g_snprintf(buf, sizeof(buf), "PNG\r\n");
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
+	if (!msn_servconn_send_command(session->notification_conn, "ADD", buf)) {
 		hide_login_progress(gc, _("Write error"));
 		signoff(gc);
 		return;
 	}
 }
 
-static void msn_set_permit_deny(struct gaim_connection *gc)
+static void
+msn_set_permit_deny(struct gaim_connection *gc)
 {
-	struct msn_data *md = gc->proto_data;
+	MsnSession *session = gc->proto_data;
 	char buf[MSN_BUF_LEN];
 	GSList *s, *t = NULL;
 
-	if (gc->account->permdeny == PERMIT_ALL || gc->account->permdeny == DENY_SOME)
-		g_snprintf(buf, sizeof(buf), "BLP %u AL\r\n", ++md->trId);
+	if (gc->account->permdeny == PERMIT_ALL ||
+		gc->account->permdeny == DENY_SOME) {
+
+		strcpy(buf, "AL");
+	}
 	else
-		g_snprintf(buf, sizeof(buf), "BLP %u BL\r\n", ++md->trId);
+		strcpy(buf, "BL");
 
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
+	if (!msn_servconn_send_command(session->notification_conn, "BLP", buf)) {
 		hide_login_progress(gc, _("Write error"));
 		signoff(gc);
 		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(md->permit)) {
-		g_slist_free(md->permit);
-		md->permit = NULL;
+	/*
+	 * 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(md->deny)) {
-		g_slist_free(md->deny);
-		md->deny = 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 (!md->permit && !md->deny)
+
+	if (session->lists.allow == NULL && session->lists.block == NULL)
 		return;
 
-	if (md->permit) {
-		s = g_slist_nth(gc->account->permit, g_slist_length(md->permit));
-		while (s) {
+	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;
-			s = s->next;
+
 			if (!strchr(who, '@')) {
 				t = g_slist_append(t, who);
 				continue;
 			}
-			if (g_slist_find(md->deny, who)) {
+
+			if (g_slist_find(session->lists.block, who)) {
 				t = g_slist_append(t, who);
 				continue;
 			}
-			g_snprintf(buf, sizeof(buf), "ADD %u AL %s %s\r\n", ++md->trId, who, who);
-			if (msn_write(md->fd, buf, strlen(buf)) < 0) {
+
+			g_snprintf(buf, sizeof(buf), "AL %s %s", who, who);
+
+			if (!msn_servconn_send_command(session->notification_conn,
+										   "ADD", buf)) {
 				hide_login_progress(gc, _("Write error"));
 				signoff(gc);
 				return;
 			}
 		}
-		while (t) {
+
+		for (; t != NULL; t = t->next)
 			gaim_privacy_permit_remove(gc->account, t->data);
-			t = t->next;
-		}
-		if (t)
+
+		if (t != NULL)
 			g_slist_free(t);
-	t = NULL;
-	g_slist_free(md->permit);
-	md->permit = NULL;
+
+		t = NULL;
+		g_slist_free(session->lists.allow);
+		session->lists.allow = NULL;
 	}
-	
-	if (md->deny) {
-		s = g_slist_nth(gc->account->deny, g_slist_length(md->deny));
-		while (s) {
+
+	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;
-			s = s->next;
+
 			if (!strchr(who, '@')) {
 				t = g_slist_append(t, who);
 				continue;
 			}
-			if (g_slist_find(md->deny, who)) {
+
+			if (g_slist_find(session->lists.block, who)) {
 				t = g_slist_append(t, who);
 				continue;
 			}
-			g_snprintf(buf, sizeof(buf), "ADD %u BL %s %s\r\n", ++md->trId, who, who);
-			if (msn_write(md->fd, buf, strlen(buf)) < 0) {
+
+			g_snprintf(buf, sizeof(buf), "BL %s %s", who, who);
+
+			if (!msn_servconn_send_command(session->notification_conn,
+										   "ADD", buf)) {
 				hide_login_progress(gc, _("Write error"));
 				signoff(gc);
 				return;
 			}
 		}
-		while (t) {
+
+		for (; t != NULL; t = t->next)
 			gaim_privacy_deny_remove(gc->account, t->data);
-			t = t->next;
-		}
-		if (t)
+
+		if (t != NULL)
 			g_slist_free(t);
-	g_slist_free(md->deny);
-	md->deny = NULL;
+
+		g_slist_free(session->lists.block);
+		session->lists.block = NULL;
 	}
 }
 
-static void msn_add_permit(struct gaim_connection *gc, const char *who)
+static void
+msn_chat_invite(struct gaim_connection *gc, int id, const char *msg,
+				const char *who)
+{
+	MsnSession *session = gc->proto_data;
+	MsnSwitchBoard *swboard = msn_session_find_switch_with_id(session, id);
+
+	if (swboard == NULL)
+		return;
+
+	if (!msn_switchboard_send_command(swboard, "CAL", who))
+		msn_switchboard_destroy(swboard);
+}
+
+static void
+msn_chat_leave(struct gaim_connection *gc, int id)
+{
+	MsnSession *session = gc->proto_data;
+	MsnSwitchBoard *swboard = msn_session_find_switch_with_id(session, id);
+	char buf[6];
+
+	if (swboard == NULL)
+		return;
+
+	strcpy(buf, "OUT\r\n");
+
+	if (!msn_servconn_write(swboard->servconn, buf, strlen(buf)))
+		msn_switchboard_destroy(swboard);
+}
+
+static int
+msn_chat_send(struct gaim_connection *gc, int id, char *message)
 {
-	struct msn_data *md = gc->proto_data;
+	MsnSession *session = gc->proto_data;
+	MsnSwitchBoard *swboard = msn_session_find_switch_with_id(session, id);
+	MsnMessage *msg;
+	char *send;
+
+	if (swboard == NULL)
+		return -EINVAL;
+
+	send = add_cr(message);
+
+	msg = msn_message_new();
+	msn_message_set_attr(msg, "X-MMS-IM-Format", "FN=Arial; EF=; CO=0; PF=0");
+	msn_message_set_body(msg, send);
+
+	g_free(send);
+
+	if (!msn_switchboard_send_msg(swboard, msg)) {
+		msn_switchboard_destroy(swboard);
+
+		msn_message_destroy(msg);
+
+		return 0;
+	}
+
+	msn_message_destroy(msg);
+
+	serv_got_chat_in(gc, id, gc->username, 0, message, time(NULL));
+
+	return 0;
+}
+
+static void
+msn_keepalive(struct gaim_connection *gc)
+{
+	MsnSession *session = gc->proto_data;
 	char buf[MSN_BUF_LEN];
 
-	if (!strchr(who, '@')) {
-		g_snprintf(buf, sizeof(buf), 
-			   _("An MSN screenname must be in the form \"user@server.com\".  "
-			     "Perhaps you meant %s@hotmail.com.  No changes were made to your "
-			     "allow list."), who);
-		do_error_dialog(_("Invalid MSN screenname"), buf, GAIM_ERROR);
-		gaim_privacy_permit_remove(gc->account, who);
-		return;
-	}
+	g_snprintf(buf, sizeof(buf), "PNG\r\n");
 
-	if (g_slist_find_custom(gc->account->deny, who, (GCompareFunc)strcmp)) {
-		gaim_debug(GAIM_DEBUG_INFO, "msn", "Moving %s from BL to AL\n", who);
-		gaim_privacy_deny_remove(gc->account, who);
-		g_snprintf(buf, sizeof(buf), "REM %u BL %s\r\n", ++md->trId, who);
-			if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-				hide_login_progress(gc, _("Write error"));
-				signoff(gc);
-				return;
-			}
-	}
-	g_snprintf(buf, sizeof(buf), "ADD %u AL %s %s\r\n", ++md->trId, who, who);
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
+	if (msn_servconn_write(session->notification_conn,
+						   buf, strlen(buf)) < 0) {
+
 		hide_login_progress(gc, _("Write error"));
 		signoff(gc);
 		return;
 	}
 }
 
-static void msn_rem_permit(struct gaim_connection *gc, const char *who)
+static void
+msn_buddy_free(struct buddy *b)
 {
-	struct msn_data *md = gc->proto_data;
-	char buf[MSN_BUF_LEN];
+	if (b->proto_data != NULL)
+		g_free(b->proto_data);
+}
 
-	g_snprintf(buf, sizeof(buf), "REM %u AL %s\r\n", ++md->trId, who);
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-		hide_login_progress(gc, _("Write error"));
-		signoff(gc);
-		return;
-	}
+static void
+msn_convo_closed(struct gaim_connection *gc, char *who)
+{
+	MsnSession *session = gc->proto_data;
+	MsnSwitchBoard *swboard;
+	
+	swboard = msn_session_find_switch_with_passport(session, who);
 
-	gaim_privacy_deny_add(gc->account, who);
-	g_snprintf(buf, sizeof(buf), "ADD %u BL %s %s\r\n", ++md->trId, who, who);
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-		hide_login_progress(gc, _("Write error"));
-		signoff(gc);
-		return;
+	if (swboard != NULL) {
+		char sendbuf[256];
+
+		g_snprintf(sendbuf, sizeof(sendbuf), "BYE %s\r\n", gc->username);
+
+		msn_servconn_write(swboard->servconn, sendbuf, strlen(sendbuf));
+
+		msn_switchboard_destroy(swboard);
 	}
 }
 
-static void msn_add_deny(struct gaim_connection *gc, const char *who)
+static char *
+msn_normalize(const char *str)
 {
-	struct msn_data *md = gc->proto_data;
-	char buf[MSN_BUF_LEN];
-
-	if (!strchr(who, '@')) {
-		g_snprintf(buf, sizeof(buf), 
-			   _("An MSN screenname must be in the form \"user@server.com\".  "
-			     "Perhaps you meant %s@hotmail.com.  No changes were made to your "
-			     "block list."), who);
-		do_error_dialog(_("Invalid MSN screenname"), buf, GAIM_ERROR);
-		gaim_privacy_deny_remove(gc->account, who);
-		return;
-	}
-
-	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);
-		g_snprintf(buf, sizeof(buf), "REM %u AL %s\r\n", ++md->trId, who);
-		if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-			hide_login_progress(gc, _("Write error"));
-			signoff(gc);
-			return;
-		}
-	}
-
+	static char buf[BUF_LEN];
 
-	g_snprintf(buf, sizeof(buf), "ADD %u BL %s %s\r\n", ++md->trId, who, who);
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-		hide_login_progress(gc, _("Write error"));
-		signoff(gc);
-		return;
-	}
-}
-
-static void msn_rem_deny(struct gaim_connection *gc, const char *who)
-{
-	struct msn_data *md = gc->proto_data;
-	char buf[MSN_BUF_LEN];
+	g_return_val_if_fail(str != NULL, NULL);
 
-	g_snprintf(buf, sizeof(buf), "REM %u BL %s\r\n", ++md->trId, who);
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-		hide_login_progress(gc, _("Write error"));
-		signoff(gc);
-		return;
-	}
+	g_snprintf(buf, sizeof(buf), "%s%s", str,
+			   (strchr(str, '@') ? "" : "@hotmail.com"));
 
-	gaim_privacy_permit_add(gc->account, who);
-	g_snprintf(buf, sizeof(buf), "ADD %u AL %s %s\r\n", ++md->trId, who, who);
-	if (msn_write(md->fd, buf, strlen(buf)) < 0) {
-		hide_login_progress(gc, _("Write error"));
-		signoff(gc);
-		return;
-	}
-}
-
-static void msn_buddy_free(struct buddy *b)
-{
-	if (b->proto_data)
-		g_free(b->proto_data);
+	return buf;
 }
 
 static GaimPluginProtocolInfo prpl_info =
--- a/src/protocols/msn/msn.h	Tue May 06 00:34:54 2003 +0000
+++ b/src/protocols/msn/msn.h	Tue May 06 02:06:56 2003 +0000
@@ -3,7 +3,7 @@
  *
  * gaim
  *
- * Copyright (C) 2003, Christian Hammond <chipx86@gnupdate.org>
+ * 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
@@ -18,7 +18,6 @@
  * 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_H_
 #define _MSN_H_
@@ -42,39 +41,19 @@
 #ifndef _WIN32
 #include <netdb.h>
 #endif
-#include "gaim.h"
+
+#include "blist.h"
+#include "debug.h"
+#include "md5.h"
+#include "proxy.h"
 #include "prpl.h"
-#include "proxy.h"
-#include "md5.h"
 
 #ifdef _WIN32
 #include "win32dep.h"
 #include "stdint.h"
 #endif
 
-#include "msg.h"
-#include "switchboard.h"
-
 #define MSN_BUF_LEN 8192
-#define MIME_HEADER	"MIME-Version: 1.0\r\n" \
-			"Content-Type: text/plain; charset=UTF-8\r\n" \
-			"User-Agent: Gaim/" VERSION "\r\n" \
-			"X-MMS-IM-Format: FN=Arial; EF=; CO=0; PF=0\r\n\r\n"
-
-#define HOTMAIL_URL "http://www.hotmail.com/cgi-bin/folders"
-#define PASSPORT_URL "http://lc1.law13.hotmail.passport.com/cgi-bin/dologin?login="
-
-#define MSN_ONLINE  1
-#define MSN_BUSY    2
-#define MSN_IDLE    3
-#define MSN_BRB     4
-#define MSN_AWAY    5
-#define MSN_PHONE   6
-#define MSN_LUNCH   7
-#define MSN_OFFLINE 8
-#define MSN_HIDDEN  9
-
-#define USEROPT_HOTMAIL 0
 
 #define USEROPT_MSNSERVER 3
 #define MSN_SERVER "messenger.hotmail.com"
@@ -84,70 +63,12 @@
 #define MSN_TYPING_RECV_TIMEOUT 6
 #define MSN_TYPING_SEND_TIMEOUT	4
 
-#define MSN_FT_GUID "{5D3E02AB-6190-11d3-BBBB-00C04F795683}"
+#define HOTMAIL_URL "http://www.hotmail.com/cgi-bin/folders"
+#define PASSPORT_URL "http://lc1.law13.hotmail.passport.com/cgi-bin/dologin?login="
 
-#define GET_NEXT(tmp) \
-	while (*(tmp) && *(tmp) != ' ' && *(tmp) != '\r') \
-		(tmp)++; \
-	*(tmp)++ = 0; \
-	while (*(tmp) && *(tmp) == ' ') \
-		(tmp)++;
+#define USEROPT_HOTMAIL 0
 
 
-struct msn_xfer_data {
-	int inpa;
-
-	guint32 cookie;
-	guint32 authcookie;
-
-	gboolean transferring;
-	gboolean do_cancel;
-
-	char *rxqueue;
-	int rxlen;
-	gboolean msg;
-	char *msguser;
-	int msglen;
-};
-
-struct msn_data {
-	int fd;
-	guint32 trId;
-	int inpa;
-
-	char *rxqueue;
-	int rxlen;
-	gboolean msg;
-	char *msguser;
-	int msglen;
-
-	GSList *switches;
-	GSList *fl;
-	GSList *permit;
-	GSList *deny;
-	GSList *file_transfers;
-
-	char *kv;
-	char *sid;
-	char *mspauth;
-	unsigned long sl;
-	char *passport;
-};
-
-struct msn_buddy {
-	char *user;
-	char *friend;
-};
-
-/**
- * Processes a file transfer message.
- *
- * @param ms  The switchboard.
- * @param msg The message.
- */
-void msn_process_ft_msg(struct msn_switchboard *ms, char *msg);
-
-char *handle_errcode(char *buf, gboolean show);
-char *url_decode(const char *msg);
+#define MSN_FT_GUID "{5D3E02AB-6190-11d3-BBBB-00C04F795683}"
 
 #endif /* _MSN_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/notification.c	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,1061 @@
+/**
+ * @file notification.c Notification server functions
+ *
+ * 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 "notification.h"
+#include "away.h"
+#include "error.h"
+#include "utils.h"
+
+typedef struct
+{
+	struct gaim_connection *gc;
+	MsnUser *user;
+
+} MsnPermitAdd;
+
+static GHashTable *notification_commands  = NULL;
+static GHashTable *notification_msg_types = NULL;
+
+/**************************************************************************
+ * Callbacks
+ **************************************************************************/
+static void
+msn_accept_add_cb(MsnPermitAdd *pa)
+{
+	if (g_slist_find(connections, pa->gc) != NULL) {
+		MsnSession *session = pa->gc->proto_data;
+		char outparams[MSN_BUF_LEN];
+
+		g_snprintf(outparams, sizeof(outparams), "AL %s %s",
+				   msn_user_get_passport(pa->user),
+				   msn_url_encode(msn_user_get_name(pa->user)));
+
+		if (msn_servconn_send_command(session->notification_conn,
+									  "ADD", outparams) <= 0) {
+			hide_login_progress(pa->gc, _("Write error"));
+			signoff(pa->gc);
+			return;
+		}
+
+		gaim_privacy_permit_add(pa->gc->account,
+								msn_user_get_passport(pa->user));
+		build_allow_list();
+
+		show_got_added(pa->gc, NULL, msn_user_get_passport(pa->user),
+					   msn_user_get_name(pa->user), NULL);
+	}
+
+	msn_user_destroy(pa->user);
+	g_free(pa);
+}
+
+static void
+msn_cancel_add_cb(MsnPermitAdd *pa)
+{
+	if (g_slist_find(connections, pa->gc) != NULL) {
+		MsnSession *session = pa->gc->proto_data;
+		char outparams[MSN_BUF_LEN];
+
+		g_snprintf(outparams, sizeof(outparams), "BL %s %s",
+				   msn_user_get_passport(pa->user),
+				   msn_url_encode(msn_user_get_name(pa->user)));
+
+		if (msn_servconn_send_command(session->notification_conn,
+									  "ADD", outparams) <= 0) {
+			hide_login_progress(pa->gc, _("Write error"));
+			signoff(pa->gc);
+			return;
+		}
+
+		gaim_privacy_deny_add(pa->gc->account,
+							  msn_user_get_passport(pa->user));
+		build_block_list();
+	}
+
+	msn_user_destroy(pa->user);
+	g_free(pa);
+}
+
+/**************************************************************************
+ * Catch-all commands
+ **************************************************************************/
+static gboolean
+__blank_cmd(MsnServConn *servconn, const char *command, const char **params,
+			size_t param_count)
+{
+	return TRUE;
+}
+
+static gboolean
+__unknown_cmd(MsnServConn *servconn, const char *command, const char **params,
+			  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+
+	if (isdigit(*command)) {
+		hide_login_progress(gc, (char *)msn_error_get_text(atoi(command)));
+	}
+	else
+		hide_login_progress(gc, _("Unable to parse message."));
+
+	signoff(gc);
+
+	return FALSE;
+}
+
+
+/**************************************************************************
+ * Login
+ **************************************************************************/
+static gboolean
+__ver_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+	size_t i;
+	gboolean msnp5_found = FALSE;
+
+	for (i = 1; i < param_count; i++) {
+		if (!strcmp(params[i], "MSNP5")) {
+			msnp5_found = TRUE;
+			break;
+		}
+	}
+
+	if (!msnp5_found) {
+		hide_login_progress(gc, _("Protocol not supported"));
+		signoff(gc);
+
+		return FALSE;
+	}
+
+	if (!msn_servconn_send_command(servconn, "INF", NULL)) {
+		hide_login_progress(gc, _("Unable to request INF"));
+		signoff(gc);
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+__inf_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+	char outparams[MSN_BUF_LEN];
+
+	if (strcmp(params[1], "MD5")) {
+		hide_login_progress(gc, _("Unable to login using MD5"));
+		signoff(gc);
+
+		return FALSE;
+	}
+
+	g_snprintf(outparams, sizeof(outparams), "MD5 I %s", gc->username);
+
+	if (!msn_servconn_send_command(servconn, "USR", outparams)) {
+		hide_login_progress(gc, _("Unable to send USR"));
+		signoff(gc);
+
+		return FALSE;
+	}
+
+	set_login_progress(gc, 3, _("Requesting to send password"));
+
+	return TRUE;
+}
+
+static gboolean
+__usr_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+	char outparams[MSN_BUF_LEN];
+
+	/* We're either getting the challenge or the OK. Let's find out. */
+	if (!g_ascii_strcasecmp(params[1], "OK")) {
+		/* OK */
+
+		if (!msn_servconn_send_command(servconn, "SYN", "0")) {
+			hide_login_progress(gc, _("Unable to write"));
+			signoff(gc);
+
+			return FALSE;
+		}
+	}
+	else {
+		/* Challenge */
+		const char *challenge = params[3];
+		char buf[MSN_BUF_LEN];
+		md5_state_t st;
+		md5_byte_t di[16];
+		int i;
+
+		g_snprintf(buf, sizeof(buf), "%s%s", challenge, gc->password);
+
+		md5_init(&st);
+		md5_append(&st, (const md5_byte_t *)buf, strlen(buf));
+		md5_finish(&st, di);
+
+		g_snprintf(outparams, sizeof(outparams), "MD5 S ");
+
+		for (i = 0; i < 16; i++) {
+			g_snprintf(buf, sizeof(buf), "%02x", di[i]);
+			strcat(outparams, buf);
+		}
+
+		if (!msn_servconn_send_command(servconn, "USR", outparams)) {
+			hide_login_progress(gc, _("Unable to send password"));
+			signoff(gc);
+
+			return FALSE;
+		}
+
+		set_login_progress(gc, 4, _("Password sent"));
+	}
+
+	return TRUE;
+}
+
+/**************************************************************************
+ * Log out
+ **************************************************************************/
+static gboolean
+__out_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+
+	if (!g_ascii_strcasecmp(params[0], "OTH")) {
+		hide_login_progress(gc,
+							_("You have been disconnected. You have "
+							  "signed on from another location."));
+		signoff(gc);
+	}
+	else if (!g_ascii_strcasecmp(params[0], "SSD")) {
+		hide_login_progress(gc,
+							_("You have been disconnected. The MSN servers "
+							  "are going down temporarily."));
+		signoff(gc);
+	}
+
+	return FALSE;
+}
+
+/**************************************************************************
+ * Messages
+ **************************************************************************/
+static gboolean
+__msg_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	gaim_debug(GAIM_DEBUG_INFO, "msn", "Found message. Parsing.\n");
+
+	servconn->parsing_msg = TRUE;
+	servconn->msg_passport = g_strdup(params[0]);
+	servconn->msg_friendly = g_strdup(params[1]);
+	servconn->msg_len      = atoi(params[2]);
+
+	return TRUE;
+}
+
+/**************************************************************************
+ * Challenges
+ **************************************************************************/
+static gboolean
+__chl_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+	char buf[MSN_BUF_LEN];
+	char buf2[3];
+	md5_state_t st;
+	md5_byte_t di[16];
+	int i;
+
+	md5_init(&st);
+	md5_append(&st, (const md5_byte_t *)params[1], strlen(params[1]));
+	md5_append(&st, (const md5_byte_t *)"Q1P7W2E4J9R8U3S5",
+			   strlen("Q1P7W2E4J9R8U3S5"));
+	md5_finish(&st, di);
+
+	g_snprintf(buf, sizeof(buf),
+			   "QRY %u msmsgs@msnmsgr.com 32\r\n",
+			   servconn->session->trId++);
+
+	for (i = 0; i < 16; i++) {
+		g_snprintf(buf2, sizeof(buf2), "%02x", di[i]);
+		strcat(buf, buf2);
+	}
+
+	if (msn_servconn_write(servconn, buf, strlen(buf)) <= 0) {
+		hide_login_progress(gc, _("Unable to write to server"));
+		signoff(gc);
+	}
+
+	return TRUE;
+}
+
+/**************************************************************************
+ * Buddy Lists
+ **************************************************************************/
+static gboolean
+__add_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	MsnSession *session = servconn->session;
+	struct gaim_connection *gc = session->account->gc;
+	MsnPermitAdd *pa;
+	GSList *sl;
+	const char *list, *passport;
+	char *friend;
+	char msg[MSN_BUF_LEN];
+
+	list = params[1];
+	passport = params[3];
+
+	friend = msn_url_decode(params[4]);
+
+	if (g_ascii_strcasecmp(list, "RL"))
+		return TRUE;
+
+	for (sl = gc->account->permit; sl != NULL; sl = sl->next) {
+		if (!gaim_utf8_strcasecmp(sl->data, passport))
+			return TRUE;
+	}
+
+	pa = g_new0(MsnPermitAdd, 1);
+	pa->user = msn_user_new(session, passport, friend);
+	pa->gc = gc;
+
+	g_snprintf(msg, sizeof(msg),
+			   _("The user %s (%s) wants to add %s to his or her buddy list."),
+			   passport, friend, gc->username);
+
+	do_ask_dialog(msg, NULL, pa,
+				  _("Authorize"), msn_accept_add_cb,
+				  _("Deny"), msn_cancel_add_cb,
+				  session->prpl->handle, FALSE);
+
+	return TRUE;
+}
+
+static gboolean
+__blp_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+
+	if (!g_ascii_strcasecmp(params[2], "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->permdeny = DENY_SOME;
+	}
+	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->permdeny = PERMIT_SOME;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+__fln_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+
+	serv_got_update(gc, (char *)params[0], 0, 0, 0, 0, 0);
+
+	return TRUE;
+}
+
+static gboolean
+__iln_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+	int status = 0;
+	const char *state, *passport, *friend;
+
+	state    = params[1];
+	passport = params[2];
+	friend   = msn_url_decode(params[3]);
+
+	serv_got_alias(gc, (char *)passport, (char *)friend);
+
+	if (!g_ascii_strcasecmp(state, "BSY"))
+		status |= UC_UNAVAILABLE | (MSN_BUSY << 1);
+	else if (!g_ascii_strcasecmp(state, "IDL"))
+		status |= UC_UNAVAILABLE | (MSN_IDLE << 1);
+	else if (!g_ascii_strcasecmp(state, "BRB"))
+		status |= UC_UNAVAILABLE | (MSN_BRB << 1);
+	else if (!g_ascii_strcasecmp(state, "AWY"))
+		status |= UC_UNAVAILABLE | (MSN_AWAY << 1);
+	else if (!g_ascii_strcasecmp(state, "PHN"))
+		status |= UC_UNAVAILABLE | (MSN_PHONE << 1);
+	else if (!g_ascii_strcasecmp(state, "LUN"))
+		status |= UC_UNAVAILABLE | (MSN_LUNCH << 1);
+
+	serv_got_update(gc, (char *)passport, 1, 0, 0, 0, status);
+
+	return TRUE;
+}
+
+static gboolean
+__lsg_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	MsnSession *session = servconn->session;
+	struct group *g;
+	const char *name;
+	int group_num, num_groups, group_id;
+
+	group_num  = atoi(params[2]);
+	num_groups = atoi(params[3]);
+	group_id   = atoi(params[4]);
+	name       = msn_url_decode(params[5]);
+
+	if (group_num == 1) {
+		session->groups = g_hash_table_new_full(g_int_hash, g_int_equal,
+												NULL, g_free);
+	}
+
+	g_hash_table_insert(session->groups, GINT_TO_POINTER(group_id),
+						g_strdup(name));
+
+	if ((g = gaim_find_group(name)) == NULL) {
+		g = gaim_group_new(name);
+		gaim_blist_add_group(g, NULL);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+__lst_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	MsnSession *session = servconn->session;
+	struct gaim_connection *gc = session->account->gc;
+	int user_num;
+	int num_users;
+	const char *type;
+	const char *passport;
+	const char *friend;
+
+	user_num  = atoi(params[3]);
+	num_users = atoi(params[4]);
+
+	if (user_num == 0 && num_users == 0)
+		return TRUE; /* There are no users on this list. */
+
+	type      = params[1];
+	passport  = params[5];
+	friend    = msn_url_decode(params[6]);
+
+	if (!g_ascii_strcasecmp(type, "FL") && user_num != 0) {
+		/* These are users on our contact list. */
+		MsnUser *user;
+
+		user = msn_user_new(session, passport, friend);
+
+		if (param_count == 8)
+			msn_user_set_group_id(user, atoi(params[7]));
+
+		session->lists.forward = g_slist_append(session->lists.forward, user);
+	}
+	else if (!g_ascii_strcasecmp(type, "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);
+		}
+
+		gaim_privacy_permit_add(gc->account, passport);
+	}
+	else if (!g_ascii_strcasecmp(type, "BL") && user_num != 0) {
+		/* These are users who are not allowed to see our status. */
+		gaim_privacy_deny_add(gc->account, passport);
+	}
+	else if (!g_ascii_strcasecmp(type, "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;
+
+				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));
+
+				do_ask_dialog(msg, NULL, pa,
+							  _("Authorize"), msn_accept_add_cb,
+							  _("Deny"), msn_cancel_add_cb,
+							  session->prpl->handle, FALSE);
+			}
+		}
+
+		if (user_num != num_users)
+			return TRUE; /* This isn't the last one in the RL. */
+
+		if (!msn_servconn_send_command(servconn, "CHG", "NLN")) {
+			hide_login_progress(gc, _("Unable to write"));
+			signoff(gc);
+
+			return FALSE;
+		}
+
+		account_online(gc);
+		serv_finish_login(gc);
+
+		session->lists.allow = g_slist_copy(gc->account->permit);
+		session->lists.block = g_slist_copy(gc->account->deny);
+
+		while (session->lists.forward != NULL) {
+			MsnUser *user = session->lists.forward->data;
+			struct buddy *b;
+			
+			b = gaim_find_buddy(gc->account, msn_user_get_passport(user));
+
+			session->lists.forward = g_slist_remove(session->lists.forward,
+													user);
+
+			if (b == NULL) {
+				struct group *g = NULL;
+				const char *group_name = NULL;
+				int group_id;
+
+				group_id = msn_user_get_group_id(user);
+
+				if (group_id > -1) {
+					group_name = g_hash_table_lookup(session->groups,
+							GINT_TO_POINTER(group_id));
+				}
+
+				if (group_name == NULL) {
+					gaim_debug(GAIM_DEBUG_WARNING, "msn",
+							   "Group ID %d for user %s was not defined.\n",
+							   group_id, passport);
+				}
+				else if ((g = gaim_find_group(group_name)) == NULL) {
+					gaim_debug(GAIM_DEBUG_ERROR, "msn",
+							   "Group '%s' appears in server-side "
+							   "buddy list, but not here!",
+							   group_name);
+				}
+
+				if (g == NULL) {
+					if ((g = gaim_find_group(_("Buddies"))) == NULL) {
+						g = gaim_group_new(_("Buddies"));
+						gaim_blist_add_group(g, NULL);
+					}
+				}
+
+				b = gaim_buddy_new(gc->account,
+								   msn_user_get_passport(user), NULL);
+
+				gaim_blist_add_buddy(b, g, NULL);
+			}
+
+			serv_got_alias(gc, (char *)msn_user_get_passport(user),
+						   (char *)msn_user_get_name(user));
+
+			msn_user_destroy(user);
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean
+__nln_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	MsnSession *session = servconn->session;
+	struct gaim_connection *gc = session->account->gc;
+	const char *state;
+	const char *passport;
+	const char *friend;
+	int status = 0;
+
+	state    = params[0];
+	passport = params[1];
+	friend   = msn_url_decode(params[2]);
+
+	serv_got_alias(gc, (char *)passport, (char *)friend);
+
+	if (!g_ascii_strcasecmp(state, "BSY"))
+		status |= UC_UNAVAILABLE | (MSN_BUSY << 1);
+	else if (!g_ascii_strcasecmp(state, "IDL"))
+		status |= UC_UNAVAILABLE | (MSN_IDLE << 1);
+	else if (!g_ascii_strcasecmp(state, "BRB"))
+		status |= UC_UNAVAILABLE | (MSN_BRB << 1);
+	else if (!g_ascii_strcasecmp(state, "AWY"))
+		status |= UC_UNAVAILABLE | (MSN_AWAY << 1);
+	else if (!g_ascii_strcasecmp(state, "PHN"))
+		status |= UC_UNAVAILABLE | (MSN_PHONE << 1);
+	else if (!g_ascii_strcasecmp(state, "LUN"))
+		status |= UC_UNAVAILABLE | (MSN_LUNCH << 1);
+
+	serv_got_update(gc, (char *)passport, 1, 0, 0, 0, status);
+
+	return TRUE;
+}
+
+static gboolean
+__rea_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	MsnSession *session = servconn->session;
+	struct gaim_connection *gc = session->account->gc;
+	char *friend;
+
+	friend = msn_url_decode(params[2]);
+
+	g_snprintf(gc->displayname, sizeof(gc->displayname), "%s", friend);
+
+	return TRUE;
+}
+
+/**************************************************************************
+ * Misc commands
+ **************************************************************************/
+static gboolean
+__url_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	MsnSession *session = servconn->session;
+	struct gaim_connection *gc = session->account->gc;
+	const char *rru;
+	const char *url;
+	md5_state_t st;
+	md5_byte_t di[16];
+	FILE *fd;
+	char buf[2048];
+	char buf2[3];
+	char sendbuf[64];
+	int i;
+
+	rru = params[1];
+	url = params[2];
+
+	g_snprintf(buf, sizeof(buf), "%s%lu%s",
+			   session->passport_info.mspauth,
+			   time(NULL) - session->passport_info.sl, gc->password);
+
+	md5_init(&st);
+	md5_append(&st, (const md5_byte_t *)buf, strlen(buf));
+	md5_finish(&st, di);
+
+	memset(sendbuf, 0, sizeof(sendbuf));
+
+	for (i = 0; i < 16; i++) {
+		g_snprintf(buf2, sizeof(buf2), "%02x", di[i]);
+		strcat(sendbuf, buf2);
+	}
+
+	if (session->passport_info.file != NULL) {
+		unlink(session->passport_info.file);
+		g_free(session->passport_info.file);
+	}
+
+	if ((fd = gaim_mkstemp(&session->passport_info.file)) == NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "msn",
+				   "Error opening temp passport file: %s\n",
+				   strerror(errno));
+	}
+	else {
+		fputs("<html>\n"
+			  "<head>\n"
+			  "<noscript>\n"
+			  "<meta http-equiv=\"Refresh\" content=\"0; "
+			        "url=http://www.hotmail.com\">\n"
+			  "</noscript>\n"
+			  "</head>\n\n",
+			  fd);
+
+		fprintf(fd, "<body onload=\"document.pform.submit(); \">\n");
+		fprintf(fd, "<form name=\"pform\" action=\"%s\" method=\"POST\">\n\n",
+				url);
+		fprintf(fd, "<input type=\"hidden\" name=\"mode\" value=\"ttl\">\n");
+		fprintf(fd, "<input type=\"hidden\" name=\"login\" value=\"%s\">\n",
+				gc->username);
+		fprintf(fd, "<input type=\"hidden\" name=\"username\" value=\"%s\">\n",
+				gc->username);
+		fprintf(fd, "<input type=\"hidden\" name=\"sid\" value=\"%s\">\n",
+				session->passport_info.sid);
+		fprintf(fd, "<input type=\"hidden\" name=\"kv\" value=\"%s\">\n",
+				session->passport_info.kv);
+		fprintf(fd, "<input type=\"hidden\" name=\"id\" value=\"2\">\n");
+		fprintf(fd, "<input type=\"hidden\" name=\"sl\" value=\"%ld\">\n",
+				time(NULL) - session->passport_info.sl);
+		fprintf(fd, "<input type=\"hidden\" name=\"rru\" value=\"%s\">\n",
+				rru);
+		fprintf(fd, "<input type=\"hidden\" name=\"auth\" value=\"%s\">\n",
+				session->passport_info.mspauth);
+		fprintf(fd, "<input type=\"hidden\" name=\"creds\" value=\"%s\">\n",
+				sendbuf); /* TODO Digest me (huh? -- ChipX86) */
+		fprintf(fd, "<input type=\"hidden\" name=\"svc\" value=\"mail\">\n");
+		fprintf(fd, "<input type=\"hiden\" name=\"js\" value=\"yes\">\n");
+		fprintf(fd, "</form></body>\n");
+		fprintf(fd, "</html>\n");
+
+		if (fclose(fd)) {
+			gaim_debug(GAIM_DEBUG_ERROR, "msn",
+					   "Error closing temp passport file: %s\n",
+					   strerror(errno));
+
+			unlink(session->passport_info.file);
+			g_free(session->passport_info.file);
+		}
+		else {
+			/*
+			 * Renaming file with .html extension, so that the
+			 * win32 open_url will work.
+			 */
+			char *tmp;
+
+			if ((tmp = g_strdup_printf("%s.html",
+					session->passport_info.file)) != NULL) {
+
+				if (rename(session->passport_info.file, tmp) == 0) {
+					g_free(session->passport_info.file);
+					session->passport_info.file = tmp;
+				}
+				else
+					g_free(tmp);
+			}
+		}
+	}
+
+	return TRUE;
+}
+/**************************************************************************
+ * Switchboards
+ **************************************************************************/
+static gboolean
+__rng_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	MsnSession *session = servconn->session;
+	MsnSwitchBoard *swboard;
+	MsnUser *user;
+	const char *session_id;
+	char *host, *c;
+	int port;
+
+	session_id = params[0];
+
+	host = g_strdup(params[1]);
+
+	if ((c = strchr(host, ':')) != NULL) {
+		*c = '\0';
+		port = atoi(c + 1);
+	}
+	else
+		port = 1863;
+
+	swboard = msn_switchboard_new(session);
+
+	user = msn_user_new(session, params[4], NULL);
+
+	msn_switchboard_set_invited(swboard, TRUE);
+	msn_switchboard_set_session_id(swboard, params[0]);
+	msn_switchboard_set_auth_key(swboard, params[3]);
+	msn_switchboard_set_user(swboard, user);
+
+	if (!msn_switchboard_connect(swboard, host, port)) {
+		gaim_debug(GAIM_DEBUG_ERROR, "msn",
+				   "Unable to connect to switchboard on %s, port %d\n",
+				   host, port);
+
+		g_free(host);
+
+		return FALSE;
+	}
+
+	g_free(host);
+
+	return TRUE;
+}
+
+static gboolean
+__xfr_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	MsnSession *session = servconn->session;
+	MsnSwitchBoard *swboard;
+	struct gaim_connection *gc = session->account->gc;
+	char *host;
+	char *c;
+	int port;
+
+	if (strcmp(params[1], "SB")) {
+		hide_login_progress(gc, _("Got invalid XFR"));
+		signoff(gc);
+		
+		return FALSE;
+	}
+
+	host = g_strdup(params[2]);
+
+	if ((c = strchr(host, ':')) != NULL) {
+		*c = '\0';
+		port = atoi(c + 1);
+	}
+	else
+		port = 1863;
+
+	swboard = msn_session_find_unused_switch(session);
+
+	if (swboard == NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "msn",
+				   "Received an XFR SB request when there's no unused "
+				   "switchboards!\n");
+	}
+
+	msn_switchboard_set_auth_key(swboard, params[4]);
+
+	if (!msn_switchboard_connect(swboard, host, port)) {
+		gaim_debug(GAIM_DEBUG_ERROR, "msn",
+				   "Unable to connect to switchboard on %s, port %d\n",
+				   host, port);
+
+		g_free(host);
+
+		return FALSE;
+	}
+
+	g_free(host);
+
+	return TRUE;
+}
+
+/**************************************************************************
+ * Message Types
+ **************************************************************************/
+static gboolean
+__profile_msg(MsnServConn *servconn, const MsnMessage *msg)
+{
+	MsnSession *session = servconn->session;
+	const char *value;
+
+	if ((value = msn_message_get_attr(msg, "kv")) != NULL)
+		session->passport_info.kv = g_strdup(value);
+
+	if ((value = msn_message_get_attr(msg, "sid")) != NULL)
+		session->passport_info.sid = g_strdup(value);
+
+	if ((value = msn_message_get_attr(msg, "MSPAuth")) != NULL)
+		session->passport_info.mspauth = g_strdup(value);
+
+	return TRUE;
+}
+
+static gboolean
+__initial_email_msg(MsnServConn *servconn, const MsnMessage *msg)
+{
+	MsnSession *session = servconn->session;
+	struct gaim_connection *gc = session->account->gc;
+	GHashTable *table;
+	const char *unread;
+
+	table = msn_message_get_hashtable_from_body(msg);
+
+	unread = g_hash_table_lookup(table, "Inbox-Unread");
+
+	if (unread != NULL)
+		connection_has_mail(gc, atoi(unread), NULL, NULL,
+							session->passport_info.file);
+
+	g_hash_table_destroy(table);
+
+	return TRUE;
+}
+
+static gboolean
+__email_msg(MsnServConn *servconn, const MsnMessage *msg)
+{
+	MsnSession *session = servconn->session;
+	struct gaim_connection *gc = session->account->gc;
+	GHashTable *table;
+	const char *from, *subject;
+
+	table = msn_message_get_hashtable_from_body(msg);
+
+	from    = g_hash_table_lookup(table, "From");
+	subject = g_hash_table_lookup(table, "Subject");
+
+	if (from == NULL || subject == NULL) {
+		connection_has_mail(gc, 1, NULL, NULL, session->passport_info.file);
+	}
+	else {
+		connection_has_mail(gc, -1, from, subject,
+							session->passport_info.file);
+	}
+
+	g_hash_table_destroy(table);
+
+	return TRUE;
+}
+
+static gboolean
+__connect_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnServConn *notification = data;
+	MsnSession *session = notification->session;
+	struct gaim_connection *gc = session->account->gc;
+
+	if (source == -1) {
+		hide_login_progress(session->account->gc, _("Unable to connect"));
+		signoff(session->account->gc);
+		return FALSE;
+	}
+
+	if (notification->fd != source)
+		notification->fd = source;
+
+	if (!msn_servconn_send_command(notification, "VER",
+								   "MSNP6 MSNP5 MSNP4 CVR0")) {
+		hide_login_progress(gc, _("Unable to write to server"));
+		signoff(gc);
+		return FALSE;
+	}
+
+	set_login_progress(session->account->gc, 2, _("Syncing with server"));
+
+	return TRUE;
+}
+
+static void
+__failed_read_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnServConn *notification = data;
+	struct gaim_connection *gc;
+
+	gc = notification->session->account->gc;
+
+	hide_login_progress(gc, _("Error reading from server"));
+	signoff(gc);
+}
+
+MsnServConn *
+msn_notification_new(MsnSession *session, const char *server, int port)
+{
+	MsnServConn *notification;
+
+	notification = msn_servconn_new(session);
+
+	msn_servconn_set_server(notification, server, port);
+	msn_servconn_set_connect_cb(notification, __connect_cb);
+	msn_servconn_set_failed_read_cb(notification, __failed_read_cb);
+
+	if (notification_commands == NULL) {
+		/* Register the command callbacks. */
+		msn_servconn_register_command(notification, "ADD",       __add_cmd);
+		msn_servconn_register_command(notification, "BLP",       __blp_cmd);
+		msn_servconn_register_command(notification, "BPR",       __blank_cmd);
+		msn_servconn_register_command(notification, "CHG",       __blank_cmd);
+		msn_servconn_register_command(notification, "CHL",       __chl_cmd);
+		msn_servconn_register_command(notification, "FLN",       __fln_cmd);
+		msn_servconn_register_command(notification, "GTC",       __blank_cmd);
+		msn_servconn_register_command(notification, "ILN",       __iln_cmd);
+		msn_servconn_register_command(notification, "INF",       __inf_cmd);
+		msn_servconn_register_command(notification, "LSG",       __lsg_cmd);
+		msn_servconn_register_command(notification, "LST",       __lst_cmd);
+		msn_servconn_register_command(notification, "MSG",       __msg_cmd);
+		msn_servconn_register_command(notification, "NLN",       __nln_cmd);
+		msn_servconn_register_command(notification, "OUT",       __out_cmd);
+		msn_servconn_register_command(notification, "PRP",       __blank_cmd);
+		msn_servconn_register_command(notification, "QNG",       __blank_cmd);
+		msn_servconn_register_command(notification, "QRY",       __blank_cmd);
+		msn_servconn_register_command(notification, "REA",       __rea_cmd);
+		msn_servconn_register_command(notification, "REM",       __blank_cmd);
+		msn_servconn_register_command(notification, "RNG",       __rng_cmd);
+		msn_servconn_register_command(notification, "SYN",       __blank_cmd);
+		msn_servconn_register_command(notification, "URL",       __url_cmd);
+		msn_servconn_register_command(notification, "USR",       __usr_cmd);
+		msn_servconn_register_command(notification, "VER",       __ver_cmd);
+		msn_servconn_register_command(notification, "XFR",       __xfr_cmd);
+		msn_servconn_register_command(notification, "_unknown_", __unknown_cmd);
+
+		/* Register the message type callbacks. */
+		msn_servconn_register_msg_type(notification, "text/x-msmsgsprofile",
+									   __profile_msg);
+		msn_servconn_register_msg_type(notification,
+									   "text/x-msmsgsinitialemailnotification",
+									   __initial_email_msg);
+		msn_servconn_register_msg_type(notification,
+									   "text/x-msmsgsemailnotification",
+									   __email_msg);
+
+		/* Save these for future use. */
+		notification_commands  = notification->commands;
+		notification_msg_types = notification->msg_types;
+	}
+	else {
+		g_hash_table_destroy(notification->commands);
+		g_hash_table_destroy(notification->msg_types);
+
+		notification->commands  = notification_commands;
+		notification->msg_types = notification_msg_types;
+	}
+
+	return notification;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/notification.h	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,31 @@
+/**
+ * @file notification.h Notification server functions
+ *
+ * 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_NOTIFICATION_H_
+#define _MSN_NOTIFICATION_H_
+
+#include "session.h"
+#include "servconn.h"
+
+MsnServConn *msn_notification_new(MsnSession *session, const char *server,
+								  int port);
+
+#endif /* _MSN_NOTIFICATION_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/servconn.c	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,394 @@
+/**
+ * @file servconn.c Server connection functions
+ *
+ * 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 "servconn.h"
+
+static gboolean
+__process_single_line(MsnServConn *servconn, char *str)
+{
+	MsnServConnCommandCb cb;
+	gboolean result;
+	size_t param_count = 0;
+	char *command, *param_start;
+	char **params = NULL;
+
+	command = str;
+
+	/**
+	 * See how many spaces we have in this.
+	 */
+	param_start = strchr(command, ' ');
+
+	if (param_start != NULL) {
+		params = g_strsplit(param_start + 1, " ", 0);
+
+		for (param_count = 0; params[param_count] != NULL; param_count++)
+			;
+
+		*param_start = '\0';
+	}
+
+	cb = g_hash_table_lookup(servconn->commands, command);
+
+	if (cb == NULL) {
+		cb = g_hash_table_lookup(servconn->commands, "_unknown_");
+
+		if (cb == NULL) {
+			gaim_debug(GAIM_DEBUG_WARNING, "msn",
+					   "Unhandled command '%s'\n", str);
+
+			if (params != NULL)
+				g_strfreev(params);
+
+			return TRUE;
+		}
+	}
+
+	result = cb(servconn, command, (const char **)params, param_count);
+
+	if (params != NULL)
+		g_strfreev(params);
+
+	return result;
+}
+
+static gboolean
+__process_multi_line(MsnServConn *servconn, char *buffer)
+{
+	MsnServConnMsgCb cb;
+	MsnMessage *msg;
+	char msg_str[MSN_BUF_LEN];
+
+	g_snprintf(msg_str, sizeof(msg_str),
+			   "MSG %s %s %d\r\n%s",
+			   servconn->msg_passport, servconn->msg_friendly,
+			   servconn->msg_len, buffer);
+
+	msg = msn_message_new_from_str(servconn->session, msg_str);
+
+	cb = g_hash_table_lookup(servconn->msg_types,
+							 msn_message_get_content_type(msg));
+
+	if (cb == NULL) {
+		gaim_debug(GAIM_DEBUG_WARNING, "msn",
+				   "Unhandled content-type '%s': %s\n",
+				   msn_message_get_content_type(msg),
+				   msn_message_get_body(msg));
+
+		msn_message_destroy(msg);
+
+		return FALSE;
+	}
+
+	cb(servconn, msg);
+
+	msn_message_destroy(msg);
+
+	return TRUE;
+}
+
+static void
+__connect_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnServConn *servconn = data;
+
+	if (servconn->connect_cb(data, source, cond))
+		servconn->inpa = gaim_input_add(servconn->fd, GAIM_INPUT_READ,
+										servconn->login_cb, data);
+}
+
+MsnServConn *
+msn_servconn_new(MsnSession *session)
+{
+	MsnServConn *servconn;
+
+	g_return_val_if_fail(session != NULL, NULL);
+
+	servconn = g_new0(MsnServConn, 1);
+
+	servconn->login_cb = msn_servconn_parse_data;
+	servconn->session = session;
+
+	servconn->commands = g_hash_table_new_full(g_str_hash, g_str_equal,
+											   g_free, NULL);
+
+	servconn->msg_types = g_hash_table_new_full(g_str_hash, g_str_equal,
+												g_free, NULL);
+
+	return servconn;
+}
+
+gboolean
+msn_servconn_connect(MsnServConn *servconn)
+{
+	int i;
+
+	g_return_val_if_fail(servconn != NULL, FALSE);
+	g_return_val_if_fail(servconn->server != NULL, FALSE);
+	g_return_val_if_fail(!servconn->connected, TRUE);
+
+	i = proxy_connect(servconn->session->account, servconn->server,
+					  servconn->port, __connect_cb, servconn);
+
+	if (i == 0)
+		servconn->connected = TRUE;
+
+	return servconn->connected;
+}
+
+void
+msn_servconn_disconnect(MsnServConn *servconn)
+{
+	g_return_if_fail(servconn != NULL);
+	g_return_if_fail(servconn->connected);
+
+	close(servconn->fd);
+
+	if (servconn->inpa)
+		gaim_input_remove(servconn->inpa);
+
+	g_free(servconn->rxqueue);
+
+	while (servconn->txqueue) {
+		g_free(servconn->txqueue->data);
+
+		servconn->txqueue = g_slist_remove(servconn->txqueue,
+										   servconn->txqueue->data);
+	}
+
+	servconn->connected = FALSE;
+}
+
+void
+msn_servconn_destroy(MsnServConn *servconn)
+{
+	g_return_if_fail(servconn != NULL);
+
+	if (servconn->connected)
+		msn_servconn_disconnect(servconn);
+
+	if (servconn->server != NULL)
+		g_free(servconn->server);
+
+	g_free(servconn);
+}
+
+void
+msn_servconn_set_server(MsnServConn *servconn, const char *server, int port)
+{
+	g_return_if_fail(servconn != NULL);
+	g_return_if_fail(server != NULL);
+	g_return_if_fail(port > 0);
+
+	if (servconn->server != NULL)
+		g_free(servconn->server);
+
+	servconn->server = g_strdup(server);
+	servconn->port   = port;
+}
+
+const char *
+msn_servconn_get_server(const MsnServConn *servconn)
+{
+	g_return_val_if_fail(servconn != NULL, NULL);
+
+	return servconn->server;
+}
+
+int
+msn_servconn_get_port(const MsnServConn *servconn)
+{
+	g_return_val_if_fail(servconn != NULL, 0);
+
+	return servconn->port;
+}
+
+void
+msn_servconn_set_connect_cb(MsnServConn *servconn,
+							gboolean (*connect_cb)(gpointer, gint,
+												   GaimInputCondition))
+{
+	g_return_if_fail(servconn != NULL);
+
+	servconn->connect_cb = connect_cb;
+}
+
+void
+msn_servconn_set_failed_read_cb(MsnServConn *servconn,
+								void (*failed_read_cb)(gpointer, gint,
+													   GaimInputCondition))
+{
+	g_return_if_fail(servconn != NULL);
+
+	servconn->failed_read_cb = failed_read_cb;
+}
+
+size_t
+msn_servconn_write(MsnServConn *servconn, const char *buf, size_t size)
+{
+	g_return_val_if_fail(servconn != NULL, 0);
+
+	gaim_debug(GAIM_DEBUG_MISC, "msn", "C: %s%s", buf,
+			   (*(buf + size - 1) == '\n' ? "" : "\n"));
+
+	return write(servconn->fd, buf, size);
+}
+
+gboolean
+msn_servconn_send_command(MsnServConn *servconn, const char *command,
+						  const char *params)
+{
+	char buf[MSN_BUF_LEN];
+
+	g_return_val_if_fail(servconn != NULL, FALSE);
+	g_return_val_if_fail(command != NULL, FALSE);
+
+	if (params == NULL)
+		g_snprintf(buf, sizeof(buf), "%s %u\r\n", command,
+				   servconn->session->trId++);
+	else
+		g_snprintf(buf, sizeof(buf), "%s %u %s\r\n",
+				   command, servconn->session->trId++, params);
+
+	return (msn_servconn_write(servconn, buf, strlen(buf)) > 0);
+}
+
+void
+msn_servconn_register_command(MsnServConn *servconn, const char *command,
+							  MsnServConnCommandCb cb)
+{
+	char *command_up;
+
+	g_return_if_fail(servconn != NULL);
+	g_return_if_fail(command != NULL);
+	g_return_if_fail(cb != NULL);
+
+	command_up = g_ascii_strup(command, -1);
+
+	g_hash_table_insert(servconn->commands, command_up, cb);
+}
+
+void
+msn_servconn_register_msg_type(MsnServConn *servconn,
+							   const char *content_type,
+							   MsnServConnMsgCb cb)
+{
+	g_return_if_fail(servconn != NULL);
+	g_return_if_fail(content_type != NULL);
+	g_return_if_fail(cb != NULL);
+
+	g_hash_table_insert(servconn->msg_types, g_strdup(content_type), cb);
+}
+
+void
+msn_servconn_parse_data(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnServConn *servconn = (MsnServConn *)data;
+	char buf[MSN_BUF_LEN];
+	gboolean cont = TRUE;
+	int len;
+
+	len = read(servconn->fd, buf, sizeof(buf));
+
+	if (len <= 0) {
+		if (servconn->failed_read_cb != NULL)
+			servconn->failed_read_cb(data, source, cond);
+
+		return;
+	}
+
+	servconn->rxqueue = g_realloc(servconn->rxqueue, len + servconn->rxlen);
+	memcpy(servconn->rxqueue + servconn->rxlen, buf, len);
+	servconn->rxlen += len;
+
+	while (cont) {
+		if (servconn->parsing_msg) {
+			char *msg;
+
+			if (servconn->rxlen == 0)
+				break;
+
+			if (servconn->msg_len > servconn->rxlen)
+				break;
+
+			msg = servconn->rxqueue;
+			servconn->rxlen -= servconn->msg_len;
+
+			if (servconn->rxlen) {
+				servconn->rxqueue = g_memdup(msg + servconn->msg_len,
+											 servconn->rxlen);
+			}
+			else {
+				servconn->rxqueue = NULL;
+				msg = g_realloc(msg, servconn->msg_len + 1);
+			}
+
+			msg[servconn->msg_len] = '\0';
+			servconn->parsing_msg = FALSE;
+
+			__process_multi_line(servconn, msg);
+
+			servconn->msg_len = 0;
+			g_free(servconn->msg_passport);
+			g_free(servconn->msg_friendly);
+			g_free(msg);
+		}
+		else {
+			char *end = servconn->rxqueue;
+			char *cmd;
+			int cmdlen, i;
+
+			if (!servconn->rxlen)
+				return;
+
+			for (i = 0; i < servconn->rxlen - 1; end++, i++) {
+				if (*end == '\r' && end[1] == '\n')
+					break;
+			}
+
+			if (i == servconn->rxlen - 1)
+				return;
+
+			cmdlen = end - servconn->rxqueue + 2;
+			cmd = servconn->rxqueue;
+			servconn->rxlen -= cmdlen;
+
+			if (servconn->rxlen)
+				servconn->rxqueue = g_memdup(cmd + cmdlen, servconn->rxlen);
+			else {
+				servconn->rxqueue = NULL;
+				cmd = g_realloc(cmd, cmdlen + 1);
+			}
+
+			cmd[cmdlen] = '\0';
+
+			gaim_debug(GAIM_DEBUG_MISC, "msn", "S: %s", cmd);
+
+			g_strchomp(cmd);
+
+			cont = __process_single_line(servconn, cmd);
+
+			g_free(cmd);
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/servconn.h	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,106 @@
+/**
+ * @file servconn.h Server connection functions
+ *
+ * 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_SERVCONN_H_
+#define _MSN_SERVCONN_H_
+
+typedef struct _MsnServConn MsnServConn;
+
+#include "msg.h"
+
+typedef gboolean (*MsnServConnCommandCb)(MsnServConn *servconn,
+										 const char *cmd, const char **params,
+										 size_t param_count);
+
+typedef gboolean (*MsnServConnMsgCb)(MsnServConn *servconn,
+									 const MsnMessage *msg);
+
+#include "session.h"
+
+struct _MsnServConn
+{
+	MsnSession *session;
+
+	gboolean connected;
+
+	char *server;
+	int port;
+
+	int fd;
+	int inpa;
+
+	char *rxqueue;
+	int rxlen;
+
+	GSList *txqueue;
+
+	gboolean parsing_msg;
+	char *msg_passport;
+	char *msg_friendly;
+	int msg_len;
+
+	GHashTable *commands;
+	GHashTable *msg_types;
+
+	gboolean (*connect_cb)(gpointer, gint, GaimInputCondition);
+	void (*failed_read_cb)(gpointer, gint, GaimInputCondition);
+	void (*login_cb)(gpointer, gint, GaimInputCondition);
+
+	void *data;
+};
+
+MsnServConn *msn_servconn_new(MsnSession *session);
+
+void msn_servconn_destroy(MsnServConn *servconn);
+
+gboolean msn_servconn_connect(MsnServConn *servconn);
+void msn_servconn_disconnect(MsnServConn *servconn);
+
+void msn_servconn_set_server(MsnServConn *servconn, const char *server,
+							 int port);
+
+const char *msn_servconn_get_server(const MsnServConn *servconn);
+int msn_servconn_get_port(const MsnServConn *servconn);
+
+void msn_servconn_set_connect_cb(MsnServConn *servconn,
+		gboolean (*connect_cb)(gpointer, gint, GaimInputCondition));
+
+void msn_servconn_set_failed_read_cb(MsnServConn *servconn,
+		void (*failed_read_cb)(gpointer, gint, GaimInputCondition));
+
+size_t msn_servconn_write(MsnServConn *servconn, const char *buf,
+						  size_t size);
+
+gboolean msn_servconn_send_command(MsnServConn *servconn, const char *command,
+								   const char *params);
+
+void msn_servconn_register_command(MsnServConn *servconn,
+								   const char *command,
+								   MsnServConnCommandCb cb);
+
+void msn_servconn_register_msg_type(MsnServConn *servconn,
+									const char *content_type,
+									MsnServConnMsgCb cb);
+
+void msn_servconn_parse_data(gpointer data, gint source,
+							 GaimInputCondition cond);
+
+#endif /* _MSN_SERVCONN_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/session.c	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,185 @@
+/**
+ * @file session.c MSN session functions
+ *
+ * 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 "session.h"
+#include "dispatch.h"
+
+MsnSession *
+msn_session_new(struct gaim_account *account, const char *server, int port)
+{
+	MsnSession *session;
+
+	g_return_val_if_fail(account != NULL, NULL);
+
+	session = g_new0(MsnSession, 1);
+
+	session->account         = account;
+	session->dispatch_server = g_strdup(server);
+	session->dispatch_port   = port;
+
+	session->users = msn_users_new();
+
+	return session;
+}
+
+void
+msn_session_destroy(MsnSession *session)
+{
+	g_return_if_fail(session != NULL);
+
+	if (session->connected)
+		msn_session_disconnect(session);
+
+	if (session->dispatch_server != NULL)
+		g_free(session->dispatch_server);
+
+	while (session->switches != NULL)
+		msn_switchboard_destroy(session->switches->data);
+
+	while (session->lists.forward)
+		msn_user_destroy(session->lists.forward->data);
+
+	g_slist_free(session->lists.allow);
+	g_slist_free(session->lists.block);
+
+	msn_users_destroy(session->users);
+
+	g_free(session);
+}
+
+gboolean
+msn_session_connect(MsnSession *session)
+{
+	g_return_val_if_fail(session != NULL, FALSE);
+	g_return_val_if_fail(!session->connected, TRUE);
+
+	session->connected = TRUE;
+
+	session->dispatch_conn = msn_dispatch_new(session,
+											  session->dispatch_server,
+											  session->dispatch_port);
+
+	if (msn_servconn_connect(session->dispatch_conn))
+		return TRUE;
+
+	return FALSE;
+}
+
+void
+msn_session_disconnect(MsnSession *session)
+{
+	g_return_if_fail(session != NULL);
+	g_return_if_fail(session->connected);
+
+	if (session->dispatch_conn != NULL) {
+		msn_servconn_destroy(session->dispatch_conn);
+		session->dispatch_conn = NULL;
+	}
+
+	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;
+	}
+}
+
+MsnSwitchBoard *
+msn_session_open_switchboard(MsnSession *session)
+{
+	MsnSwitchBoard *swboard;
+
+	g_return_val_if_fail(session != NULL, NULL);
+
+	if (msn_servconn_send_command(session->notification_conn,
+								  "XFR", "SB") < 0) {
+		return NULL;
+	}
+
+	swboard = msn_switchboard_new(session);
+
+	return swboard;
+}
+
+MsnSwitchBoard *
+msn_session_find_switch_with_passport(const MsnSession *session,
+									  const char *passport)
+{
+	GList *l;
+	MsnSwitchBoard *swboard;
+
+	g_return_val_if_fail(session != NULL, NULL);
+	g_return_val_if_fail(passport != NULL, NULL);
+
+	for (l = session->switches; l != NULL; l = l->next) {
+		swboard = (MsnSwitchBoard *)l->data;
+
+		if (!g_ascii_strcasecmp(passport,
+								msn_user_get_passport(swboard->user))) {
+			return swboard;
+		}
+	}
+
+	return NULL;
+}
+
+MsnSwitchBoard *
+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);
+
+	for (l = session->switches; l != NULL; l = l->next) {
+		swboard = (MsnSwitchBoard *)l->data;
+
+		if (swboard->chat_id == chat_id)
+			return swboard;
+	}
+
+	return NULL;
+}
+
+MsnSwitchBoard *
+msn_session_find_unused_switch(const MsnSession *session)
+{
+	GList *l;
+	MsnSwitchBoard *swboard;
+
+	g_return_val_if_fail(session != NULL, NULL);
+
+	for (l = session->switches; l != NULL; l = l->next) {
+		swboard = (MsnSwitchBoard *)l->data;
+
+		if (!swboard->in_use)
+			return swboard;
+	}
+
+	return NULL;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/session.h	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,148 @@
+/**
+ * @file session.h MSN session functions
+ *
+ * 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_SESSION_H_
+#define _MSN_SESSION_H_
+
+typedef struct _MsnSession MsnSession;
+
+#include "servconn.h"
+#include "switchboard.h"
+#include "user.h"
+
+struct _MsnSession
+{
+	struct gaim_account *account;
+
+	char *dispatch_server;
+	int dispatch_port;
+
+	gboolean connected;
+
+	MsnServConn *dispatch_conn;
+	MsnServConn *notification_conn;
+
+	unsigned int trId;
+
+	MsnUsers *users;
+
+	GList *switches;
+	GHashTable *groups;
+
+	struct
+	{
+		GSList *forward;
+		GSList *reverse;
+		GSList *allow;
+		GSList *block;
+
+	} lists;
+
+	struct
+	{
+		char *kv;
+		char *sid;
+		char *mspauth;
+		unsigned long sl;
+		char *file;
+
+	} passport_info;
+
+	/* You have no idea how much I hate this. */
+	GaimPlugin *prpl;
+};
+
+/**
+ * Creates an MSN session.
+ *
+ * @param account The account.
+ * @param server  The dispatch server.
+ * @param port    The dispatch port.
+ *
+ * @return The new MSN session.
+ */
+MsnSession *msn_session_new(struct gaim_account *account,
+							const char *server, int port);
+
+/**
+ * Destroys an MSN session.
+ *
+ * @param session The MSN session to destroy.
+ */
+void msn_session_destroy(MsnSession *session);
+
+/**
+ * Connects to and initiates an MSN session.
+ *
+ * @param session The MSN session.
+ *
+ * @return @c TRUE on success, @c FALSE on failure.
+ */
+gboolean msn_session_connect(MsnSession *session);
+
+/**
+ * Disconnects from an MSN session.
+ *
+ * @param session The MSN session.
+ */
+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);
+
+/**
+ * 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.
+ * @param chat_id The chat ID to search for.
+ *
+ * @return The switchboard, if found.
+ */
+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);
+
+#endif /* _MSN_SESSION_H_ */
--- a/src/protocols/msn/switchboard.c	Tue May 06 00:34:54 2003 +0000
+++ b/src/protocols/msn/switchboard.c	Tue May 06 02:06:56 2003 +0000
@@ -17,518 +17,535 @@
  *
  * 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
- *
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 #include "msn.h"
+#include "switchboard.h"
+#include "utils.h"
 
-#ifdef _WIN32
-#include "win32dep.h"
-#endif
+static GHashTable *switchboard_commands  = NULL;
+static GHashTable *switchboard_msg_types = NULL;
+
 
-G_MODULE_IMPORT GSList *connections;
+/**************************************************************************
+ * Catch-all commands
+ **************************************************************************/
+static gboolean
+__blank_cmd(MsnServConn *servconn, const char *command, const char **params,
+			size_t param_count)
+{
+	return TRUE;
+}
 
-static char *
-msn_parse_format(char *mime)
+static gboolean
+__unknown_cmd(MsnServConn *servconn, const char *command, const char **params,
+			  size_t param_count)
 {
-	char *cur;
-	GString *ret = g_string_new(NULL);
-	guint colorbuf;
-	char *colors = (char *)(&colorbuf);
-	
+	gaim_debug(GAIM_DEBUG_ERROR, "msg",
+			   "Handled switchboard message: %s\n", command);
+
+	return FALSE;
+}
+
+/**************************************************************************
+ * Switchboard Commands
+ **************************************************************************/
+static gboolean
+__ans_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+	MsnSwitchBoard *swboard = servconn->data;
+
+	if (swboard->chat != NULL)
+		gaim_chat_add_user(GAIM_CHAT(swboard->chat), gc->username, NULL);
+
+	return TRUE;
+}
 
-	cur = strstr(mime, "FN=");
-	if (cur && (*(cur = cur + 3) != ';')) {
-		ret = g_string_append(ret, "<FONT FACE=\"");
-		while (*cur && *cur != ';') {
-			ret = g_string_append_c(ret, *cur);
-			cur++;
-		}
-		ret = g_string_append(ret, "\">");
+static gboolean
+__bye_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+	MsnSwitchBoard *swboard = servconn->data;
+	const char *user = params[0];
+
+	if (swboard->chat != NULL)
+		gaim_chat_remove_user(GAIM_CHAT(swboard->chat), user, NULL);
+	else {
+		const char *username;
+		struct gaim_conversation *conv;
+		struct buddy *b;
+		char buf[MSN_BUF_LEN];
+
+		if ((b = gaim_find_buddy(gc->account, user)) != NULL)
+			username = gaim_get_buddy_alias(b);
+		else
+			username = user;
+
+		g_snprintf(buf, sizeof(buf),
+				   _("%s has closed the conversation window."), username);
+
+		if ((conv = gaim_find_conversation(user)) != NULL)
+			gaim_conversation_write(conv, NULL, buf, -1, WFLAG_SYSTEM,
+									time(NULL));
+
+		msn_switchboard_destroy(swboard);
+
+		return FALSE;
 	}
-	
-	cur = strstr(mime, "EF=");
-	if (cur && (*(cur = cur + 3) != ';')) {
-		while (*cur && *cur != ';') {
-			ret = g_string_append_c(ret, '<');
-			ret = g_string_append_c(ret, *cur);
-			ret = g_string_append_c(ret, '>');
-			cur++;
-		}
+
+	return TRUE;
+}
+
+static gboolean
+__iro_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+	MsnSwitchBoard *swboard = servconn->data;
+
+	swboard->total_users = atoi(params[2]);
+
+	if (swboard->total_users > 1) {
+		if (swboard->chat == NULL)
+			swboard->chat = serv_got_joined_chat(gc, ++swboard->chat_id,
+												 "MSN Chat");
+
+		gaim_chat_add_user(GAIM_CHAT(swboard->chat), params[3], NULL);
 	}
-	
-	cur = strstr(mime, "CO=");
-	if (cur && (*(cur = cur + 3) != ';')) {
-		if (sscanf (cur, "%x;", &colorbuf) == 1) {
-			char tag[64];
-			g_snprintf(tag, sizeof(tag), "<FONT COLOR=\"#%02hhx%02hhx%02hhx\">", colors[0], colors[1], colors[2]);
-			ret = g_string_append(ret, tag);
-		}
-	}
-	
-	cur = url_decode(ret->str);
-	g_string_free(ret, TRUE);
-	return cur;
+
+	if (swboard->chat != NULL)
+		gaim_chat_add_user(GAIM_CHAT(swboard->chat), gc->username, NULL);
+
+	return TRUE;
 }
 
-static int
-msn_process_switch(struct msn_switchboard *ms, char *buf)
+static gboolean
+__joi_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
 {
-	struct gaim_connection *gc = ms->gc;
-	char sendbuf[MSN_BUF_LEN];
-	static int id = 0;
-
-	if (!g_ascii_strncasecmp(buf, "ACK", 3)) {
-	} else if (!g_ascii_strncasecmp(buf, "ANS", 3)) {
-		if (ms->chat)
-			gaim_chat_add_user(GAIM_CHAT(ms->chat), gc->username, NULL);
-	} else if (!g_ascii_strncasecmp(buf, "BYE", 3)) {
-		char *user, *tmp = buf;
-		GET_NEXT(tmp);
-		user = tmp;
-
-		if (ms->chat) {
-			gaim_chat_remove_user(GAIM_CHAT(ms->chat), user, NULL);
-		} else {
-			char msgbuf[256];
-			const char *username;
-			struct gaim_conversation *cnv;
-			struct buddy *b;
-
-			if ((b = gaim_find_buddy(gc->account, user)) != NULL)
-				username = gaim_get_buddy_alias(b);
-			else
-				username = user;
-
-			g_snprintf(msgbuf, sizeof(msgbuf),
-					   _("%s has closed the conversation window"), username);
+	struct gaim_connection *gc = servconn->session->account->gc;
+	MsnSwitchBoard *swboard = servconn->data;
+	const char *passport;
 
-			if ((cnv = gaim_find_conversation(user)))
-				gaim_conversation_write(cnv, NULL, msgbuf, -1,
-							WFLAG_SYSTEM, time(NULL));
-
-			msn_kill_switch(ms);
-			return 0;
-		}
-	} else if (!g_ascii_strncasecmp(buf, "CAL", 3)) {
-	} else if (!g_ascii_strncasecmp(buf, "IRO", 3)) {
-		char *tot, *user, *tmp = buf;
+	passport = params[0];
 
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-		GET_NEXT(tmp);
-		tot = tmp;
-		GET_NEXT(tmp);
-		ms->total = atoi(tot);
-		user = tmp;
-		GET_NEXT(tmp);
-
-		if (ms->total > 1) {
-			if (!ms->chat)
-				ms->chat = serv_got_joined_chat(gc, ++id, "MSN Chat");
-
-			gaim_chat_add_user(GAIM_CHAT(ms->chat), user, NULL);
-		} 
-	} else if (!g_ascii_strncasecmp(buf, "JOI", 3)) {
-		char *user, *tmp = buf;
-		GET_NEXT(tmp);
-		user = tmp;
-		GET_NEXT(tmp);
+	if (swboard->total_users == 1) {
+		swboard->chat = serv_got_joined_chat(gc, ++swboard->chat_id,
+											 "MSN Chat");
+		gaim_chat_add_user(GAIM_CHAT(swboard->chat),
+						   msn_user_get_passport(swboard->user), NULL);
+		gaim_chat_add_user(GAIM_CHAT(swboard->chat), gc->username, NULL);
 
-		if (ms->total == 1) {
-			ms->chat = serv_got_joined_chat(gc, ++id, "MSN Chat");
-			gaim_chat_add_user(GAIM_CHAT(ms->chat), ms->user, NULL);
-			gaim_chat_add_user(GAIM_CHAT(ms->chat), gc->username, NULL);
-			g_free(ms->user);
-			ms->user = NULL;
-		}
-		if (ms->chat)
-			gaim_chat_add_user(GAIM_CHAT(ms->chat), user, NULL);
-		ms->total++;
-		while (ms->txqueue) {
-			char *send = add_cr(ms->txqueue->data);
-			g_snprintf(sendbuf, sizeof(sendbuf),
-					   "MSG %u N %d\r\n%s%s", ++ms->trId,
-					   strlen(MIME_HEADER) + strlen(send),
-					   MIME_HEADER, send);
+		msn_user_unref(swboard->user);
+	}
 
-			g_free(ms->txqueue->data);
-			ms->txqueue = g_slist_remove(ms->txqueue, ms->txqueue->data);
+	if (swboard->chat != NULL)
+		gaim_chat_add_user(GAIM_CHAT(swboard->chat), passport, NULL);
 
-			if (msn_write(ms->fd, sendbuf, strlen(sendbuf)) < 0) {
-				msn_kill_switch(ms);
-				return 0;
-			}
-		}
-	} else if (!g_ascii_strncasecmp(buf, "MSG", 3)) {
-		char *user, *tmp = buf;
-		int length;
-
-		GET_NEXT(tmp);
-		user = tmp;
-
-		GET_NEXT(tmp);
+	swboard->total_users++;
 
-		GET_NEXT(tmp);
-		length = atoi(tmp);
+	while (servconn->txqueue) {
+		char *buf = servconn->txqueue->data;
 
-		ms->msg = TRUE;
-		ms->msguser = g_strdup(user);
-		ms->msglen = length;
-	} else if (!g_ascii_strncasecmp(buf, "NAK", 3)) {
-		do_error_dialog(_("An MSN message may not have been received."), NULL, GAIM_ERROR);
-	} else if (!g_ascii_strncasecmp(buf, "NLN", 3)) {
-	} else if (!g_ascii_strncasecmp(buf, "OUT", 3)) {
-		if (ms->chat)
-			serv_got_chat_left(gc, gaim_chat_get_id(GAIM_CHAT(ms->chat)));
-		msn_kill_switch(ms);
-		return 0;
-	} else if (!g_ascii_strncasecmp(buf, "USR", 3)) {
-		/* good, we got USR, now we need to find out who we want to talk to */
-		struct msn_switchboard *ms = msn_find_writable_switch(gc);
+		servconn->txqueue = g_slist_remove(servconn->txqueue, buf);
 
-		if (!ms)
-			return 0;
-
-		g_snprintf(sendbuf, sizeof(sendbuf), "CAL %u %s\r\n",
-				   ++ms->trId, ms->user);
+		if (msn_servconn_write(swboard->servconn, buf, strlen(buf)) < 0) {
+			msn_switchboard_destroy(swboard);
 
-		if (msn_write(ms->fd, sendbuf, strlen(sendbuf)) < 0) {
-			msn_kill_switch(ms);
-			return 0;
+			return FALSE;
 		}
-	} else if (isdigit(*buf)) {
-		handle_errcode(buf, TRUE);
-
-		if (atoi(buf) == 217)
-			msn_kill_switch(ms);
-
-	} else {
-		gaim_debug(GAIM_DEBUG_WARNING, "msn", "Unhandled message!\n");
 	}
 
-	return 1;
+	return TRUE;
+}
+
+static gboolean
+__msg_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	gaim_debug(GAIM_DEBUG_INFO, "msn", "Found message. Parsing.\n");
+
+	servconn->parsing_msg = TRUE;
+	servconn->msg_passport = g_strdup(params[0]);
+	servconn->msg_friendly = g_strdup(params[1]);
+	servconn->msg_len      = atoi(params[2]);
+
+	return TRUE;
+}
+
+static gboolean
+__nak_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	/*
+	 * TODO: Investigate this, as it seems to occur frequently with
+	 *       the old prpl.
+	 */
+	do_error_dialog(_("An MSN message may not have been received."),
+					NULL, GAIM_ERROR);
+
+	return TRUE;
 }
 
-static void
-msn_process_switch_msg(struct msn_switchboard *ms, char *msg)
+static gboolean
+__out_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+	MsnSwitchBoard *swboard = servconn->data;
+
+	if (swboard->chat != NULL)
+		serv_got_chat_left(gc, gaim_chat_get_id(GAIM_CHAT(swboard->chat)));
+
+	msn_switchboard_destroy(swboard);
+
+	return FALSE;
+}
+
+static gboolean
+__usr_cmd(MsnServConn *servconn, const char *command, const char **params,
+		  size_t param_count)
 {
-	char *content, *agent, *format;
-	char *message = NULL;
+	MsnSwitchBoard *swboard = servconn->data;
+
+	if (!msn_switchboard_send_command(swboard, "CAL",
+									  msn_user_get_passport(swboard->user))) {
+		msn_switchboard_destroy(swboard);
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/**************************************************************************
+ * Message Types
+ **************************************************************************/
+static gboolean
+__plain_msg(MsnServConn *servconn, const MsnMessage *msg)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+	MsnSwitchBoard *swboard = servconn->data;
+	char *body;
+	const char *value;
+	char *format;
 	int flags = 0;
 
-	agent = strstr(msg, "User-Agent: ");
-	if (agent) {
-		if (!g_ascii_strncasecmp(agent, "User-Agent: Gaim",
-						   strlen("User-Agent: Gaim")))
+	body = g_strdup(msn_message_get_body(msg));
+
+	if ((value = msn_message_get_attr(msg, "User-Agent")) != NULL) {
+		if (!g_ascii_strncasecmp(value, "Gaim", 4))
 			flags |= IM_FLAG_GAIMUSER;
 	}
 
-	format = strstr(msg, "X-MMS-IM-Format: ");
-	if (format) {
-		format = msn_parse_format(format);
-	} else {
-		format = NULL;
+	if ((value = msn_message_get_attr(msg, "X-MMS-IM-Format")) != NULL) {
+		format = msn_parse_format(value);
+
+		body = g_strdup_printf("%s%s", format, body);
+
+		g_free(format);
+	}
+
+	if (swboard->chat != NULL)
+		serv_got_chat_in(gc, gaim_chat_get_id(GAIM_CHAT(swboard->chat)),
+						 servconn->msg_passport, flags, body, time(NULL));
+	else
+		serv_got_im(gc, servconn->msg_passport, body, flags, time(NULL), -1);
+
+	g_free(body);
+
+	return TRUE;
+}
+
+static gboolean
+__control_msg(MsnServConn *servconn, const MsnMessage *msg)
+{
+	struct gaim_connection *gc = servconn->session->account->gc;
+	MsnSwitchBoard *swboard = servconn->data;
+	const char *value;
+
+	if (swboard->chat == NULL &&
+		(value = msn_message_get_attr(msg, "TypingUser")) != NULL) {
+
+		serv_got_typing(gc, servconn->msg_passport, MSN_TYPING_RECV_TIMEOUT,
+						TYPING);
 	}
 
-	content = strstr(msg, "Content-Type: ");
-	if (!content)
-		return;
-	if (!g_ascii_strncasecmp(content, "Content-Type: text/x-msmsgscontrol\r\n",
-			   strlen(  "Content-Type: text/x-msmsgscontrol\r\n"))) {
-		if (strstr(content,"TypingUser: ") && !ms->chat) {
-			serv_got_typing(ms->gc, ms->msguser,
-							MSN_TYPING_RECV_TIMEOUT, TYPING);
-			return;
-		} 
+	return TRUE;
+}
 
-	} else if (!g_ascii_strncasecmp(content, "Content-Type: text/x-msmsgsinvite;",
-							  strlen("Content-Type: text/x-msmsgsinvite;"))) {
+/**************************************************************************
+ * Connect stuff
+ **************************************************************************/
+static gboolean
+__connect_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnServConn *servconn = data;
+	MsnSwitchBoard *swboard = servconn->data;
+	char outparams[MSN_BUF_LEN];
 
-		/*
-		 * NOTE: Other things, such as voice communication, would go in
-		 *       here too (since they send the same Content-Type). However,
-		 *       this is the best check for file transfer messages, so I'm
-		 *       calling msn_process_ft_invite_msg(). If anybody adds support
-		 *       for anything else that sends a text/x-msmsgsinvite, perhaps
-		 *       this should be changed. For now, it stays.
-		 */
-		msn_process_ft_msg(ms, content);
+	if (servconn->fd != source)
+		servconn->fd = source;
+
+	swboard->in_use = TRUE;
+
+	gaim_debug(GAIM_DEBUG_INFO, "msn", "Connecting to switchboard...\n");
 
-	} else if (!g_ascii_strncasecmp(content, "Content-Type: text/plain",
-				  strlen("Content-Type: text/plain"))) {
+	if (msn_switchboard_is_invited(swboard)) {
+		g_snprintf(outparams, sizeof(outparams), "%s %s %s",
+				   servconn->session->account->gc->username,
+				   swboard->auth_key, swboard->session_id);
 
-		char *skiphead = strstr(msg, "\r\n\r\n");
-
-		if (!skiphead || !skiphead[4]) {
-			return;
-		}
+		if (!msn_switchboard_send_command(swboard, "ANS", outparams)) {
+			msn_switchboard_destroy(swboard);
 
-		skiphead += 4;
-		strip_linefeed(skiphead);
-		
-		if (format) { 
-			message = g_strdup_printf("%s%s", format, skiphead);
-		} else {
-			message = g_strdup(skiphead);
+			return FALSE;
 		}
-		
-		if (ms->chat)
-			serv_got_chat_in(ms->gc, gaim_chat_get_id(GAIM_CHAT(ms->chat)),
-							 ms->msguser, flags, message, time(NULL));
-		else
-			serv_got_im(ms->gc, ms->msguser, message, flags, time(NULL), -1);
+	}
+	else {
+		g_snprintf(outparams, sizeof(outparams), "%s %s",
+				   servconn->session->account->gc->username, swboard->auth_key);
 
-		g_free(message);
+		if (!msn_switchboard_send_command(swboard, "USR", outparams)) {
+			msn_switchboard_destroy(swboard);
+
+			return FALSE;
+		}
 	}
+
+	return TRUE;
 }
 
 static void
-msn_switchboard_callback(gpointer data, gint source, GaimInputCondition cond)
+__failed_read_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	MsnServConn *servconn = data;
+
+	msn_switchboard_destroy(servconn->data);
+}
+
+MsnSwitchBoard *
+msn_switchboard_new(MsnSession *session)
 {
-	struct msn_switchboard *ms = data;
-	char buf[MSN_BUF_LEN];
-	int cont = 1;
-	int len;
-	
-	ms->fd = source;
-	len = read(ms->fd, buf, sizeof(buf));
-	if (len <= 0) {
-		msn_kill_switch(ms);
-		return;
+	MsnSwitchBoard *swboard;
+	MsnServConn *servconn;
+
+	g_return_val_if_fail(session != NULL, NULL);
+
+	swboard = g_new0(MsnSwitchBoard, 1);
+
+	swboard->servconn = servconn = msn_servconn_new(session);
+	msn_servconn_set_connect_cb(servconn, __connect_cb);
+	msn_servconn_set_failed_read_cb(servconn, __failed_read_cb);
+
+	servconn->data = swboard;
+
+	session->switches = g_list_append(session->switches, swboard);
+
+	if (switchboard_commands == NULL) {
+		/* Register the command callbacks. */
+		msn_servconn_register_command(servconn, "ACK",       __blank_cmd);
+		msn_servconn_register_command(servconn, "ANS",       __ans_cmd);
+		msn_servconn_register_command(servconn, "BYE",       __bye_cmd);
+		msn_servconn_register_command(servconn, "CAL",       __blank_cmd);
+		msn_servconn_register_command(servconn, "IRO",       __iro_cmd);
+		msn_servconn_register_command(servconn, "JOI",       __joi_cmd);
+		msn_servconn_register_command(servconn, "MSG",       __msg_cmd);
+		msn_servconn_register_command(servconn, "NAK",       __nak_cmd);
+		msn_servconn_register_command(servconn, "NLN",       __blank_cmd);
+		msn_servconn_register_command(servconn, "OUT",       __out_cmd);
+		msn_servconn_register_command(servconn, "USR",       __usr_cmd);
+		msn_servconn_register_command(servconn, "_unknown_", __unknown_cmd);
+
+		/* Register the message type callbacks. */
+		msn_servconn_register_msg_type(servconn, "text/plain", __plain_msg);
+		msn_servconn_register_msg_type(servconn, "text/x-msmsgscontrol",
+									   __control_msg);
+
+		/* Save these for future use. */
+		switchboard_commands  = servconn->commands;
+		switchboard_msg_types = servconn->msg_types;
+	}
+	else {
+		g_hash_table_destroy(servconn->commands);
+		g_hash_table_destroy(servconn->msg_types);
+
+		servconn->commands  = switchboard_commands;
+		servconn->msg_types = switchboard_msg_types;
 	}
 
-	ms->rxqueue = g_realloc(ms->rxqueue, len + ms->rxlen);
-	memcpy(ms->rxqueue + ms->rxlen, buf, len);
-	ms->rxlen += len;
-
-	while (cont) {
-		if (!ms->rxlen)
-			return;
-
-		if (ms->msg) {
-			char *msg;
-			if (ms->msglen > ms->rxlen)
-				return;
-			msg = ms->rxqueue;
-			ms->rxlen -= ms->msglen;
-			if (ms->rxlen) {
-				ms->rxqueue = g_memdup(msg + ms->msglen, ms->rxlen);
-			} else {
-				ms->rxqueue = NULL;
-				msg = g_realloc(msg, ms->msglen + 1);
-			}
-			msg[ms->msglen] = 0;
-			ms->msglen = 0;
-			ms->msg = FALSE;
-
-			msn_process_switch_msg(ms, msg);
-
-			g_free(ms->msguser);
-			g_free(msg);
-		} else {
-			char *end = ms->rxqueue;
-			int cmdlen;
-			char *cmd;
-			int i = 0;
-
-			while (i + 1 < ms->rxlen) {
-				if (*end == '\r' && end[1] == '\n')
-					break;
-				end++; i++;
-			}
-			if (i + 1 == ms->rxlen)
-				return;
-
-			cmdlen = end - ms->rxqueue + 2;
-			cmd = ms->rxqueue;
-			ms->rxlen -= cmdlen;
-			if (ms->rxlen) {
-				ms->rxqueue = g_memdup(cmd + cmdlen, ms->rxlen);
-			} else {
-				ms->rxqueue = NULL;
-				cmd = g_realloc(cmd, cmdlen + 1);
-			}
-			cmd[cmdlen] = 0;
-
-			gaim_debug(GAIM_DEBUG_MISC, "msn", "S: %s", cmd);
-			g_strchomp(cmd);
-			cont = msn_process_switch(ms, cmd);
-
-			g_free(cmd);
-		}
-	}
+	return swboard;
 }
 
 void
-msn_rng_connect(gpointer data, gint source, GaimInputCondition cond)
+msn_switchboard_destroy(MsnSwitchBoard *swboard)
 {
-	struct msn_switchboard *ms = data;
-	struct gaim_connection *gc = ms->gc;
-	struct msn_data *md;
-	char buf[MSN_BUF_LEN];
+	MsnSession *session;
+
+	g_return_if_fail(swboard != NULL);
+
+	session = swboard->servconn->session;
 
-	if (source == -1 || !g_slist_find(connections, gc)) {
-		close(source);
-		g_free(ms->sessid);
-		g_free(ms->auth);
-		g_free(ms);
-		return;
-	}
+	if (swboard->servconn->connected)
+		msn_switchboard_disconnect(swboard);
+
+	if (swboard->user != NULL)
+		msn_user_unref(swboard->user);
+
+	if (swboard->auth_key != NULL)
+		g_free(swboard->auth_key);
 
-	md = gc->proto_data;
+	if (swboard->session_id != NULL)
+		g_free(swboard->session_id);
 
-	if (ms->fd != source)
-		ms->fd = source;
+	session->switches = g_list_remove(session->switches, swboard);
+
+	msn_servconn_destroy(swboard->servconn);
 
-	g_snprintf(buf, sizeof(buf), "ANS %u %s %s %s\r\n", ++ms->trId, gc->username, ms->auth, ms->sessid);
-	if (msn_write(ms->fd, buf, strlen(buf)) < 0) {
-		close(ms->fd);
-		g_free(ms->sessid);
-		g_free(ms->auth);
-		g_free(ms);
-		return;
-	}
+	g_free(swboard);
+}
 
-	md->switches = g_slist_append(md->switches, ms);
-	ms->inpa = gaim_input_add(ms->fd, GAIM_INPUT_READ,
-							  msn_switchboard_callback, ms);
+void
+msn_switchboard_set_user(MsnSwitchBoard *swboard, MsnUser *user)
+{
+	g_return_if_fail(swboard != NULL);
+
+	swboard->user = user;
+
+	msn_user_ref(user);
 }
 
-static void
-msn_ss_xfr_connect(gpointer data, gint source, GaimInputCondition cond)
+MsnUser *
+msn_switchboard_get_user(const MsnSwitchBoard *swboard)
 {
-	struct msn_switchboard *ms = data;
-	struct gaim_connection *gc = ms->gc;
-	char buf[MSN_BUF_LEN];
+	g_return_val_if_fail(swboard != NULL, NULL);
 
-	if (source == -1 || !g_slist_find(connections, gc)) {
-		close(source);
-		if (g_slist_find(connections, gc)) {
-			msn_kill_switch(ms);
-			do_error_dialog(_("Gaim was unable to send an MSN message"),
-					_("Gaim encountered an error communicating with the "
-					  "MSN switchboard server.  Please try again later."),
-					GAIM_ERROR);
-		}
+	return swboard->user;
+}
 
-		return;
-	}
-
-	if (ms->fd != source)
-		ms->fd = source;
-
-	g_snprintf(buf, sizeof(buf), "USR %u %s %s\r\n",
-			   ++ms->trId, gc->username, ms->auth);
+void
+msn_switchboard_set_auth_key(MsnSwitchBoard *swboard, const char *key)
+{
+	g_return_if_fail(swboard != NULL);
+	g_return_if_fail(key != NULL);
 
-	if (msn_write(ms->fd, buf, strlen(buf)) < 0) {
-		g_free(ms->auth);
-		g_free(ms);
-		return;
-	}
-
-	ms->inpa = gaim_input_add(ms->fd, GAIM_INPUT_READ,
-							  msn_switchboard_callback, ms);
+	swboard->auth_key = g_strdup(key);
 }
 
-struct msn_switchboard *
-msn_find_switch(struct gaim_connection *gc, const char *username)
+const char *
+msn_switchboard_get_auth_key(const MsnSwitchBoard *swboard)
 {
-	struct msn_data *md = (struct msn_data *)gc->proto_data;
-	GSList *m = md->switches;
+	g_return_val_if_fail(swboard != NULL, NULL);
 
-	for (m = md->switches; m != NULL; m = m->next) {
-		struct msn_switchboard *ms = (struct msn_switchboard *)m->data;
-
-		if (ms->total <= 1 && !gaim_utf8_strcasecmp(ms->user, username))
-			return ms;
-	}
-
-	return NULL;
+	return swboard->auth_key;
 }
 
-struct msn_switchboard *
-msn_find_switch_by_id(struct gaim_connection *gc, int chat_id)
+void
+msn_switchboard_set_session_id(MsnSwitchBoard *swboard, const char *id)
 {
-	struct msn_data *md = (struct msn_data *)gc->proto_data;
-	GSList *m;
+	g_return_if_fail(swboard != NULL);
+	g_return_if_fail(id != NULL);
 
-	for (m = md->switches; m != NULL; m = m->next) {
-		struct msn_switchboard *ms = (struct msn_switchboard *)m->data;
+	if (swboard->session_id != NULL)
+		g_free(swboard->session_id);
 
-		if (ms->chat && gaim_chat_get_id(GAIM_CHAT(ms->chat)) == chat_id)
-			return ms;
-	}
-
-	return NULL;
+	swboard->session_id = g_strdup(id);
 }
 
-struct msn_switchboard *
-msn_find_writable_switch(struct gaim_connection *gc)
+const char *
+msn_switchboard_get_session_id(const MsnSwitchBoard *swboard)
 {
-	struct msn_data *md = (struct msn_data *)gc->proto_data;
-	GSList *m;
-	
-	for (m = md->switches; m != NULL; m = m->next) {
-		struct msn_switchboard *ms = (struct msn_switchboard *)m->data;
+	g_return_val_if_fail(swboard != NULL, NULL);
 
-		if (ms->txqueue != NULL)
-			return ms;
-	}
-
-	return NULL;
+	return swboard->session_id;
 }
 
 void
-msn_kill_switch(struct msn_switchboard *ms)
+msn_switchboard_set_invited(MsnSwitchBoard *swboard, gboolean invited)
+{
+	g_return_if_fail(swboard != NULL);
+
+	swboard->invited = invited;
+}
+
+gboolean
+msn_switchboard_is_invited(const MsnSwitchBoard *swboard)
 {
-	struct gaim_connection *gc = ms->gc;
-	struct msn_data *md = gc->proto_data;
+	g_return_val_if_fail(swboard != NULL, FALSE);
+
+	return swboard->invited;
+}
 
-	if (ms->inpa)
-		gaim_input_remove(ms->inpa);
+gboolean
+msn_switchboard_connect(MsnSwitchBoard *swboard, const char *server, int port)
+{
+	g_return_val_if_fail(swboard != NULL, FALSE);
+
+	msn_servconn_set_server(swboard->servconn, server, port);
+
+	if (msn_servconn_connect(swboard->servconn))
+		swboard->in_use = TRUE;
+
+	return swboard->in_use;
+}
 
-	close(ms->fd);
-	g_free(ms->rxqueue);
+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;
+}
 
-	if (ms->msg)    g_free(ms->msguser);
-	if (ms->user)   g_free(ms->user);
-	if (ms->sessid) g_free(ms->sessid);
+gboolean
+msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg)
+{
+	char *buf;
+	int ret;
+
+	g_return_val_if_fail(swboard != NULL, FALSE);
+	g_return_val_if_fail(msg != NULL, FALSE);
 
-	g_free(ms->auth);
+	msn_message_set_transaction_id(msg, ++swboard->trId);
+	buf = msn_message_build_string(msg);
 
-	while (ms->txqueue) {
-		g_free(ms->txqueue->data);
-		ms->txqueue = g_slist_remove(ms->txqueue, ms->txqueue->data);
+	if (swboard->servconn->txqueue != NULL || !swboard->in_use) {
+		gaim_debug(GAIM_DEBUG_INFO, "msn", "Appending message to queue.\n");
+
+		swboard->servconn->txqueue =
+			g_slist_append(swboard->servconn->txqueue, buf);
+
+		return TRUE;
 	}
 
-	if (ms->chat)
-		serv_got_chat_left(gc, gaim_chat_get_id(GAIM_CHAT(ms->chat)));
+	ret = msn_servconn_write(swboard->servconn, buf, strlen(buf));
 
-	md->switches = g_slist_remove(md->switches, ms);
+	g_free(buf);
 
-	g_free(ms);
+	return (ret > 0);
 }
 
-struct msn_switchboard *
-msn_switchboard_connect(struct gaim_connection *gc, const char *host, int port)
+gboolean
+msn_switchboard_send_command(MsnSwitchBoard *swboard, const char *command,
+							 const char *params)
 {
-	struct msn_switchboard *ms;
+	char buf[MSN_BUF_LEN];
 
-	if (host == NULL || port == 0)
-		return NULL;
-
-	ms = msn_find_writable_switch(gc);
+	g_return_val_if_fail(swboard != NULL, FALSE);
+	g_return_val_if_fail(command != NULL, FALSE);
 
-	if (ms == NULL)
-		return NULL;
+	if (params == NULL)
+		g_snprintf(buf, sizeof(buf), "%s %u\r\n", command,
+				   ++swboard->trId);
+	else
+		g_snprintf(buf, sizeof(buf), "%s %u %s\r\n",
+				   command, ++swboard->trId, params);
 
-	if (proxy_connect(gc->account, (char *)host, port, msn_ss_xfr_connect,
-				ms) != 0) {
-		msn_kill_switch(ms);
-
-		return NULL;
-	}
-
-	return ms;
+	return (msn_servconn_write(swboard->servconn, buf, strlen(buf)) > 0);
 }
--- a/src/protocols/msn/switchboard.h	Tue May 06 00:34:54 2003 +0000
+++ b/src/protocols/msn/switchboard.h	Tue May 06 02:06:56 2003 +0000
@@ -3,7 +3,7 @@
  *
  * gaim
  *
- * Copyright (C) 2003, Christian Hammond <chipx86@gnupdate.org>
+ * 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
@@ -18,81 +18,164 @@
  * 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_SWITCHBOARD_H_
 #define _MSN_SWITCHBOARD_H_
 
-struct msn_switchboard {
-	struct gaim_connection *gc;
+typedef struct _MsnSwitchBoard MsnSwitchBoard;
+
+#include "servconn.h"
+#include "msg.h"
+#include "user.h"
+
+struct _MsnSwitchBoard
+{
+	MsnServConn *servconn;
+	MsnUser *user;
+
+	char *auth_key;
+	char *session_id;
+
+	gboolean invited;
+
 	struct gaim_conversation *chat;
-	int fd;
-	int inpa;
+
+	gboolean in_use;
 
-	char *rxqueue;
-	int rxlen;
+	int total_users;
+
 	gboolean msg;
-	char *msguser;
 	int msglen;
 
-	char *sessid;
-	char *auth;
-	guint32 trId;
-	int total;
-	char *user;
-	GSList *txqueue;
+	int chat_id;
+	int trId;
 };
 
 /**
- * Finds a switch with the given username.
+ * Creates a new switchboard.
+ *
+ * @param session The MSN session.
  *
- * @param gc       The gaim connection.
- * @param username The username to search for.
+ * @return The new switchboard.
+ */
+MsnSwitchBoard *msn_switchboard_new(MsnSession *session);
+
+/**
+ * Destroys a switchboard.
+ *
+ * @param swboard The switchboard to destroy.
+ */
+void msn_switchboard_destroy(MsnSwitchBoard *swboard);
+
+/**
+ * Sets the user the switchboard is supposed to connect to.
  *
- * @return The switchboard, if found.
+ * @param swboard The switchboard.
+ * @param user    The user.
  */
-struct msn_switchboard *msn_find_switch(struct gaim_connection *gc,
-										const char *username);
+void msn_switchboard_set_user(MsnSwitchBoard *swboard, MsnUser *user);
+
+/**
+ * Returns the user the switchboard is supposed to connect to.
+ *
+ * @param swboard The switchboard.
+ *
+ * @return The user.
+ */
+MsnUser *msn_switchboard_get_user(const MsnSwitchBoard *swboard);
+
+/**
+ * Sets the auth key the switchboard must use when connecting.
+ *
+ * @param swboard The switchboard.
+ * @param key     The auth key.
+ */
+void msn_switchboard_set_auth_key(MsnSwitchBoard *swboard, const char *key);
 
 /**
- * Finds a switchboard with the given chat ID.
+ * Returns the auth key the switchboard must use when connecting.
  *
- * @param gc      The gaim connection.
- * @param chat_id The chat ID to search for.
+ * @param swboard The switchboard.
  *
- * @return The switchboard, if found.
+ * @return The auth key.
  */
-struct msn_switchboard *msn_find_switch_by_id(struct gaim_connection *gc,
-											  int chat_id);
+const char *msn_switchboard_get_auth_key(const MsnSwitchBoard *swboard);
+
+/**
+ * Sets the session ID the switchboard must use when connecting.
+ *
+ * @param swboard The switchboard.
+ * @param id      The session ID.
+ */
+void msn_switchboard_set_session_id(MsnSwitchBoard *swboard, const char *id);
 
 /**
- * Finds the first writable switchboard.
+ * Returns the session ID the switchboard must use when connecting.
+ *
+ * @param swboard The switchboard.
  *
- * @param gc The gaim connection.
+ * @return The session ID.
+ */
+const char *msn_switchboard_get_session_id(const MsnSwitchBoard *swboard);
+
+/**
+ * Sets whether or not the user was invited to this switchboard.
  *
- * @return The first writable switchboard, if found.
+ * @param swboard The switchboard.
+ * @param invite  @c TRUE if invited, @c FALSE otherwise.
  */
-struct msn_switchboard *msn_find_writable_switch(struct gaim_connection *gc);
+void msn_switchboard_set_invited(MsnSwitchBoard *swboard, gboolean invited);
+
+/**
+ * Returns whether or not the user was invited to this switchboard.
+ *
+ * @param swboard The switchboard.
+ *
+ * @return @c TRUE if invited, @c FALSE otherwise.
+ */
+gboolean msn_switchboard_is_invited(const MsnSwitchBoard *swboard);
 
 /**
  * Connects to a switchboard.
  *
- * @param gc   The gaim connection.
- * @param host The hostname.
- * @param port The port.
+ * @param swboard The switchboard.
+ * @param server  The server.
+ * @param port    The port.
  *
- * @return The new switchboard.
+ * @return @c TRUE if able to connect, or @c FALSE otherwise.
  */
-struct msn_switchboard *msn_switchboard_connect(struct gaim_connection *gc,
-												const char *host, int port);
+gboolean msn_switchboard_connect(MsnSwitchBoard *swboard,
+								 const char *server, int port);
+
+/**
+ * Disconnects from a switchboard.
+ *
+ * @param swboard The switchboard.
+ */
+void msn_switchboard_disconnect(MsnSwitchBoard *swboard);
 
 /**
- * Kills a switchboard.
+ * Sends a message to a switchboard.
  *
- * @param ms The switchboard to kill.
+ * @param swboard The switchboard.
+ * @param msg     The message to send.
+ *
+ * @return @c TRUE if successful, or @c FALSE otherwise.
  */
-void msn_kill_switch(struct msn_switchboard *ms);
+gboolean msn_switchboard_send_msg(MsnSwitchBoard *swboard,
+								  MsnMessage *msg);
 
-void msn_rng_connect(gpointer data, gint source, GaimInputCondition cond);
+/**
+ * Sends a command to the switchboard.
+ *
+ * @param swboard The switchboard.
+ * @param command The command.
+ * @param params  The parameters.
+ *
+ * @return @c TRUE if successful, or @c FALSE otherwise.
+ */
+gboolean msn_switchboard_send_command(MsnSwitchBoard *swboard,
+									  const char *command,
+									  const char *params);
 
 #endif /* _MSN_SWITCHBOARD_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/user.c	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,227 @@
+/**
+ * @file user.c User functions
+ *
+ * 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 "user.h"
+
+MsnUser *
+msn_user_new(MsnSession *session, const char *passport, const char *name)
+{
+	MsnUser *user;
+
+	user = msn_users_find_with_passport(session->users, passport);
+
+	if (user != NULL) {
+		if (name != NULL)
+			msn_user_set_name(user, name);
+
+		msn_user_ref(user);
+
+		return user;
+	}
+
+	user = g_new0(MsnUser, 1);
+
+	user->session = session;
+
+	if (name != NULL)
+		msn_user_set_name(user, name);
+
+	msn_user_set_passport(user, passport);
+	msn_user_set_group_id(user, -1);
+
+	msn_users_add(session->users, user);
+
+	msn_user_ref(user);
+
+	return user;
+}
+
+void
+msn_user_destroy(MsnUser *user)
+{
+	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->passport != NULL)
+		g_free(user->passport);
+
+	if (user->name != NULL)
+		g_free(user->name);
+
+	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)
+{
+	g_return_if_fail(user != NULL);
+
+	if (user->passport != NULL)
+		g_free(user->passport);
+
+	user->passport = g_strdup(passport);
+}
+
+void
+msn_user_set_name(MsnUser *user, const char *name)
+{
+	g_return_if_fail(user != NULL);
+
+	if (user->name != NULL)
+		g_free(user->name);
+
+	user->name = g_strdup(name);
+}
+
+void
+msn_user_set_group_id(MsnUser *user, int id)
+{
+	g_return_if_fail(user != NULL);
+
+	user->group_id = id;
+}
+
+const char *
+msn_user_get_passport(const MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, NULL);
+
+	return user->passport;
+}
+
+const char *
+msn_user_get_name(const MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, NULL);
+
+	return user->name;
+}
+
+int
+msn_user_get_group_id(const MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, -1);
+
+	return user->group_id;
+}
+
+MsnUsers *
+msn_users_new(void)
+{
+	MsnUsers *users = g_new0(MsnUsers, 1);
+
+	return users;
+}
+
+void
+msn_users_destroy(MsnUsers *users)
+{
+	g_return_if_fail(users != NULL);
+
+	while (users->users != NULL)
+		msn_user_destroy(users->users->data);
+
+	/* See if we've leaked anybody. */
+	while (users->users != NULL) {
+		gaim_debug(GAIM_DEBUG_WARNING, "msn",
+				   "Leaking user %s\n",
+				   msn_user_get_passport(users->users->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);
+}
+
+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);
+}
+
+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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/user.h	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,184 @@
+/**
+ * @file user.h User functions
+ *
+ * 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_USER_H_
+#define _MSN_USER_H_
+
+typedef struct _MsnUser  MsnUser;
+typedef struct _MsnUsers MsnUsers;
+
+#include "session.h"
+
+/**
+ * A user.
+ */
+struct _MsnUser
+{
+	MsnSession *session; /**< The MSN session.      */
+
+	char *passport;      /**< The passport account. */
+	char *name;          /**< The friendly name.    */
+
+	int group_id;        /**< The group ID.         */
+
+	size_t ref_count;    /**< The reference count.  */
+};
+
+/**
+ * A collection of users.
+ */
+struct _MsnUsers
+{
+	GList *users; /** The list of users. */
+};
+
+/**
+ * Creates a new user structure.
+ * 
+ * @param session  The MSN session.
+ * @param passport The initial passport.
+ * @param name     The initial friendly name.
+ *
+ * @return A new user structure.
+ */
+MsnUser *msn_user_new(MsnSession *session, const char *passport,
+					  const char *name);
+
+/**
+ * Destroys a user structure.
+ *
+ * @param user The user to destroy.
+ */
+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.
+ * @param passport The passport account.
+ */
+void msn_user_set_passport(MsnUser *user, const char *passport);
+
+/**
+ * Sets the friendly name for a user.
+ *
+ * @param user The user.
+ * @param name The friendly name.
+ */
+void msn_user_set_name(MsnUser *user, const char *name);
+
+/**
+ * Sets the group ID for a user.
+ *
+ * @param user The user.
+ * @param id   The group ID.
+ */
+void msn_user_set_group_id(MsnUser *user, int id);
+
+/**
+ * Returns the passport account for a user.
+ *
+ * @param user The user.
+ *
+ * @return The passport account.
+ */
+const char *msn_user_get_passport(const MsnUser *user);
+
+/**
+ * Returns the friendly name for a user.
+ *
+ * @param user The user.
+ *
+ * @return The friendly name.
+ */
+const char *msn_user_get_name(const MsnUser *user);
+
+/**
+ * Returns the group ID for a user.
+ *
+ * @param user The user.
+ *
+ * @return The group ID.
+ */
+int msn_user_get_group_id(const MsnUser *user);
+
+/**
+ * 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);
+
+
+/**
+ * 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/utils.c	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,132 @@
+/**
+ * @file utils.h Utility functions
+ *
+ * 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"
+
+char *
+msn_url_decode(const char *str)
+{
+	static char buf[MSN_BUF_LEN];
+	int i, j = 0;
+	char *bum;
+
+	g_return_val_if_fail(str != NULL, NULL);
+
+	for (i = 0; i < strlen(str); i++) {
+		char hex[3];
+
+		if (str[i] != '%')
+			buf[j++] = str[i];
+		else {
+			strncpy(hex, str + ++i, 2);
+			hex[2] = '\0';
+
+			/* i is pointing to the start of the number */
+			i++;
+
+			/*
+			 * Now it's at the end and at the start of the for loop
+			 * will be at the next character.
+			 */
+			buf[j++] = strtol(hex, NULL, 16);
+		}
+	}
+
+	buf[j] = '\0';
+
+	if (!g_utf8_validate(buf, -1, (const char **)&bum))
+		*bum = '\0';
+
+	return buf;
+}
+
+char *
+msn_url_encode(const char *str)
+{
+	static char buf[MSN_BUF_LEN];
+	int i, j = 0;
+
+	g_return_val_if_fail(str != NULL, NULL);
+
+	for (i = 0; i < strlen(str); i++) {
+		if (isalnum(str[i]))
+			buf[j++] = str[i];
+		else {
+			sprintf(buf + j, "%%%02x", (unsigned char)str[i]);
+			j += 3;
+		}
+	}
+
+	buf[j] = '\0';
+
+	return buf;
+}
+
+char *
+msn_parse_format(const char *mime)
+{
+	char *cur;
+	GString *ret = g_string_new(NULL);
+	guint colorbuf;
+	char *colors = (char *)(&colorbuf);
+
+	cur = strstr(mime, "FN=");
+
+	if (cur && (*(cur = cur + 3) != ';')) {
+		ret = g_string_append(ret, "<FONT FACE=\"");
+
+		while (*cur && *cur != ';') {
+			ret = g_string_append_c(ret, *cur);
+			cur++;
+		}
+
+		ret = g_string_append(ret, "\">");
+	}
+	
+	cur = strstr(mime, "EF=");
+
+	if (cur && (*(cur = cur + 3) != ';')) {
+		while (*cur && *cur != ';') {
+			ret = g_string_append_c(ret, '<');
+			ret = g_string_append_c(ret, *cur);
+			ret = g_string_append_c(ret, '>');
+			cur++;
+		}
+	}
+
+	cur = strstr(mime, "CO=");
+
+	if (cur && (*(cur = cur + 3) != ';')) {
+		if (sscanf (cur, "%x;", &colorbuf) == 1) {
+			char tag[64];
+			g_snprintf(tag, sizeof(tag),
+					   "<FONT COLOR=\"#%02hhx%02hhx%02hhx\">",
+					   colors[0], colors[1], colors[2]);
+
+			ret = g_string_append(ret, tag);
+		}
+	}
+
+	cur = msn_url_decode(ret->str);
+	g_string_free(ret, TRUE);
+
+	return cur;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/msn/utils.h	Tue May 06 02:06:56 2003 +0000
@@ -0,0 +1,56 @@
+/**
+ * @file utils.h Utility functions
+ *
+ * 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_UTILS_H_
+#define _MSN_UTILS_H_
+
+/**
+ * Decodes a URL into a plain string.
+ *
+ * This will change hex codes and such to their ascii equivalents.
+ *
+ * @param str The string to translate.
+ *
+ * @return The resulting string.
+ */
+char *msn_url_decode(const char *str);
+
+/**
+ * Encodes a URL into an escaped string.
+ *
+ * This will change non-alphanumeric characters to hex codes.
+ *
+ * @param str The string to translate.
+ *
+ * @return The resulting string.
+ */
+char *msn_url_encode(const char *str);
+
+/**
+ * Parses the MSN message formatting into a format compatible with Gaim.
+ *
+ * @param mime The mime header with the formatting.
+ *
+ * @return The new message.
+ */
+char *msn_parse_format(const char *mime);
+
+#endif /* _MSN_UTILS_H_ */