changeset 17936:3c613fec5617

Set status to current status when signing on. This makes the "Sign in as hidden" option no longer needed.
author Jeffrey Connelly <jaconnel@calpoly.edu>
date Thu, 05 Jul 2007 03:13:34 +0000
parents 3aed9c8c6af7
children 231b87b76da4
files libpurple/protocols/myspace/CHANGES libpurple/protocols/myspace/myspace.c
diffstat 2 files changed, 3083 insertions(+), 3087 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/protocols/myspace/CHANGES	Thu Jul 05 00:29:25 2007 +0000
+++ b/libpurple/protocols/myspace/CHANGES	Thu Jul 05 03:13:34 2007 +0000
@@ -3,6 +3,8 @@
  (thanks to Scott Ellis, developing a MySpaceIM plugin for Miranda IM,
   for finding the idle status code.)
 * Time out if no data from server within a certain amount of time (keep alives).
+* Remove "Sign on as hidden" option, and always set status to current status
+  when signing on.
 
 2007-07-03 Jeff Connelly <jeff2@soc.pidgin.im> - 0.10
 * On incoming instant messages, add support for:
--- a/libpurple/protocols/myspace/myspace.c	Thu Jul 05 00:29:25 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Thu Jul 05 03:13:34 2007 +0000
@@ -1,3087 +1,3081 @@
-/* MySpaceIM Protocol Plugin
- *
- * \author Jeff Connelly
- *
- * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
- *
- * Based on Purple's "C Plugin HOWTO" hello world example.
- *
- * Code also drawn from mockprpl:
- *  http://snarfed.org/space/purple+mock+protocol+plugin
- *  Copyright (C) 2004-2007, Ryan Barrett <mockprpl@ryanb.org>
- *
- * and some constructs also based on existing Purple plugins, which are:
- *   Copyright (C) 2003, Robbert Haarman <purple@inglorion.net>
- *   Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
- *   Copyright (C) 2000-2003, Rob Flynn <rob@tgflinux.com>
- *   Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
- *
- * 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
- */
-
-#define PURPLE_PLUGIN
-
-#include "message.h"
-#include "persist.h"
-#include "myspace.h"
-
-/** 
- * Load the plugin.
- */
-gboolean 
-msim_load(PurplePlugin *plugin)
-{
-	/* If compiled to use RC4 from libpurple, check if it is really there. */
-	if (!purple_ciphers_find_cipher("rc4"))
-	{
-		purple_debug_error("msim", "rc4 not in libpurple, but it is required - not loading MySpaceIM plugin!\n");
-		purple_notify_error(plugin, _("Missing Cipher"), 
-				_("The RC4 cipher could not be found"),
-				_("Upgrade "
-					"to a libpurple with RC4 support (>= 2.0.1). MySpaceIM "
-					"plugin will not be loaded."));
-		return FALSE;
-	}
-	return TRUE;
-}
-
-/**
- * Get possible user status types. Based on mockprpl.
- *
- * @return GList of status types.
- */
-GList *
-msim_status_types(PurpleAccount *acct)
-{
-    GList *types;
-    PurpleStatusType *status;
-
-    purple_debug_info("myspace", "returning status types\n");
-
-    types = NULL;
-
-    status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, NULL, FALSE, TRUE, FALSE);
-    types = g_list_append(types, status);
-
-    status = purple_status_type_new_full(PURPLE_STATUS_AWAY, NULL, NULL, FALSE, TRUE, FALSE);
-    types = g_list_append(types, status);
-
-    status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, FALSE, TRUE, FALSE);
-    types = g_list_append(types, status);
-
-    status = purple_status_type_new_full(PURPLE_STATUS_INVISIBLE, NULL, NULL, FALSE, TRUE, FALSE);
-    types = g_list_append(types, status);
-
-    return types;
-}
-
-/**
- * Return the icon name for a buddy and account.
- *
- * @param acct The account to find the icon for, or NULL for protocol icon.
- * @param buddy The buddy to find the icon for, or NULL for the account icon.
- *
- * @return The base icon name string.
- */
-const gchar *
-msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy)
-{
-    /* Use a MySpace icon submitted by hbons at
-     * http://developer.pidgin.im/wiki/MySpaceIM. */
-    return "myspace";
-}
-
-/* Replacement codes to be replaced with associated replacement text,
- * used for protocol message escaping / unescaping. */
-static gchar* msim_replacement_code[] = { "/1", "/2", NULL };
-static gchar* msim_replacement_text[] = { "/", "\\", NULL };
-
-/**
- * Unescape or escape a protocol message.
- *
- * @param msg The message to be unescaped or escaped. WILL BE FREED.
- * @param escape TRUE to escape, FALSE to unescape.
- *
- * @return The unescaped or escaped message. Caller must g_free().
- */
-gchar *
-msim_unescape_or_escape(gchar *msg, gboolean escape)
-{
-	gchar *tmp, *code, *text;
-	guint i;
-
-	/* Replace each code in msim_replacement_code with
-	 * corresponding entry in msim_replacement_text. */
-	for (i = 0; (code = msim_replacement_code[i])
-		   	&& (text = msim_replacement_text[i]); ++i)
-	{
-		if (escape)
-		{
-			tmp = str_replace(msg, text, code);
-		}
-		else
-		{
-			tmp = str_replace(msg, code, text);
-		}
-		g_free(msg);
-		msg = tmp;
-	}
-	
-	return msg;
-}
-
-/**
- * Escape a protocol message.
- *
- * @return The escaped message. Caller must g_free().
- */
-gchar *
-msim_escape(const gchar *msg)
-{
-	return msim_unescape_or_escape(g_strdup(msg), TRUE);
-}
-
-gchar *
-msim_unescape(const gchar *msg)
-{
-	return msim_unescape_or_escape(g_strdup(msg), FALSE);
-}
-
-/**
- * Replace 'old' with 'new' in 'str'.
- *
- * @param str The original string.
- * @param old The substring of 'str' to replace.
- * @param new The replacement for 'old' within 'str'.
- *
- * @return A _new_ string, based on 'str', with 'old' replaced
- * 		by 'new'. Must be g_free()'d by caller.
- *
- * This string replace method is based on
- * http://mail.gnome.org/archives/gtk-app-devel-list/2000-July/msg00201.html
- *
- */
-gchar *
-str_replace(const gchar *str, const gchar *old, const gchar *new)
-{
-	gchar **items;
-	gchar *ret;
-
-	items = g_strsplit(str, old, -1);
-	ret = g_strjoinv(new, items);
-	g_free(items);
-	return ret;
-}
-
-#ifdef MSIM_DEBUG_MSG
-void 
-print_hash_item(gpointer key, gpointer value, gpointer user_data)
-{
-    purple_debug_info("msim", "%s=%s\n", (gchar *)key, (gchar *)value);
-}
-#endif
-
-/** 
- * Send raw data (given as a NUL-terminated string) to the server.
- *
- * @param session 
- * @param msg The raw data to send, in a NUL-terminated string.
- *
- * @return TRUE if succeeded, FALSE if not.
- *
- */
-gboolean 
-msim_send_raw(MsimSession *session, const gchar *msg)
-{
-	purple_debug_info("msim", "msim_send_raw: writing <%s>\n", msg);
-
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-    g_return_val_if_fail(msg != NULL, FALSE);
-
-	return msim_send_really_raw(session->gc, msg, strlen(msg)) ==
-		strlen(msg);
-}
-
-/** Send raw data to the server, possibly with embedded NULs. 
- *
- * Used in prpl_info struct, so that plugins can have the most possible
- * control of what is sent over the connection. Inside this prpl, 
- * msim_send_raw() is used, since it sends NUL-terminated strings (easier).
- *
- * @param gc PurpleConnection
- * @param buf Buffer to send
- * @param total_bytes Size of buffer to send
- *
- * @return Bytes successfully sent, or -1 on error.
- */
-int 
-msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes)
-{
-	int total_bytes_sent;
-    MsimSession *session;
-
-    g_return_val_if_fail(gc != NULL, -1);
-    g_return_val_if_fail(buf != NULL, -1);
-    g_return_val_if_fail(total_bytes >= 0, -1);
-
-    session = (MsimSession *)(gc->proto_data);
-
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), -1);
-	
-	/* Loop until all data is sent, or a failure occurs. */
-	total_bytes_sent = 0;
-	do
-	{
-		int bytes_sent;
-
-		bytes_sent = send(session->fd, buf + total_bytes_sent, 
-                total_bytes - total_bytes_sent, 0);
-
-		if (bytes_sent < 0)
-		{
-			purple_debug_info("msim", "msim_send_raw(%s): send() failed: %s\n",
-					buf, g_strerror(errno));
-			return total_bytes_sent;
-		}
-		total_bytes_sent += bytes_sent;
-
-	} while(total_bytes_sent < total_bytes);
-
-	return total_bytes_sent;
-}
-
-
-/** 
- * Start logging in to the MSIM servers.
- * 
- * @param acct Account information to use to login.
- */
-void 
-msim_login(PurpleAccount *acct)
-{
-    PurpleConnection *gc;
-    const gchar *host;
-    int port;
-
-    g_return_if_fail(acct != NULL);
-    g_return_if_fail(acct->username != NULL);
-
-    purple_debug_info("myspace", "logging in %s\n", acct->username);
-
-    gc = purple_account_get_connection(acct);
-    gc->proto_data = msim_session_new(acct);
-
-    /* Passwords are limited in length. */
-	if (strlen(acct->password) > MSIM_MAX_PASSWORD_LENGTH)
-	{
-		gchar *str;
-
-		str = g_strdup_printf(
-				_("Sorry, passwords over %d characters in length (yours is "
-				"%d) are not supported by the MySpaceIM plugin."), 
-				MSIM_MAX_PASSWORD_LENGTH,
-				(int)strlen(acct->password));
-
-		/* Notify an error message also, because this is important! */
-		purple_notify_error(acct, g_strdup(_("MySpaceIM Error")), str, NULL);
-
-        purple_connection_error(gc, str);
-		
-		g_free(str);
-	}
-
-    /* 1. connect to server */
-    purple_connection_update_progress(gc, _("Connecting"),
-                                  0,   /* which connection step this is */
-                                  4);  /* total number of steps */
-
-    host = purple_account_get_string(acct, "server", MSIM_SERVER);
-    port = purple_account_get_int(acct, "port", MSIM_PORT);
-
-    /* From purple.sf.net/api:
-     * """Note that this function name can be misleading--although it is called 
-     * "proxy connect," it is used for establishing any outgoing TCP connection, 
-     * whether through a proxy or not.""" */
-
-    /* Calls msim_connect_cb when connected. */
-    if (purple_proxy_connect(gc, acct, host, port, msim_connect_cb, gc) == NULL)
-    {
-        /* TODO: try other ports if in auto mode, then save
-         * working port and try that first next time. */
-        purple_connection_error(gc, _("Couldn't create socket"));
-        return;
-    }
-}
-
-/**
- * Process a login challenge, sending a response. 
- *
- * @param session 
- * @param msg Login challenge message.
- *
- * @return TRUE if successful, FALSE if not
- */
-gboolean 
-msim_login_challenge(MsimSession *session, MsimMessage *msg) 
-{
-    PurpleAccount *account;
-    const gchar *response;
-	guint response_len;
-	gchar *nc;
-	gsize nc_len;
-
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-    g_return_val_if_fail(msg != NULL, FALSE);
-
-    g_return_val_if_fail(msim_msg_get_binary(msg, "nc", &nc, &nc_len), FALSE);
-
-    account = session->account;
-
-	g_return_val_if_fail(account != NULL, FALSE);
-
-    purple_connection_update_progress(session->gc, _("Reading challenge"), 1, 4);
-
-    purple_debug_info("msim", "nc is %d bytes, decoded\n", nc_len);
-
-    if (nc_len != 0x40)
-    {
-        purple_debug_info("msim", "bad nc length: %x != 0x40\n", nc_len);
-        purple_connection_error(session->gc, _("Unexpected challenge length from server"));
-        return FALSE;
-    }
-
-    purple_connection_update_progress(session->gc, _("Logging in"), 2, 4);
-
-    response = msim_compute_login_response(nc, account->username, account->password, &response_len);
-
-    g_free(nc);
-
-	return msim_send(session, 
-			"login2", MSIM_TYPE_INTEGER, MSIM_AUTH_ALGORITHM,
-			/* This is actually user's email address. */
-			"username", MSIM_TYPE_STRING, g_strdup(account->username),
-			/* GString and gchar * response will be freed in msim_msg_free() in msim_send(). */
-			"response", MSIM_TYPE_BINARY, g_string_new_len(response, response_len),
-			"clientver", MSIM_TYPE_INTEGER, MSIM_CLIENT_VERSION,
-			"reconn", MSIM_TYPE_INTEGER, 0,
-			"status", MSIM_TYPE_INTEGER, 100,
-			"id", MSIM_TYPE_INTEGER, 1,
-			NULL);
-}
-
-/**
- * Compute the base64'd login challenge response based on username, password, nonce, and IPs.
- *
- * @param nonce The base64 encoded nonce ('nc') field from the server.
- * @param email User's email address (used as login name).
- * @param password User's cleartext password.
- * @param response_len Will be written with response length.
- *
- * @return Binary login challenge response, ready to send to the server. 
- * Must be g_free()'d when finished. NULL if error.
- */
-const gchar *
-msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], 
-		const gchar *email, const gchar *password, guint *response_len)
-{
-    PurpleCipherContext *key_context;
-    PurpleCipher *sha1;
-	PurpleCipherContext *rc4;
-
-    guchar hash_pw[HASH_SIZE];
-    guchar key[HASH_SIZE];
-    gchar *password_utf16le;
-    guchar *data;
-	guchar *data_out;
-	size_t data_len, data_out_len;
-	gsize conv_bytes_read, conv_bytes_written;
-	GError *conv_error;
-#ifdef MSIM_DEBUG_LOGIN_CHALLENGE
-	int i;
-#endif
-
-    g_return_val_if_fail(nonce != NULL, NULL);
-    g_return_val_if_fail(email != NULL, NULL);
-    g_return_val_if_fail(password != NULL, NULL);
-    g_return_val_if_fail(response_len != NULL, NULL);
-
-    /* Convert ASCII password to UTF16 little endian */
-    purple_debug_info("msim", "converting password to UTF-16LE\n");
-	conv_error = NULL;
-	password_utf16le = g_convert(password, -1, "UTF-16LE", "UTF-8", 
-			&conv_bytes_read, &conv_bytes_written, &conv_error);
-
-	g_return_val_if_fail(conv_bytes_read == strlen(password), NULL);
-
-	if (conv_error != NULL)
-	{
-		purple_debug_error("msim", 
-				"g_convert password UTF8->UTF16LE failed: %s",
-				conv_error->message);
-		g_error_free(conv_error);
-        return NULL;
-	}
-
-    /* Compute password hash */ 
-    purple_cipher_digest_region("sha1", (guchar *)password_utf16le, 
-			conv_bytes_written, sizeof(hash_pw), hash_pw, NULL);
-	g_free(password_utf16le);
-
-#ifdef MSIM_DEBUG_LOGIN_CHALLENGE
-    purple_debug_info("msim", "pwhash = ");
-    for (i = 0; i < sizeof(hash_pw); i++)
-        purple_debug_info("msim", "%.2x ", hash_pw[i]);
-    purple_debug_info("msim", "\n");
-#endif
-
-    /* key = sha1(sha1(pw) + nonce2) */
-    sha1 = purple_ciphers_find_cipher("sha1");
-    key_context = purple_cipher_context_new(sha1, NULL);
-    purple_cipher_context_append(key_context, hash_pw, HASH_SIZE);
-    purple_cipher_context_append(key_context, (guchar *)(nonce + NONCE_SIZE), NONCE_SIZE);
-    purple_cipher_context_digest(key_context, sizeof(key), key, NULL);
-
-#ifdef MSIM_DEBUG_LOGIN_CHALLENGE
-    purple_debug_info("msim", "key = ");
-    for (i = 0; i < sizeof(key); i++)
-    {
-        purple_debug_info("msim", "%.2x ", key[i]);
-    }
-    purple_debug_info("msim", "\n");
-#endif
-
-	rc4 = purple_cipher_context_new_by_name("rc4", NULL);
-
-    /* Note: 'key' variable is 0x14 bytes (from SHA-1 hash), 
-     * but only first 0x10 used for the RC4 key. */
-	purple_cipher_context_set_option(rc4, "key_len", (gpointer)0x10);
-	purple_cipher_context_set_key(rc4, key);
-
-    /* TODO: obtain IPs of network interfaces */
-
-    /* rc4 encrypt:
-     * nonce1+email+IP list */
-
-    data_len = NONCE_SIZE + strlen(email) + MSIM_LOGIN_IP_LIST_LEN;
-    data = g_new0(guchar, data_len);
-    memcpy(data, nonce, NONCE_SIZE);
-    memcpy(data + NONCE_SIZE, email, strlen(email));
-    memcpy(data + NONCE_SIZE + strlen(email), MSIM_LOGIN_IP_LIST, MSIM_LOGIN_IP_LIST_LEN);
-
-	data_out = g_new0(guchar, data_len);
-
-    purple_cipher_context_encrypt(rc4, (const guchar *)data, 
-			data_len, data_out, &data_out_len);
-	purple_cipher_context_destroy(rc4);
-
-	g_assert(data_out_len == data_len);
-
-#ifdef MSIM_DEBUG_LOGIN_CHALLENGE
-    purple_debug_info("msim", "response=<%s>\n", data_out);
-#endif
-
-	*response_len = data_out_len;
-
-    return (const gchar *)data_out;
-}
-
-/**
- * Schedule an IM to be sent once the user ID is looked up. 
- *
- * @param gc Connection.
- * @param who A user id, email, or username to send the message to.
- * @param message Instant message text to send.
- * @param flags Flags.
- *
- * @return 1 if successful or postponed, -1 if failed
- *
- * Allows sending to a user by username, email address, or userid. If
- * a username or email address is given, the userid must be looked up.
- * This function does that by calling msim_postprocess_outgoing().
- */
-int 
-msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, 
-		PurpleMessageFlags flags)
-{
-    MsimSession *session;
-    
-    g_return_val_if_fail(gc != NULL, -1);
-    g_return_val_if_fail(who != NULL, -1);
-    g_return_val_if_fail(message != NULL, -1);
-
-	/* 'flags' has many options, not used here. */
-
-	session = (MsimSession *)gc->proto_data;
-
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), -1);
-
-	if (msim_send_bm(session, who, message, MSIM_BM_INSTANT))
-	{
-		/* Return 1 to have Purple show this IM as being sent, 0 to not. I always
-		 * return 1 even if the message could not be sent, since I don't know if
-		 * it has failed yet--because the IM is only sent after the userid is
-		 * retrieved from the server (which happens after this function returns).
-		 */
-        /* TODO: maybe if message is delayed, don't echo to conv window,
-         * but do echo it to conv window manually once it is actually
-         * sent? Would be complicated. */
-		return 1;
-	} else {
-		return -1;
-	}
-    /*
-     * In MySpace, you login with your email address, but don't talk to other
-     * users using their email address. So there is currently an asymmetry in the 
-     * IM windows when using this plugin:
-     *
-     * you@example.com: hello
-     * some_other_user: what's going on?
-     * you@example.com: just coding a prpl
-     *
-     * TODO: Make the sent IM's appear as from the user's username, instead of
-     * their email address. Purple uses the login (in MSIM, the email)--change this.
-     */
-}
-
-/** Send a buddy message of a given type.
- *
- * @param session
- * @param who Username to send message to.
- * @param text Message text to send. Not freed; will be copied.
- * @param type A MSIM_BM_* constant.
- *
- * @return TRUE if success, FALSE if fail.
- *
- * Buddy messages ('bm') include instant messages, action messages, status messages, etc.
- *
- */
-gboolean 
-msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, 
-		int type)
-{
-	gboolean rc;
-	MsimMessage *msg;
-    const gchar *from_username;
-
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-    g_return_val_if_fail(who != NULL, FALSE);
-    g_return_val_if_fail(text != NULL, FALSE);
-   
-	from_username = session->account->username;
-
-    purple_debug_info("msim", "sending %d message from %s to %s: %s\n",
-                  type, from_username, who, text);
-
-	msg = msim_msg_new(TRUE,
-			"bm", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(type),
-			"sesskey", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(session->sesskey),
-			/* 't' will be inserted here */
-			"cv", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(MSIM_CLIENT_VERSION),
-			"msg", MSIM_TYPE_STRING, g_strdup(text),
-			NULL);
-
-	rc = msim_postprocess_outgoing(session, msg, who, "t", "cv");
-
-	msim_msg_free(msg);
-
-	return rc;
-}
-
-
-#ifdef MSIM_FONT_SIZE_WORKS
-/** Convert a msim markup font height to points. */
-static guint 
-msim_font_height_to_point(guint height)
-{
-	/* See also: libpurple/protocols/bonjour/jabber.c
-	 * _font_size_ichat_to_purple */
-	switch (height)
-	{
-	case 11: return 8;
-	case 12: return 9;
-	case 13: return 10;
-	case 14: 
-	case 15: return 11;
-	case 16: return 12;
-	case 17:
-	case 18: return 13;
-	case 19: return 14;
-	case 20: return 15;
-	case 21: return 16;	 
-	default: return 12;
-	}
-}
-#endif
-
-/** Convert the msim markup <f> (font) tag into HTML. */
-static void 
-msim_markup_f_to_html(xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *face, *height_str, *decor_str;
-	GString *gs_end, *gs_begin;
-	guint decor, height;
-
-	face = xmlnode_get_attrib(root, "f");	
-	height_str = xmlnode_get_attrib(root, "h");
-	decor_str = xmlnode_get_attrib(root, "s");
-
-	if (height_str)
-		height = atol(height_str);
-	else
-		height = 12;
-
-	if (decor_str)
-		decor = atol(decor_str);
-	else
-		decor = 0;
-
-	gs_begin = g_string_new("");
-#ifdef MSIM_FONT_SIZE_WORKS
-	/* TODO: get font size working */
-	if (!face)
-		g_string_printf(gs_begin, "<font size='%d'>",
-				msim_font_height_to_point(height)); 
-	else
-		g_string_printf(gs_begin, "<font face='%s' size='%d'>", face, 
-				msim_font_height_to_point(height)); 
-#else
-	if (face)
-	{
-		g_string_printf(gs_begin, "<font face='%s'>", face);
-	} else {
-		g_string_printf(gs_begin, "<font>");
-	}
-#endif
-
-
-	/* No support for font-size CSS? */
-	/* g_string_printf(gs_begin, "<span style='font-family: %s; font-size: %dpt'>", face, 
-			msim_font_height_to_point(height)); */
-
-	gs_end = g_string_new("</font>");
-
-	if (decor & MSIM_TEXT_BOLD)
-	{
-		g_string_append(gs_begin, "<b>");
-		g_string_prepend(gs_end, "</b>");
-	}
-
-	if (decor & MSIM_TEXT_ITALICS)
-	{
-		g_string_append(gs_begin, "<i>");
-		g_string_append(gs_end, "</i>");	
-	}
-
-	if (decor & MSIM_TEXT_UNDERLINE)
-	{
-		g_string_append(gs_begin, "<u>");
-		g_string_append(gs_end, "</u>");	
-	}
-
-
-	*begin = gs_begin->str;
-	*end = gs_end->str;
-}
-
-/** Convert a msim markup color to a color suitable for libpurple.
-  *
-  * @param msim Either a color name, or an rgb(x,y,z) code.
-  *
-  * @return A new string, either a color name or #rrggbb code. Must g_free(). 
-  */
-static char *
-msim_color_to_purple(const char *msim)
-{
-	guint red, green, blue;
-
-	if (!msim)
-		return g_strdup("black");
-
-	if (sscanf(msim, "rgb(%d,%d,%d)", &red, &green, &blue) != 3)
-	{
-		/* Color name. */
-		return g_strdup(msim);
-	}
-	/* TODO: rgba (alpha). */
-
-	return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue);
-}	
-
-/** Convert the msim markup <p> (paragraph) tag into HTML. */
-static void 
-msim_markup_p_to_html(xmlnode *root, gchar **begin, gchar **end)
-{
-    /* Just pass through unchanged. 
-	 *
-	 * Note: attributes currently aren't passed, if there are any. */
-	*begin = g_strdup("<p>");
-	*end = g_strdup("</p>");
-}
-
-/** Convert the msim markup <c> tag (text color) into HTML. TODO: Test */
-static void 
-msim_markup_c_to_html(xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *color;
-	gchar *purple_color;
-
-	color = xmlnode_get_attrib(root, "v");
-	if (!color)
-	{
-		purple_debug_info("msim", "msim_markup_c_to_html: <c> tag w/o v attr");
-		*begin = g_strdup("");
-		*end = g_strdup("");
-		/* TODO: log as unrecognized */
-		return;
-	}
-
-	purple_color = msim_color_to_purple(color);
-
-	*begin = g_strdup_printf("<font color='%s'>", purple_color); 
-
-	g_free(purple_color);
-
-	/* *begin = g_strdup_printf("<span style='color: %s'>", color); */
-	*end = g_strdup("</font>");
-}
-
-/** Convert the msim markup <b> tag (background color) into HTML. TODO: Test */
-static void 
-msim_markup_b_to_html(xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *color;
-	gchar *purple_color;
-
-	color = xmlnode_get_attrib(root, "v");
-	if (!color)
-	{
-		*begin = g_strdup("");
-		*end = g_strdup("");
-		purple_debug_info("msim", "msim_markup_b_to_html: <b> w/o v attr");
-		/* TODO: log as unrecognized. */
-		return;
-	}
-
-	purple_color = msim_color_to_purple(color);
-
-	/* TODO: find out how to set background color. */
-	*begin = g_strdup_printf("<span style='background-color: %s'>", 
-			purple_color);
-	g_free(purple_color);
-
-	*end = g_strdup("</p>");
-}
-
-/** Convert the msim markup <i> tag (emoticon image) into HTML. TODO: Test */
-static void 
-msim_markup_i_to_html(xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *name;
-
-	name = xmlnode_get_attrib(root, "n");
-	if (!name)
-	{
-		purple_debug_info("msim", "msim_markup_i_to_html: <i> w/o n");
-		*begin = g_strdup("");
-		*end = g_strdup("");
-		/* TODO: log as unrecognized */
-		return;
-	}
-
-	/* TODO: Support these emoticons:
-	 *
-	 * bigsmile growl mad scared tongue devil happy messed sidefrown upset 
-	 * frazzled heart nerd sinister wink geek laugh oops smirk worried 
-	 * googles mohawk pirate straight kiss 
-	 */
-
-	*begin = g_strdup_printf("<img id='%s'>", name);
-	*end = g_strdup("</p>");
-}
-
-
-/** Convert an xmlnode of msim markup to an HTML string.
- * @return An HTML string. Caller frees.
- */
-static gchar *
-msim_markup_xmlnode_to_html(xmlnode *root)
-{
-	xmlnode *node;
-	gchar *begin, *inner, *end;
-	gchar *final;
-
-	if (!root || !root->name)
-		return g_strdup("");
-
-	purple_debug_info("msim", "msim_markup_xmlnode_to_html: got root=%s\n",
-			root->name);
-
-	begin = inner = end = NULL;
-
-	if (!strcmp(root->name, "f"))
-	{
-		msim_markup_f_to_html(root, &begin, &end);
-	} else if (!strcmp(root->name, "p")) {
-		msim_markup_p_to_html(root, &begin, &end);
-	} else if (!strcmp(root->name, "c")) {
-		msim_markup_c_to_html(root, &begin, &end);
-	} else if (!strcmp(root->name, "b")) {
-		msim_markup_b_to_html(root, &begin, &end);
-	} else if (!strcmp(root->name, "i")) {
-		msim_markup_i_to_html(root, &begin, &end);
-	} else {
-		purple_debug_info("msim", "msim_markup_xmlnode_to_html: "
-				"unknown tag name=%s, ignoring", root->name);
-		begin = g_strdup("");
-		end = g_strdup("");
-	}
-
-	/* Loop over all child nodes. */
- 	for (node = root->child; node != NULL; node = node->next)
-	{
-		switch (node->type)
-		{
-		case XMLNODE_TYPE_ATTRIB:
-			/* Attributes handled above. */
-			break;
-
-		case XMLNODE_TYPE_TAG:
-			/* A tag or tag with attributes. Recursively descend. */
-			inner = msim_markup_xmlnode_to_html(node);
-            g_return_val_if_fail(inner != NULL, NULL);
-
-			purple_debug_info("msim", " ** node name=%s\n", node->name);
-			break;
-	
-		case XMLNODE_TYPE_DATA:	
-			/* Literal text. */
-			inner = g_new0(char, node->data_sz + 1);
-			strncpy(inner, node->data, node->data_sz);
-			inner[node->data_sz + 1] = 0;
-
-			purple_debug_info("msim", " ** node data=%s\n", inner);
-			break;
-			
-		default:
-			purple_debug_info("msim",
-					"msim_markup_xmlnode_to_html: strange node\n");
-			inner = g_strdup("");
-		}
-	}
-
-	final = g_strconcat(begin, inner, end, NULL);
-	g_free(begin);
-	g_free(inner);
-	g_free(end);
-
-	purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n",
-			final);
-
-	return final;
-}
-
-/** Convert MySpaceIM markup to Purple (HTML) markup. 
- *
- * @return Purple markup string, must be g_free()'d. */
-gchar *
-msim_markup_to_html(const gchar *raw)
-{
-	xmlnode *root;
-	gchar *str;
-
-	root = xmlnode_from_str(raw, -1);
-	if (!root)
-	{
-		purple_debug_info("msim", "msim_markup_to_html: couldn't parse "
-				"%s as XML, returning raw\n", raw);
-		return g_strdup(raw);
-	}
-
-	str = msim_markup_xmlnode_to_html(root);
-	purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str);
-
-	xmlnode_free(root);
-
-	return g_strdup(str);
-}
-
-/**
- * Handle an incoming instant message.
- *
- * @param session The session
- * @param msg Message from the server, containing 'f' (userid from) and 'msg'. 
- * 			  Should also contain username in _username from preprocessing.
- *
- * @return TRUE if successful.
- */
-gboolean 
-msim_incoming_im(MsimSession *session, MsimMessage *msg)
-{
-    gchar *username, *msg_msim_markup, *msg_purple_markup;
-
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-    g_return_val_if_fail(msg != NULL, FALSE);
-
-    username = msim_msg_get_string(msg, "_username");
-    g_return_val_if_fail(username != NULL, FALSE);
-
-	msg_msim_markup = msim_msg_get_string(msg, "msg");
-    g_return_val_if_fail(msg_msim_markup != NULL, FALSE);
-
-	msg_purple_markup = msim_markup_to_html(msg_msim_markup);
-	g_free(msg_msim_markup);
-
-    serv_got_im(session->gc, username, msg_purple_markup, 
-			PURPLE_MESSAGE_RECV, time(NULL));
-
-	g_free(username);
-	g_free(msg_purple_markup);
-
-	return TRUE;
-}
-
-/**
- * Process unrecognized information.
- *
- * @param session
- * @param msg An MsimMessage that was unrecognized, or NULL.
- * @param note Information on what was unrecognized, or NULL.
- */
-void 
-msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note)
-{
-	/* TODO: Some more context, outwardly equivalent to a backtrace, 
-     * for helping figure out what this msg is for. What was going on?
-     * But not too much information so that a user
-	 * posting this dump reveals confidential information.
-	 */
-
-	/* TODO: dump unknown msgs to file, so user can send them to me
-	 * if they wish, to help add support for new messages (inspired
-	 * by Alexandr Shutko, who maintains OSCAR protocol documentation). */
-
-	purple_debug_info("msim", "Unrecognized data on account for %s\n", 
-            session->account->username);
-	if (note)
-	{
-		purple_debug_info("msim", "(Note: %s)\n", note);
-	}
-
-    if (msg)
-    {
-        msim_msg_dump("Unrecognized message dump: %s\n", msg);
-    }
-}
-
-/**
- * Handle an incoming action message.
- *
- * @param session
- * @param msg
- *
- * @return TRUE if successful.
- *
- * UNTESTED
- */
-gboolean 
-msim_incoming_action(MsimSession *session, MsimMessage *msg)
-{
-	gchar *msg_text, *username;
-	gboolean rc;
-
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-    g_return_val_if_fail(msg != NULL, FALSE);
-
-	msg_text = msim_msg_get_string(msg, "msg");
-    g_return_val_if_fail(msg_text != NULL, FALSE);
-
-	username = msim_msg_get_string(msg, "_username");
-    g_return_val_if_fail(username != NULL, FALSE);
-
-	purple_debug_info("msim", "msim_incoming_action: action <%s> from <%d>\n", 
-            msg_text, username);
-
-	if (strcmp(msg_text, "%typing%") == 0)
-	{
-		/* TODO: find out if msim repeatedly sends typing messages, so we can 
-         * give it a timeout. Right now, there does seem to be an inordinately 
-         * amount of time between typing stopped-typing notifications. */
-		serv_got_typing(session->gc, username, 0, PURPLE_TYPING);
-		rc = TRUE;
-	} else if (strcmp(msg_text, "%stoptyping%") == 0) {
-		serv_got_typing_stopped(session->gc, username);
-		rc = TRUE;
-	} else {
-		msim_unrecognized(session, msg, 
-                "got to msim_incoming_action but unrecognized value for 'msg'");
-		rc = FALSE;
-	}
-
-	g_free(msg_text);
-	g_free(username);
-
-	return rc;
-}
-
-/** 
- * Handle when our user starts or stops typing to another user.
- *
- * @param gc
- * @param name The buddy name to which our user is typing to
- * @param state PURPLE_TYPING, PURPLE_TYPED, PURPLE_NOT_TYPING
- *
- * @return 0
- */
-unsigned int 
-msim_send_typing(PurpleConnection *gc, const gchar *name, 
-        PurpleTypingState state)
-{
-	const gchar *typing_str;
-	MsimSession *session;
-
-    g_return_val_if_fail(gc != NULL, 0);
-    g_return_val_if_fail(name != NULL, 0);
-
-	session = (MsimSession *)gc->proto_data;
-
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), 0);
-
-	switch (state)
-	{	
-		case PURPLE_TYPING: 
-			typing_str = "%typing%"; 
-			break;
-
-		case PURPLE_TYPED:
-		case PURPLE_NOT_TYPING:
-		default:
-			typing_str = "%stoptyping%";
-			break;
-	}
-
-	purple_debug_info("msim", "msim_send_typing(%s): %d (%s)\n", name, state, typing_str);
-	msim_send_bm(session, name, typing_str, MSIM_BM_ACTION);
-	return 0;
-}
-
-/** Callback for msim_get_info(), for when user info is received. */
-void 
-msim_get_info_cb(MsimSession *session, MsimMessage *user_info_msg, gpointer data)
-{
-	GHashTable *body;
-	gchar *body_str;
-	MsimMessage *msg;
-	gchar *user;
-	PurpleNotifyUserInfo *user_info;
-	PurpleBuddy *buddy;
-	const gchar *str, *str2;
-
-    g_return_if_fail(MSIM_SESSION_VALID(session));
-
-	/* Get user{name,id} from msim_get_info, passed as an MsimMessage for 
-	   orthogonality. */
-	msg = (MsimMessage *)data;
-    g_return_if_fail(msg != NULL);
-
-	user = msim_msg_get_string(msg, "user");
-	if (!user)
-	{
-		purple_debug_info("msim", "msim_get_info_cb: no 'user' in msg");
-		return;
-	}
-
-	msim_msg_free(msg);
-	purple_debug_info("msim", "msim_get_info_cb: got for user: %s\n", user);
-
-	body_str = msim_msg_get_string(user_info_msg, "body");
-    g_return_if_fail(body_str != NULL);
-	body = msim_parse_body(body_str);
-	g_free(body_str);
-
-	buddy = purple_find_buddy(session->account, user);
-	/* Note: don't assume buddy is non-NULL; will be if lookup random user 
-     * not on blist. */
-
-	user_info = purple_notify_user_info_new();
-
-	/* Identification */
-	purple_notify_user_info_add_pair(user_info, _("User"), user);
-
-	/* note: g_hash_table_lookup does not create a new string! */
-	str = g_hash_table_lookup(body, "UserID");
-	if (str)
-		purple_notify_user_info_add_pair(user_info, _("User ID"), 
-				g_strdup(str));
-
-	/* a/s/l...the vitals */	
-	str = g_hash_table_lookup(body, "Age");
-	if (str)
-		purple_notify_user_info_add_pair(user_info, _("Age"), g_strdup(str));
-
-	str = g_hash_table_lookup(body, "Gender");
-	if (str)
-		purple_notify_user_info_add_pair(user_info, _("Gender"), g_strdup(str));
-
-	str = g_hash_table_lookup(body, "Location");
-	if (str)
-		purple_notify_user_info_add_pair(user_info, _("Location"), 
-				g_strdup(str));
-
-	/* Other information */
-
-	/* Headline comes from buddy status messages */
-	if (buddy)
-	{
-		str = purple_blist_node_get_string(&buddy->node, "Headline");
-		if (str)
-			purple_notify_user_info_add_pair(user_info, "Headline", str);
-	}
-
-
-	str = g_hash_table_lookup(body, "BandName");
-	str2 = g_hash_table_lookup(body, "SongName");
-	if (str || str2)
-	{
-		purple_notify_user_info_add_pair(user_info, _("Song"), 
-			g_strdup_printf("%s - %s",
-				str ? str : "Unknown Artist",
-				str2 ? str2 : "Unknown Song"));
-	}
-
-
-	/* Total friends only available if looked up by uid, not username. */
-	str = g_hash_table_lookup(body, "TotalFriends");
-	if (str)
-		purple_notify_user_info_add_pair(user_info, _("Total Friends"), 
-			g_strdup(str));
-
-	purple_notify_userinfo(session->gc, user, user_info, NULL, NULL);
-	purple_debug_info("msim", "msim_get_info_cb: username=%s\n", user);
-	//purple_notify_user_info_destroy(user_info);
-	/* Do not free username, since it will be used by user_info. */
-
-	//g_hash_table_destroy(body);
-}
-
-/** Retrieve a user's profile. */
-void 
-msim_get_info(PurpleConnection *gc, const gchar *user)
-{
-	PurpleBuddy *buddy;
-	MsimSession *session;
-	guint uid;
-	gchar *user_to_lookup;
-	MsimMessage *user_msg;
-
-    g_return_if_fail(gc != NULL);
-    g_return_if_fail(user != NULL);
-
-	session = (MsimSession *)gc->proto_data;
-
-    g_return_if_fail(MSIM_SESSION_VALID(session));
-
-	/* Obtain uid of buddy. */
-	buddy = purple_find_buddy(session->account, user);
-	if (buddy)
-	{
-		uid = purple_blist_node_get_int(&buddy->node, "UserID");
-		if (!uid)
-		{
-			PurpleNotifyUserInfo *user_info;
-
-			user_info = purple_notify_user_info_new();
-			purple_notify_user_info_add_pair(user_info, NULL,
-					_("This buddy appears to not have a userid stored in the buddy list, can't look up. Is the user really on the buddy list?"));
-
-			purple_notify_userinfo(session->gc, user, user_info, NULL, NULL);
-			purple_notify_user_info_destroy(user_info);
-			return;
-		}
-
-		user_to_lookup = g_strdup_printf("%d", uid);
-	} else {
-
-		/* Looking up buddy not on blist. Lookup by whatever user entered. */
-		user_to_lookup = g_strdup(user);
-	}
-
-	/* Pass the username to msim_get_info_cb(), because since we lookup
-	 * by userid, the userinfo message will only contain the uid (not 
-	 * the username).
-	 */
-	user_msg = msim_msg_new(TRUE, 
-			"user", MSIM_TYPE_STRING, g_strdup(user),
-			NULL);
-	purple_debug_info("msim", "msim_get_info, setting up lookup, user=%s\n", user);
-
-	msim_lookup_user(session, user_to_lookup, msim_get_info_cb, user_msg);
-
-	g_free(user_to_lookup); 
-}
-
-/** Set your status - callback for when user manually sets it. */
-void
-msim_set_status(PurpleAccount *account, PurpleStatus *status)
-{
-	PurpleStatusType *type;
-	MsimSession *session;
-    guint status_code;
-
-	session = (MsimSession *)account->gc->proto_data;
-
-    g_return_if_fail(MSIM_SESSION_VALID(session));
-
-	type = purple_status_get_type(status);
-
-	switch (purple_status_type_get_primitive(type))
-	{
-		case PURPLE_STATUS_AVAILABLE:
-			status_code = MSIM_STATUS_CODE_ONLINE;
-			break;
-
-		case PURPLE_STATUS_INVISIBLE:
-			status_code = MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN;
-			break;
-
-		case PURPLE_STATUS_AWAY:
-			status_code = MSIM_STATUS_CODE_AWAY;
-			break;
-
-		default:
-			purple_debug_info("msim", "msim_set_status: unknown "
-					"status interpreting as online");
-			status_code = MSIM_STATUS_CODE_ONLINE;
-			break;
-	}
-
-
-    msim_set_status_code(session, status_code);
-}
-
-/** Go idle. */
-void
-msim_set_idle(PurpleConnection *gc, int time)
-{
-    MsimSession *session;
-
-    g_return_if_fail(gc != NULL);
-
-    session = (MsimSession *)gc->proto_data;
-
-    g_return_if_fail(MSIM_SESSION_VALID(session));
-
-    if (time == 0)
-    {
-        /* Going back from idle. In msim, idle is mutually exclusive 
-         * from the other states (you can only be away or idle, but not
-         * both, for example), so by going non-idle I go online.
-         */
-        msim_set_status_code(session, MSIM_STATUS_CODE_ONLINE);
-    } else {
-        /* msim doesn't support idle time, so just go idle */
-        msim_set_status_code(session, MSIM_STATUS_CODE_IDLE);
-    }
-}
-
-/** Set status using an MSIM_STATUS_CODE_* value. (TODO: also set message) */
-void 
-msim_set_status_code(MsimSession *session, guint status_code)
-{
-    g_return_if_fail(MSIM_SESSION_VALID(session));
-
-	if (!msim_send(session,
-			"status", MSIM_TYPE_INTEGER, status_code,
-			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
-			"statstring", MSIM_TYPE_STRING, g_strdup(""),
-			"locstring", MSIM_TYPE_STRING, g_strdup("")))
-	{
-		purple_debug_info("msim", "msim_set_status: failed to set status");
-	}
-
-}
-
-/** After a uid is resolved to username, tag it with the username and submit for processing. 
- * 
- * @param session
- * @param userinfo Response messsage to resolving request.
- * @param data MsimMessage *, the message to attach information to. 
- */
-static void 
-msim_incoming_resolved(MsimSession *session, MsimMessage *userinfo, 
-		gpointer data)
-{
-	gchar *body_str;
-	GHashTable *body;
-	gchar *username;
-	MsimMessage *msg;
-
-    g_return_if_fail(MSIM_SESSION_VALID(session));
-    g_return_if_fail(userinfo != NULL);
-
-	body_str = msim_msg_get_string(userinfo, "body");
-	g_return_if_fail(body_str != NULL);
-	body = msim_parse_body(body_str);
-	g_return_if_fail(body != NULL);
-	g_free(body_str);
-
-	username = g_hash_table_lookup(body, "UserName");
-	g_return_if_fail(username != NULL);
-
-	msg = (MsimMessage *)data;
-    g_return_if_fail(msg != NULL);
-
-	/* Special elements name beginning with '_', we'll use internally within the
-	 * program (did not come from the wire). */
-	msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username));
-
-	msim_process(session, msg);
-
-	/* TODO: Free copy cloned from  msim_preprocess_incoming(). */
-	//XXX msim_msg_free(msg);
-	g_hash_table_destroy(body);
-}
-
-#if 0
-/* Lookup a username by userid, from buddy list. 
- *
- * @param wanted_uid
- *
- * @return Username of wanted_uid, if on blist, or NULL. Static string. 
- *
- * XXX WARNING: UNKNOWN MEMORY CORRUPTION HERE! 
- */
-static const gchar *
-msim_uid2username_from_blist(MsimSession *session, guint wanted_uid)
-{
-	GSList *buddies, *cur;
-
-	buddies = purple_find_buddies(session->account, NULL); 
-
-	if (!buddies)
-	{
-		purple_debug_info("msim", "msim_uid2username_from_blist: no buddies?");
-		return NULL;
-	}
-
-	for (cur = buddies; cur != NULL; cur = g_slist_next(cur))
-	{
-		PurpleBuddy *buddy;
-		//PurpleBlistNode *node;
-		guint uid;
-		const gchar *name;
-
-
-		/* See finch/gnthistory.c */
-		buddy = cur->data;
-		//node  = cur->data;
-
-		uid = purple_blist_node_get_int(&buddy->node, "UserID");
-		//uid = purple_blist_node_get_int(node, "UserID");
-
-		/* name = buddy->name; */								/* crash */
-		/* name = PURPLE_BLIST_NODE_NAME(&buddy->node);  */		/* crash */
-
-		/* XXX Is this right? Memory corruption here somehow. Happens only
-		 * when return one of these values. */
-		name = purple_buddy_get_name(buddy); 					/* crash */
-		//name = purple_buddy_get_name((PurpleBuddy *)node); 	/* crash */
-		/* return name; */										/* crash (with above) */
-
-		/* name = NULL; */										/* no crash */
-		/* return NULL; */										/* no crash (with anything) */
-
-		/* crash =
-*** glibc detected *** pidgin: realloc(): invalid pointer: 0x0000000000d2aec0 ***
-======= Backtrace: =========
-/lib/libc.so.6(__libc_realloc+0x323)[0x2b7bfc012e03]
-/usr/lib/libglib-2.0.so.0(g_realloc+0x31)[0x2b7bfba79a41]
-/usr/lib/libgtk-x11-2.0.so.0(gtk_tree_path_append_index+0x3a)[0x2b7bfa110d5a]
-/usr/lib/libgtk-x11-2.0.so.0[0x2b7bfa1287dc]
-/usr/lib/libgtk-x11-2.0.so.0[0x2b7bfa128e56]
-/usr/lib/libgtk-x11-2.0.so.0[0x2b7bfa128efd]
-/usr/lib/libglib-2.0.so.0(g_main_context_dispatch+0x1b4)[0x2b7bfba72c84]
-/usr/lib/libglib-2.0.so.0[0x2b7bfba75acd]
-/usr/lib/libglib-2.0.so.0(g_main_loop_run+0x1ca)[0x2b7bfba75dda]
-/usr/lib/libgtk-x11-2.0.so.0(gtk_main+0xa3)[0x2b7bfa0475f3]
-pidgin(main+0x8be)[0x46b45e]
-/lib/libc.so.6(__libc_start_main+0xf4)[0x2b7bfbfbf0c4]
-pidgin(gtk_widget_grab_focus+0x39)[0x429ab9]
-
-or:
- *** glibc detected *** /usr/local/bin/pidgin: malloc(): memory corruption (fast): 0x0000000000c10076 ***
- (gdb) bt
-#0  0x00002b4074ecd47b in raise () from /lib/libc.so.6
-#1  0x00002b4074eceda0 in abort () from /lib/libc.so.6
-#2  0x00002b4074f0453b in __fsetlocking () from /lib/libc.so.6
-#3  0x00002b4074f0c810 in free () from /lib/libc.so.6
-#4  0x00002b4074f0d6dd in malloc () from /lib/libc.so.6
-#5  0x00002b4074974b5b in g_malloc () from /usr/lib/libglib-2.0.so.0
-#6  0x00002b40749868bf in g_strdup () from /usr/lib/libglib-2.0.so.0
-#7  0x00002b407810969f in msim_parse (
-    raw=0xd2a910 "\\bm\\100\\f\\3656574\\msg\\|s|0|ss|Offline")
-	    at message.c:648
-#8  0x00002b407810889c in msim_input_cb (gc_uncasted=0xcf92c0, 
-    source=<value optimized out>, cond=<value optimized out>) at myspace.c:1478
-
-
-	Why is it crashing in msim_parse()'s g_strdup()?
-*/
-		purple_debug_info("msim", "msim_uid2username_from_blist: %s's uid=%d (want %d)\n",
-				name, uid, wanted_uid);
-
-		if (uid == wanted_uid)
-		{
-			gchar *ret;
-
-			ret = g_strdup(name);
-
-			g_slist_free(buddies);
-
-			return ret;
-		}
-	}
-
-	g_slist_free(buddies);
-	return NULL;
-}
-#endif
-
-/** Preprocess incoming messages, resolving as needed, calling msim_process() when ready to process.
- *
- * @param session
- * @param msg MsimMessage *, freed by caller.
- */
-gboolean 
-msim_preprocess_incoming(MsimSession *session, MsimMessage *msg)
-{
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-    g_return_val_if_fail(msg != NULL, FALSE);
-
-	if (msim_msg_get(msg, "bm") && msim_msg_get(msg, "f"))
-	{
-		guint uid;
-		const gchar *username;
-
-		/* 'f' = userid message is from, in buddy messages */
-		uid = msim_msg_get_integer(msg, "f");
-
-		/* TODO: Make caching work. Currently it is commented out because
-		 * it crashes for unknown reasons, memory realloc error. */
-#if 0
-		username = msim_uid2username_from_blist(session, uid); 
-#else
-		username = NULL; 
-#endif
-
-		if (username)
-		{
-			/* Know username already, use it. */
-			purple_debug_info("msim", "msim_preprocess_incoming: tagging with _username=%s\n",
-					username);
-			msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username));
-			return msim_process(session, msg);
-
-		} else {
-			gchar *from;
-
-			/* Send lookup request. */
-			/* XXX: where is msim_msg_get_string() freed? make _strdup and _nonstrdup. */
-			purple_debug_info("msim", "msim_incoming: sending lookup, setting up callback\n");
-			from = msim_msg_get_string(msg, "f");
-			msim_lookup_user(session, from, msim_incoming_resolved, msim_msg_clone(msg)); 
-			g_free(from);
-
-			/* indeterminate */
-			return TRUE;
-		}
-	} else {
-		/* Nothing to resolve - send directly to processing. */
-		return msim_process(session, msg);
-	}
-}
-
-/** Check if the connection is still alive, based on last communication. */
-gboolean
-msim_check_alive(gpointer data)
-{
-    MsimSession *session;
-    time_t delta;
-    gchar *errmsg;
-
-    session = (MsimSession *)data;
-
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-
-    delta = time(NULL) - session->last_comm;
-    purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta);
-    if (delta >= MSIM_KEEPALIVE_INTERVAL)
-    {
-        errmsg = g_strdup_printf(_("Connection to server lost (no data received within %d seconds)"), delta);
-
-        purple_debug_info("msim", "msim_check_alive: %s > interval of %d, presumed dead\n",
-                errmsg, MSIM_KEEPALIVE_INTERVAL);
-        purple_connection_error(session->gc, errmsg);
-
-        purple_notify_error(session->gc, NULL, errmsg, NULL);
-
-        g_free(errmsg);
-
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-/** Called when the session key arrives. */
-gboolean
-msim_we_are_logged_on(MsimSession *session, MsimMessage *msg)
-{
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-    g_return_val_if_fail(msg != NULL, FALSE);
-
-    purple_connection_update_progress(session->gc, _("Connected"), 3, 4);
-
-    session->sesskey = msim_msg_get_integer(msg, "sesskey");
-    purple_debug_info("msim", "SESSKEY=<%d>\n", session->sesskey);
-
-    /* Comes with: proof,profileid,userid,uniquenick -- all same values
-     * some of the time, but can vary. This is our own user ID. */
-    session->userid = msim_msg_get_integer(msg, "userid");
-
-    purple_connection_set_state(session->gc, PURPLE_CONNECTED);
-
-    /* We now know are our own username, only after we're logged in..
-     * which is weird, but happens because you login with your email
-     * address and not username. Will be freed in msim_session_destroy(). */
-    session->username = msim_msg_get_string(msg, "uniquenick");
-
-#ifdef MSIM_FAKE_SELF_ONLINE
-    /* Fake our self coming online. */
-    purple_prpl_got_user_status(session->account, session->username, purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE), NULL);
-#endif
-
-    /* Set status depending on preference. */
-    /* TODO: set status to current status, change status to hidden if sign on as hidden. 
-     * Or remove this preference alltogether, and set status to current status? */
-    msim_set_status_code(session, 
-            purple_account_get_bool(session->account, "hidden", FALSE) 
-            ?  PURPLE_STATUS_INVISIBLE
-            : PURPLE_STATUS_AVAILABLE);
-
-    purple_timeout_add(MSIM_KEEPALIVE_INTERVAL_CHECK, msim_check_alive, session);
-
-    return TRUE;
-}
-
-/**
- * Process a message. 
- *
- * @param session
- * @param msg A message from the server, ready for processing (possibly with resolved username information attached). Caller frees.
- *
- * @return TRUE if successful. FALSE if processing failed.
- */
-gboolean 
-msim_process(MsimSession *session, MsimMessage *msg)
-{
-    g_return_val_if_fail(session != NULL, FALSE);
-    g_return_val_if_fail(msg != NULL, FALSE);
-
-#ifdef MSIM_DEBUG_MSG
-	{
-		msim_msg_dump("ready to process: %s\n", msg);
-	}
-#endif
-
-    if (msim_msg_get(msg, "nc"))
-    {
-        return msim_login_challenge(session, msg);
-    } else if (msim_msg_get(msg, "sesskey")) {
-        return msim_we_are_logged_on(session, msg);
-    } else if (msim_msg_get(msg, "bm"))  {
-        guint bm;
-       
-        bm = msim_msg_get_integer(msg, "bm");
-        switch (bm)
-        {
-            case MSIM_BM_STATUS:
-                return msim_status(session, msg);
-            case MSIM_BM_INSTANT:
-                return msim_incoming_im(session, msg);
-			case MSIM_BM_ACTION:
-				return msim_incoming_action(session, msg);
-            default:
-                /* Not really an IM, but show it for informational 
-                 * purposes during development. */
-                return msim_incoming_im(session, msg);
-        }
-    } else if (msim_msg_get(msg, "rid")) {
-        return msim_process_reply(session, msg);
-    } else if (msim_msg_get(msg, "error")) {
-        return msim_error(session, msg);
-    } else if (msim_msg_get(msg, "ka")) {
-		/* TODO: Setup a timer, if keep-alive is not received within ~3 minutes, then 
-		 * disconnect the user. As it stands, if Internet connection goes out (this
-		 * just happened here), msimprpl will appear to be connected forever, while
-		 * other plugins (oscar, etc.) will time out. Msimprpl should timeout too. */
-        purple_debug_info("msim", "msim_process: got keep alive\n");
-        return TRUE;
-    } else {
-		msim_unrecognized(session, msg, "in msim_process");
-        return FALSE;
-    }
-}
-
-/** Store an field of information about a buddy. */
-void 
-msim_store_buddy_info_each(gpointer key, gpointer value, gpointer user_data)
-{
-	PurpleBuddy *buddy;
-	gchar *key_str, *value_str;
-
-	buddy = (PurpleBuddy *)user_data;
-	key_str = (gchar *)key;
-	value_str = (gchar *)value;
-
-	if (strcmp(key_str, "UserID") == 0 ||
-			strcmp(key_str, "Age") == 0 ||
-			strcmp(key_str, "TotalFriends") == 0)
-	{
-		/* Certain fields get set as integers, instead of strings, for
-		 * convenience. May not be the best way to do it, but having at least
-		 * UserID as an integer is convenient...until it overflows! */
-		purple_blist_node_set_int(&buddy->node, key_str, atol(value_str));
-	} else {
-		purple_blist_node_set_string(&buddy->node, key_str, value_str);
-	}
-}
-
-/** Save buddy information to the buddy list from a user info reply message.
- *
- * @param session
- * @param msg The user information reply, with any amount of information.
- *
- * The information is saved to the buddy's blist node, which ends up in blist.xml.
- */
-gboolean 
-msim_store_buddy_info(MsimSession *session, MsimMessage *msg)
-{
-	GHashTable *body;
-	gchar *username, *body_str, *uid;
-	PurpleBuddy *buddy;
-	guint rid;
-
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-    g_return_val_if_fail(msg != NULL, FALSE);
- 
-	rid = msim_msg_get_integer(msg, "rid");
-	
-	g_return_val_if_fail(rid != 0, FALSE);
-
-	body_str = msim_msg_get_string(msg, "body");
-	g_return_val_if_fail(body_str != NULL, FALSE);
-	body = msim_parse_body(body_str);
-	g_free(body_str);
-
-	/* TODO: implement a better hash-like interface, and use it. */
-	username = g_hash_table_lookup(body, "UserName");
-
-	if (!username)
-	{
-		purple_debug_info("msim", 
-			"msim_process_reply: not caching body, no UserName\n");
-		return FALSE;
-	}
-
-	uid = g_hash_table_lookup(body, "UserID");
-	g_return_val_if_fail(uid, FALSE);
-
-	purple_debug_info("msim", "associating uid %d with username %s\n", uid, username);
-
-	buddy = purple_find_buddy(session->account, username);
-	if (buddy)
-	{
-		g_hash_table_foreach(body, msim_store_buddy_info_each, buddy);
-	}
-
-	return TRUE;
-}
-
-/**
- * Process a persistance message reply from the server.
- *
- * @param session 
- * @param msg Message reply from server.
- *
- * @return TRUE if successful.
- */
-gboolean 
-msim_process_reply(MsimSession *session, MsimMessage *msg)
-{
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-    g_return_val_if_fail(msg != NULL, FALSE);
-    
-    if (msim_msg_get(msg, "rid"))  /* msim_lookup_user sets callback for here */
-    {
-        MSIM_USER_LOOKUP_CB cb;
-        gpointer data;
-        guint rid;
-
-		msim_store_buddy_info(session, msg);		
-
-		rid = msim_msg_get_integer(msg, "rid");
-
-        /* If a callback is registered for this userid lookup, call it. */
-        cb = g_hash_table_lookup(session->user_lookup_cb, GUINT_TO_POINTER(rid));
-        data = g_hash_table_lookup(session->user_lookup_cb_data, GUINT_TO_POINTER(rid));
-
-        if (cb)
-        {
-            purple_debug_info("msim", 
-					"msim_process_body: calling callback now\n");
-			/* Clone message, so that the callback 'cb' can use it (needs to free it also). */
-            cb(session, msim_msg_clone(msg), data);
-            g_hash_table_remove(session->user_lookup_cb, GUINT_TO_POINTER(rid));
-            g_hash_table_remove(session->user_lookup_cb_data, GUINT_TO_POINTER(rid));
-        } else {
-            purple_debug_info("msim", 
-					"msim_process_body: no callback for rid %d\n", rid);
-        }
-    }
-
-	return TRUE;
-}
-
-/**
- * Handle an error from the server.
- *
- * @param session 
- * @param msg The message.
- *
- * @return TRUE if successfully reported error.
- */
-gboolean 
-msim_error(MsimSession *session, MsimMessage *msg)
-{
-    gchar *errmsg, *full_errmsg;
-	guint err;
-
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-    g_return_val_if_fail(msg != NULL, FALSE);
-
-    err = msim_msg_get_integer(msg, "err");
-    errmsg = msim_msg_get_string(msg, "errmsg");
-
-    full_errmsg = g_strdup_printf(_("Protocol error, code %d: %s"), err, 
-			errmsg ? errmsg : "no 'errmsg' given");
-
-	g_free(errmsg);
-
-    purple_debug_info("msim", "msim_error: %s\n", full_errmsg);
-
-    purple_notify_error(session->account, g_strdup(_("MySpaceIM Error")), 
-            full_errmsg, NULL);
-
-	/* Destroy session if fatal. */
-    if (msim_msg_get(msg, "fatal"))
-    {
-        purple_debug_info("msim", "fatal error, closing\n");
-        purple_connection_error(session->gc, full_errmsg);
-    }
-
-    return TRUE;
-}
-
-/**
- * Process incoming status messages.
- *
- * @param session
- * @param msg Status update message. Caller frees.
- *
- * @return TRUE if successful.
- */
-gboolean 
-msim_status(MsimSession *session, MsimMessage *msg)
-{
-    PurpleBuddyList *blist;
-    PurpleBuddy *buddy;
-    //PurpleStatus *status;
-    gchar **status_array;
-    GList *list;
-    gchar *status_headline;
-    gchar *status_str;
-    gint i, status_code, purple_status_code;
-    gchar *username;
-
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-    g_return_val_if_fail(msg != NULL, FALSE);
-
-    status_str = msim_msg_get_string(msg, "msg");
-	g_return_val_if_fail(status_str != NULL, FALSE);
-
-	msim_msg_dump("msim_status msg=%s\n", msg);
-
-	/* Helpfully looked up by msim_incoming_resolve() for us. */
-    username = msim_msg_get_string(msg, "_username");
-    /* Note: DisplayName doesn't seem to be resolvable. It could be displayed on
-     * the buddy list, if the UserID was stored along with it. */
-
-	if (username == NULL)
-	{
-		g_free(status_str);
-		g_return_val_if_fail(NULL, FALSE);
-	}
-
-    purple_debug_info("msim", 
-			"msim_status_cb: updating status for <%s> to <%s>\n", 
-			username, status_str);
-
-    /* TODO: generic functions to split into a GList, part of MsimMessage */
-    status_array = g_strsplit(status_str, "|", 0);
-    for (list = NULL, i = 0;
-            status_array[i];
-            i++)
-    {
-		/* Note: this adds the 0th ordinal too, which might not be a value
-		 * at all (the 0 in the 0|1|2|3... status fields, but 0 always appears blank).
-		 */
-        list = g_list_append(list, status_array[i]);
-    }
-
-    /* Example fields: 
-	 *  |s|0|ss|Offline 
-	 *  |s|1|ss|:-)|ls||ip|0|p|0 
-	 *
-	 * TODO: write list support in MsimMessage, and use it here.
-	 */
-
-    status_code = atoi(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE));
-	purple_debug_info("msim", "msim_status_cb: %s's status code = %d\n", username, status_code);
-    status_headline = g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE);
-
-    blist = purple_get_blist();
-
-    /* Add buddy if not found */
-    buddy = purple_find_buddy(session->account, username);
-    if (!buddy)
-    {
-        purple_debug_info("msim", 
-				"msim_status: making new buddy for %s\n", username);
-        buddy = purple_buddy_new(session->account, username, NULL);
-
-        purple_blist_add_buddy(buddy, NULL, NULL, NULL);
-
-		/* All buddies on list should have 'uid' integer associated with them. */
-		purple_blist_node_set_int(&buddy->node, "UserID", msim_msg_get_integer(msg, "f"));
-		
-		msim_store_buddy_info(session, msg);
-    } else {
-        purple_debug_info("msim", "msim_status: found buddy %s\n", username);
-    }
-
-	purple_blist_node_set_string(&buddy->node, "Headline", status_headline);
-  
-    /* Set user status */	
-    switch (status_code)
-	{
-		case MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN: 
-			purple_status_code = PURPLE_STATUS_OFFLINE;	
-			break;
-
-		case MSIM_STATUS_CODE_ONLINE: 
-			purple_status_code = PURPLE_STATUS_AVAILABLE;
-			break;
-
-		case MSIM_STATUS_CODE_AWAY:
-			purple_status_code = PURPLE_STATUS_AWAY;
-			break;
-
-        case MSIM_STATUS_CODE_IDLE:
-            /* will be handled below */
-            purple_status_code = -1;
-            break;
-
-		default:
-				purple_debug_info("msim", "msim_status_cb for %s, unknown status code %d, treating as available\n",
-						username, status_code);
-				purple_status_code = PURPLE_STATUS_AVAILABLE;
-	}
-
-#ifdef MSIM_FAKE_SELF_ONLINE
-	if (!strcmp(username, session->username) && purple_status_code == PURPLE_STATUS_OFFLINE)
-	{
-		/* Hack to ignore offline status notices on self. */
-	} else if (status_code != MSIM_STATUS_CODE_IDLE) {
-#endif
-		purple_prpl_got_user_status(session->account, username, purple_primitive_get_id_from_type(purple_status_code), NULL);
-#ifdef MSIM_FAKE_SELF_ONLINE
-	}
-#endif
-
-    if (status_code == MSIM_STATUS_CODE_IDLE)
-    {
-        purple_debug_info("msim", "msim_status: got idle: %s\n", username);
-        purple_prpl_got_user_idle(session->account, username, TRUE, time(NULL));
-    } else {
-        /* All other statuses indicate going back to non-idle. */
-        purple_prpl_got_user_idle(session->account, username, FALSE, time(NULL));
-    }
-
-    g_strfreev(status_array);
-	g_free(status_str);
-	g_free(username);
-    g_list_free(list);
-
-    return TRUE;
-}
-
-/** Add a buddy to user's buddy list. */
-void 
-msim_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
-{
-	MsimSession *session;
-	MsimMessage *msg;
-    /* MsimMessage	*msg_blocklist; */
-
-	session = (MsimSession *)gc->proto_data;
-	purple_debug_info("msim", "msim_add_buddy: want to add %s to %s\n", buddy->name,
-			group ? group->name : "(no group)");
-
-	msg = msim_msg_new(TRUE,
-			"addbuddy", MSIM_TYPE_BOOLEAN, TRUE,
-			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
-			/* "newprofileid" will be inserted here with uid. */
-			"reason", MSIM_TYPE_STRING, g_strdup(""),
-			NULL);
-
-	if (!msim_postprocess_outgoing(session, msg, buddy->name, "newprofileid", "reason"))
-	{
-		purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("'addbuddy' command failed."));
-		msim_msg_free(msg);
-		return;
-	}
-	msim_msg_free(msg);
-	
-	/* TODO: if addbuddy fails ('error' message is returned), delete added buddy from
-	 * buddy list since Purple adds it locally. */
-
-	/* TODO: Update blocklist. */
-#if 0
-	msg_blocklist = msim_msg_new(TRUE,
-		"persist", MSIM_TYPE_INTEGER, 1,
-		"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
-		"cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_PUT,
-		"dsn", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_DSN,
-		"lid", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_LID,
-		/* TODO: Use msim_new_reply_callback to get rid. */
-		"rid", MSIM_TYPE_INTEGER, session->next_rid++,
-		"body", MSIM_TYPE_STRING,
-			g_strdup_printf("ContactID=<uid>\034"
-			"GroupName=%s\034"
-			"Position=1000\034"
-			"Visibility=1\034"
-			"NickName=\034"
-			"NameSelect=0",
-			"Friends" /*group->name*/ ));
-
-	if (!msim_postprocess_outgoing(session, msg, buddy->name, "body", NULL))
-	{
-		purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("persist command failed"));
-		msim_msg_free(msg_blocklist);
-		return;
-	}
-	msim_msg_free(msg_blocklist);
-#endif
-}
-
-/** Perform actual postprocessing on a message, adding userid as specified.
- *
- * @param msg The message to postprocess.
- * @param uid_before Name of field where to insert new field before, or NULL for end.
- * @param uid_field_name Name of field to add uid to.
- * @param uid The userid to insert.
- *
- * If the field named by uid_field_name already exists, then its string contents will
- * be used for the field, except "<uid>" will be replaced by the userid.
- *
- * If the field named by uid_field_name does not exist, it will be added before the
- * field named by uid_before, as an integer, with the userid.
- *
- * Does not handle sending, or scheduling userid lookup. For that, see msim_postprocess_outgoing().
- */ 
-MsimMessage *
-msim_do_postprocessing(MsimMessage *msg, const gchar *uid_before, 
-		const gchar *uid_field_name, guint uid)
-{	
-	purple_debug_info("msim", "msim_do_postprocessing called with ufn=%s, ub=%s, uid=%d\n",
-			uid_field_name, uid_before, uid);
-	msim_msg_dump("msim_do_postprocessing msg: %s\n", msg);
-
-	/* First, check - if the field already exists, treat it as a format string. */
-	if (msim_msg_get(msg, uid_field_name))
-	{
-		MsimMessageElement *elem;
-		gchar *fmt_string;
-		gchar *uid_str;
-
-		/* Warning: this probably violates the encapsulation of MsimMessage */
-
-		elem = msim_msg_get(msg, uid_field_name);
-		g_return_val_if_fail(elem->type == MSIM_TYPE_STRING, NULL);
-
-		/* Get the raw string, not with msim_msg_get_string() since that copies it. 
-		 * Want the original string so can free it. */
-		fmt_string = (gchar *)(elem->data);
-
-		uid_str = g_strdup_printf("%d", uid);
-		elem->data = str_replace(fmt_string, "<uid>", uid_str);
-		g_free(uid_str);
-		g_free(fmt_string);
-
-		purple_debug_info("msim", "msim_postprocess_outgoing_cb: formatted new string, %s\n",
-				elem->data);
-
-	} else {
-		/* Otherwise, insert new field into outgoing message. */
-		msg = msim_msg_insert_before(msg, uid_before, uid_field_name, MSIM_TYPE_INTEGER, GUINT_TO_POINTER(uid));
-	}
-
-	return msg;
-}	
-
-/** Callback for msim_postprocess_outgoing() to add a userid to a message, and send it (once receiving userid).
- *
- * @param session
- * @param userinfo The user information reply message, containing the user ID
- * @param data The message to postprocess and send.
- *
- * The data message should contain these fields:
- *
- *  _uid_field_name: string, name of field to add with userid from userinfo message
- *  _uid_before: string, name of field before field to insert, or NULL for end
- *
- *
-*/
-void 
-msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, gpointer data)
-{
-	gchar *body_str;
-	GHashTable *body;
-	gchar *uid, *uid_field_name, *uid_before;
-	MsimMessage *msg;
-
-	msg = (MsimMessage *)data;
-
-	msim_msg_dump("msim_postprocess_outgoing_cb() got msg=%s\n", msg);
-
-	/* Obtain userid from userinfo message. */
-	body_str = msim_msg_get_string(userinfo, "body");
-	g_return_if_fail(body_str != NULL);
-	body = msim_parse_body(body_str);
-	g_free(body_str);
-
-	uid = g_strdup(g_hash_table_lookup(body, "UserID"));
-	g_hash_table_destroy(body);
-
-	uid_field_name = msim_msg_get_string(msg, "_uid_field_name");
-	uid_before = msim_msg_get_string(msg, "_uid_before");
-
-	msg = msim_do_postprocessing(msg, uid_before, uid_field_name, atol(uid));
-
-	/* Send */
-	if (!msim_msg_send(session, msg))
-	{
-		purple_debug_info("msim", "msim_postprocess_outgoing_cb: sending failed for message: %s\n", msg);
-	}
-
-
-	/* Free field names AFTER sending message, because MsimMessage does NOT copy
-	 * field names - instead, treats them as static strings (which they usually are).
-	 */
-	g_free(uid_field_name);
-	g_free(uid_before);
-
-	//msim_msg_free(msg);
-}
-
-/** Postprocess and send a message.
- *
- * @param session
- * @param msg Message to postprocess. Will NOT be freed.
- * @param username Username to resolve. Assumed to be a static string (will not be freed or copied).
- * @param uid_field_name Name of new field to add, containing uid of username. Static string.
- * @param uid_before Name of existing field to insert username field before. Static string.
- *
- * @return Postprocessed message.
- */
-gboolean 
-msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, 
-		const gchar *username, const gchar *uid_field_name, 
-		const gchar *uid_before)
-{
-    PurpleBuddy *buddy;
-	guint uid;
-	gboolean rc;
-
-	/* Store information for msim_postprocess_outgoing_cb(). */
-	purple_debug_info("msim", "msim_postprocess_outgoing(u=%s,ufn=%s,ub=%s)\n",
-			username, uid_field_name, uid_before);
-	msim_msg_dump("msim_postprocess_outgoing: msg before=%s\n", msg);
-	msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username));
-	msg = msim_msg_append(msg, "_uid_field_name", MSIM_TYPE_STRING, g_strdup(uid_field_name));
-	msg = msim_msg_append(msg, "_uid_before", MSIM_TYPE_STRING, g_strdup(uid_before));
-
-	/* First, try the most obvious. If numeric userid is given, use that directly. */
-    if (msim_is_userid(username))
-    {
-		uid = atol(username);
-    } else {
-		/* Next, see if on buddy list and know uid. */
-		buddy = purple_find_buddy(session->account, username);
-		if (buddy)
-		{
-			uid = purple_blist_node_get_int(&buddy->node, "UserID");
-		} else {
-			uid = 0;
-		}
-
-		if (!buddy || !uid)
-		{
-			/* Don't have uid offhand - need to ask for it, and wait until hear back before sending. */
-			purple_debug_info("msim", ">>> msim_postprocess_outgoing: couldn't find username %s in blist\n",
-					username);
-			msim_msg_dump("msim_postprocess_outgoing - scheduling lookup, msg=%s\n", msg);
-			/* TODO: where is cloned message freed? Should be in _cb. */
-			msim_lookup_user(session, username, msim_postprocess_outgoing_cb, msim_msg_clone(msg));
-			return TRUE;		/* not sure of status yet - haven't sent! */
-		}
-	}
-	
-	/* Already have uid, postprocess and send msg immediately. */
-	purple_debug_info("msim", "msim_postprocess_outgoing: found username %s has uid %d\n",
-			username, uid);
-
-	msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid);
-
-	msim_msg_dump("msim_postprocess_outgoing: msg after (uid immediate)=%s\n", msg);
-	
-	rc = msim_msg_send(session, msg);
-
-	//msim_msg_free(msg);
-
-	return rc;
-}
-
-/** Remove a buddy from the user's buddy list. */
-void 
-msim_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
-{
-	MsimSession *session;
-	MsimMessage *delbuddy_msg;
-	MsimMessage *persist_msg;
-	MsimMessage *blocklist_msg;
-
-	session = (MsimSession *)gc->proto_data;
-
-	delbuddy_msg = msim_msg_new(TRUE,
-				"delbuddy", MSIM_TYPE_BOOLEAN, TRUE,
-				"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
-				/* 'delprofileid' with uid will be inserted here. */
-				NULL);
-	/* TODO: free msg */
-	if (!msim_postprocess_outgoing(session, delbuddy_msg, buddy->name, "delprofileid", NULL))
-	{
-		purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("'delbuddy' command failed"));
-		return;
-	}
-
-	persist_msg = msim_msg_new(TRUE, 
-			"persist", MSIM_TYPE_INTEGER, 1,
-			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
-			"cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_DELETE,
-			"dsn", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_DSN,
-			"lid", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_LID,
-			"uid", MSIM_TYPE_INTEGER, session->userid,
-			"rid", MSIM_TYPE_INTEGER, session->next_rid++,
-			/* <uid> will be replaced by postprocessing */
-			"body", MSIM_TYPE_STRING, g_strdup("ContactID=<uid>"),
-			NULL);
-
-	/* TODO: free msg */
-	if (!msim_postprocess_outgoing(session, persist_msg, buddy->name, "body", NULL))
-	{
-		purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("persist command failed"));	
-		return;
-	}
-
-	blocklist_msg = msim_msg_new(TRUE,
-			"blocklist", MSIM_TYPE_BOOLEAN, TRUE,
-			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
-			/* TODO: MsimMessage lists */
-			"idlist", MSIM_TYPE_STRING, g_strdup("a-|<uid>|b-|<uid>"),
-			NULL);
-
-	if (!msim_postprocess_outgoing(session, blocklist_msg, buddy->name, "idlist", NULL))
-	{
-		purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("blocklist command failed"));
-		return;
-	}
-}
-
-/** Return whether the buddy can be messaged while offline.
- *
- * The protocol supports offline messages in just the same way as online
- * messages.
- */
-gboolean 
-msim_offline_message(const PurpleBuddy *buddy)
-{
-	return TRUE;
-}
-
-/**
- * Callback when input available.
- *
- * @param gc_uncasted A PurpleConnection pointer.
- * @param source File descriptor.
- * @param cond PURPLE_INPUT_READ
- *
- * Reads the input, and calls msim_preprocess_incoming() to handle it.
- */
-void 
-msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond)
-{
-    PurpleConnection *gc;
-    PurpleAccount *account;
-    MsimSession *session;
-    gchar *end;
-    int n;
-
-    g_return_if_fail(gc_uncasted != NULL);
-    g_return_if_fail(source >= 0);  /* Note: 0 is a valid fd */
-
-    gc = (PurpleConnection *)(gc_uncasted);
-    account = purple_connection_get_account(gc);
-    session = gc->proto_data;
-
-    g_return_if_fail(cond == PURPLE_INPUT_READ);
-	g_return_if_fail(MSIM_SESSION_VALID(session));
-
-    /* Mark down that we got data, so don't timeout. */
-    session->last_comm = time(NULL);
-
-    /* Only can handle so much data at once... 
-     * If this happens, try recompiling with a higher MSIM_READ_BUF_SIZE.
-     * Should be large enough to hold the largest protocol message.
-     */
-    if (session->rxoff == MSIM_READ_BUF_SIZE)
-    {
-        purple_debug_error("msim", "msim_input_cb: %d-byte read buffer full!\n",
-                MSIM_READ_BUF_SIZE);
-        purple_connection_error(gc, _("Read buffer full"));
-        return;
-    }
-
-    purple_debug_info("msim", "buffer at %d (max %d), reading up to %d\n",
-            session->rxoff, MSIM_READ_BUF_SIZE, 
-			MSIM_READ_BUF_SIZE - session->rxoff);
-
-    /* Read into buffer. On Win32, need recv() not read(). session->fd also holds
-     * the file descriptor, but it sometimes differs from the 'source' parameter.
-     */
-    n = recv(session->fd, session->rxbuf + session->rxoff, MSIM_READ_BUF_SIZE - session->rxoff, 0);
-
-    if (n < 0 && errno == EAGAIN)
-    {
-        return;
-    }
-    else if (n < 0)
-    {
-        purple_debug_error("msim", "msim_input_cb: read error, ret=%d, "
-			"error=%s, source=%d, fd=%d (%X))\n", 
-			n, strerror(errno), source, session->fd, session->fd);
-        purple_connection_error(gc, _("Read error"));
-        return;
-    } 
-    else if (n == 0)
-    {
-        purple_debug_info("msim", "msim_input_cb: server disconnected\n");
-        purple_connection_error(gc, _("Server has disconnected"));
-        return;
-    }
-
-    /* Null terminate */
-    session->rxbuf[session->rxoff + n] = 0;
-
-#ifdef MSIM_CHECK_EMBEDDED_NULLS
-    /* Check for embedded NULs. I don't handle them, and they shouldn't occur. */
-    if (strlen(session->rxbuf + session->rxoff) != n)
-    {
-        /* Occurs after login, but it is not a null byte. */
-        purple_debug_info("msim", "msim_input_cb: strlen=%d, but read %d bytes"
-                "--null byte encountered?\n", 
-				strlen(session->rxbuf + session->rxoff), n);
-        //purple_connection_error(gc, "Invalid message - null byte on input");
-        return;
-    }
-#endif
-
-    session->rxoff += n;
-    purple_debug_info("msim", "msim_input_cb: read=%d\n", n);
-
-#ifdef MSIM_DEBUG_RXBUF
-    purple_debug_info("msim", "buf=<%s>\n", session->rxbuf);
-#endif
-
-    /* Look for \\final\\ end markers. If found, process message. */
-    while((end = strstr(session->rxbuf, MSIM_FINAL_STRING)))
-    {
-        MsimMessage *msg;
-
-#ifdef MSIM_DEBUG_RXBUF
-        purple_debug_info("msim", "in loop: buf=<%s>\n", session->rxbuf);
-#endif
-        *end = 0;
-        msg = msim_parse(g_strdup(session->rxbuf));
-        if (!msg)
-        {
-            purple_debug_info("msim", "msim_input_cb: couldn't parse <%s>\n", 
-					session->rxbuf);
-            purple_connection_error(gc, _("Unparseable message"));
-        }
-        else
-        {
-            /* Process message and then free it (processing function should
-			 * clone message if it wants to keep it afterwards.) */
-            if (!msim_preprocess_incoming(session, msg))
-			{
-				msim_msg_dump("msim_input_cb: preprocessing message failed on msg: %s\n", msg);
-			}
-			msim_msg_free(msg);
-        }
-
-        /* Move remaining part of buffer to beginning. */
-        session->rxoff -= strlen(session->rxbuf) + strlen(MSIM_FINAL_STRING);
-        memmove(session->rxbuf, end + strlen(MSIM_FINAL_STRING), 
-                MSIM_READ_BUF_SIZE - (end + strlen(MSIM_FINAL_STRING) - session->rxbuf));
-
-        /* Clear end of buffer */
-        //memset(end, 0, MSIM_READ_BUF_SIZE - (end - session->rxbuf));
-    }
-}
-
-/* Setup a callback, to be called when a reply is received with the returned rid.
- *
- * @param cb The callback, an MSIM_USER_LOOKUP_CB.
- * @param data Arbitrary user data to be passed to callback (probably an MsimMessage *).
- *
- * @return The request/reply ID, used to link replies with requests. Put the rid in your request.
- *
- * TODO: Make more generic and more specific:
- * 1) MSIM_USER_LOOKUP_CB - make it for PERSIST_REPLY, not just user lookup
- * 2) data - make it an MsimMessage?
- */
-guint 
-msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb, 
-		gpointer data)
-{
-	guint rid;
-
-	rid = session->next_rid++;
-
-    g_hash_table_insert(session->user_lookup_cb, GUINT_TO_POINTER(rid), cb);
-    g_hash_table_insert(session->user_lookup_cb_data, GUINT_TO_POINTER(rid), data);
-
-	return rid;
-}
-
-/**
- * Callback when connected. Sets up input handlers.
- *
- * @param data A PurpleConnection pointer.
- * @param source File descriptor.
- * @param error_message
- */
-void 
-msim_connect_cb(gpointer data, gint source, const gchar *error_message)
-{
-    PurpleConnection *gc;
-    MsimSession *session;
-
-    g_return_if_fail(data != NULL);
-
-    gc = (PurpleConnection *)data;
-    session = (MsimSession *)gc->proto_data;
-
-    if (source < 0)
-    {
-        purple_connection_error(gc, _("Couldn't connect to host"));
-        purple_connection_error(gc, g_strdup_printf(
-					_("Couldn't connect to host: %s (%d)"), 
-                    error_message ? error_message : "no message given", 
-					source));
-        return;
-    }
-
-    session->fd = source; 
-
-    gc->inpa = purple_input_add(source, PURPLE_INPUT_READ, msim_input_cb, gc);
-
-
-}
-
-/* Session methods */
-
-/**
- * Create a new MSIM session.
- *
- * @param acct The account to create the session from.
- *
- * @return Pointer to a new session. Free with msim_session_destroy.
- */
-MsimSession *
-msim_session_new(PurpleAccount *acct)
-{
-    MsimSession *session;
-
-    g_return_val_if_fail(acct != NULL, NULL);
-
-    session = g_new0(MsimSession, 1);
-
-    session->magic = MSIM_SESSION_STRUCT_MAGIC;
-    session->account = acct;
-    session->gc = purple_account_get_connection(acct);
-	session->sesskey = 0;
-	session->userid = 0;
-	session->username = NULL;
-    session->fd = -1;
-
-	/* TODO: Remove. */
-    session->user_lookup_cb = g_hash_table_new_full(g_direct_hash, 
-			g_direct_equal, NULL, NULL);  /* do NOT free function pointers! (values) */
-    session->user_lookup_cb_data = g_hash_table_new_full(g_direct_hash, 
-			g_direct_equal, NULL, NULL);/* TODO: we don't know what the values are,
-											 they could be integers inside gpointers
-											 or strings, so I don't freed them.
-											 Figure this out, once free cache. */
-    session->rxoff = 0;
-    session->rxbuf = g_new0(gchar, MSIM_READ_BUF_SIZE);
-	session->next_rid = 1;
-    session->last_comm = time(NULL);
-    
-    return session;
-}
-
-/**
- * Free a session.
- *
- * @param session The session to destroy.
- */
-void 
-msim_session_destroy(MsimSession *session)
-{
-    g_return_if_fail(MSIM_SESSION_VALID(session));
-	
-    session->magic = -1;
-
-    g_free(session->rxbuf);
-	g_free(session->username);
-
-	/* TODO: Remove. */
-	g_hash_table_destroy(session->user_lookup_cb);
-	g_hash_table_destroy(session->user_lookup_cb_data);
-	
-    g_free(session);
-}
-                 
-/** 
- * Close the connection.
- * 
- * @param gc The connection.
- */
-void 
-msim_close(PurpleConnection *gc)
-{
-	MsimSession *session;
-
-	if (gc == NULL)
-		return;
-
-	session = (MsimSession *)gc->proto_data;
-	if (session == NULL)
-		return;
-
-	gc->proto_data = NULL;
-
-	if (!MSIM_SESSION_VALID(session))
-		return;
-
-    if (session->gc->inpa)
-		purple_input_remove(session->gc->inpa);
-
-    msim_session_destroy(session);
-}
-
-
-/**
- * Check if a string is a userid (all numeric).
- *
- * @param user The user id, email, or name.
- *
- * @return TRUE if is userid, FALSE if not.
- */
-gboolean 
-msim_is_userid(const gchar *user)
-{
-    g_return_val_if_fail(user != NULL, FALSE);
-
-    return strspn(user, "0123456789") == strlen(user);
-}
-
-/**
- * Check if a string is an email address (contains an @).
- *
- * @param user The user id, email, or name.
- *
- * @return TRUE if is an email, FALSE if not.
- *
- * This function is not intended to be used as a generic
- * means of validating email addresses, but to distinguish
- * between a user represented by an email address from
- * other forms of identification.
- */ 
-gboolean 
-msim_is_email(const gchar *user)
-{
-    g_return_val_if_fail(user != NULL, FALSE);
-
-    return strchr(user, '@') != NULL;
-}
-
-
-/**
- * Asynchronously lookup user information, calling callback when receive result.
- *
- * @param session
- * @param user The user id, email address, or username. Not freed.
- * @param cb Callback, called with user information when available.
- * @param data An arbitray data pointer passed to the callback.
- */
-/* TODO: change to not use callbacks */
-void 
-msim_lookup_user(MsimSession *session, const gchar *user, 
-		MSIM_USER_LOOKUP_CB cb, gpointer data)
-{
-    gchar *field_name;
-    guint rid, cmd, dsn, lid;
-
-    g_return_if_fail(MSIM_SESSION_VALID(session));
-    g_return_if_fail(user != NULL);
-    g_return_if_fail(cb != NULL);
-
-    purple_debug_info("msim", "msim_lookup_userid: "
-			"asynchronously looking up <%s>\n", user);
-
-	msim_msg_dump("msim_lookup_user: data=%s\n", (MsimMessage *)data);
-
-    /* Setup callback. Response will be associated with request using 'rid'. */
-    rid = msim_new_reply_callback(session, cb, data);
-
-    /* Send request */
-
-    cmd = MSIM_CMD_GET;
-
-    if (msim_is_userid(user))
-    {
-        field_name = "UserID";
-        dsn = MG_MYSPACE_INFO_BY_ID_DSN; 
-        lid = MG_MYSPACE_INFO_BY_ID_LID; 
-    } else if (msim_is_email(user)) {
-        field_name = "Email";
-        dsn = MG_MYSPACE_INFO_BY_STRING_DSN;
-        lid = MG_MYSPACE_INFO_BY_STRING_LID;
-    } else {
-        field_name = "UserName";
-        dsn = MG_MYSPACE_INFO_BY_STRING_DSN;
-        lid = MG_MYSPACE_INFO_BY_STRING_LID;
-    }
-
-
-	g_return_if_fail(msim_send(session,
-			"persist", MSIM_TYPE_INTEGER, 1,
-			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
-			"cmd", MSIM_TYPE_INTEGER, 1,
-			"dsn", MSIM_TYPE_INTEGER, dsn,
-			"uid", MSIM_TYPE_INTEGER, session->userid,
-			"lid", MSIM_TYPE_INTEGER, lid,
-			"rid", MSIM_TYPE_INTEGER, rid,
-			/* TODO: dictionary field type */
-			"body", MSIM_TYPE_STRING, 
-				g_strdup_printf("%s=%s", field_name, user),
-			NULL));
-} 
-
-
-/**
- * Obtain the status text for a buddy.
- *
- * @param buddy The buddy to obtain status text for.
- *
- * @return Status text, or NULL if error. Caller g_free()'s.
- *
- */
-char *
-msim_status_text(PurpleBuddy *buddy)
-{
-    MsimSession *session;
-	const gchar *display_name, *headline;
-
-    g_return_val_if_fail(buddy != NULL, NULL);
-
-    session = (MsimSession *)buddy->account->gc->proto_data;
-    g_return_val_if_fail(MSIM_SESSION_VALID(session), NULL);
-
-	display_name = headline = NULL;
-
-	/* Retrieve display name and/or headline, depending on user preference. */
-    if (purple_account_get_bool(session->account, "show_display_name", TRUE))
-	{
-		display_name = purple_blist_node_get_string(&buddy->node, "DisplayName");
-	} 
-
-    if (purple_account_get_bool(session->account, "show_headline", FALSE))
-	{
-		headline = purple_blist_node_get_string(&buddy->node, "Headline");
-	}
-
-	/* Return appropriate combination of display name and/or headline, or neither. */
-
-	if (display_name && headline)
-		return g_strconcat(display_name, " ", headline, NULL);
-
-	if (display_name)
-		return g_strdup(display_name);
-
-	if (headline)
-		return g_strdup(headline);
-
-	return NULL;
-}
-
-/**
- * Obtain the tooltip text for a buddy.
- *
- * @param buddy Buddy to obtain tooltip text on.
- * @param user_info Variable modified to have the tooltip text.
- * @param full TRUE if should obtain full tooltip text.
- *
- */
-void 
-msim_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, 
-		gboolean full)
-{
-	const gchar *str, *str2;
-	gint n;
-
-    g_return_if_fail(buddy != NULL);
-    g_return_if_fail(user_info != NULL);
-
-    if (PURPLE_BUDDY_IS_ONLINE(buddy))
-    {
-        MsimSession *session;
-
-        session = (MsimSession *)buddy->account->gc->proto_data;
-
-        g_return_if_fail(MSIM_SESSION_VALID(session));
-
-        /* TODO: if (full), do something different */
-		
-		/* Useful to identify the account the tooltip refers to. 
-		 *  Other prpls show this. */
-		str = purple_blist_node_get_string(&buddy->node, "UserName"); 
-		if (str)
-			purple_notify_user_info_add_pair(user_info, _("User Name"), str);
-
-		/* a/s/l...the vitals */	
-		n = purple_blist_node_get_int(&buddy->node, "Age");
-		if (n)
-			purple_notify_user_info_add_pair(user_info, _("Age"),
-					g_strdup_printf("%d", n));
-
-		str = purple_blist_node_get_string(&buddy->node, "Gender");
-		if (str)
-			purple_notify_user_info_add_pair(user_info, _("Gender"), str);
-
-		str = purple_blist_node_get_string(&buddy->node, "Location");
-		if (str)
-			purple_notify_user_info_add_pair(user_info, _("Location"), str);
-
-		/* Other information */
- 		str = purple_blist_node_get_string(&buddy->node, "Headline");
-		if (str)
-			purple_notify_user_info_add_pair(user_info, _("Headline"), str);
-
-		str = purple_blist_node_get_string(&buddy->node, "BandName");
-		str2 = purple_blist_node_get_string(&buddy->node, "SongName");
-		if (str || str2)
-			purple_notify_user_info_add_pair(user_info, _("Song"), 
-                g_strdup_printf("%s - %s",
-					str ? str : _("Unknown Artist"),
-					str2 ? str2 : _("Unknown Song")));
-
-		n = purple_blist_node_get_int(&buddy->node, "TotalFriends");
-		if (n)
-			purple_notify_user_info_add_pair(user_info, _("Total Friends"),
-				g_strdup_printf("%d", n));
-
-    }
-}
-
-/** Callbacks called by Purple, to access this plugin. */
-PurplePluginProtocolInfo prpl_info =
-{
-	/* options */
-    OPT_PROTO_USE_POINTSIZE		/* specify font size in sane point size */
-	/* | OPT_PROTO_MAIL_CHECK - TODO: myspace will notify of mail */
-	/* | OPT_PROTO_IM_IMAGE - TODO: direct images. */	
-	,
-    NULL,              /* user_splits */
-    NULL,              /* protocol_options */
-    NO_BUDDY_ICONS,    /* icon_spec - TODO: eventually should add this */
-    msim_list_icon,    /* list_icon */
-    NULL,              /* list_emblems */
-    msim_status_text,  /* status_text */
-    msim_tooltip_text, /* tooltip_text */
-    msim_status_types, /* status_types */
-    NULL,              /* blist_node_menu */
-    NULL,              /* chat_info */
-    NULL,              /* chat_info_defaults */
-    msim_login,        /* login */
-    msim_close,        /* close */
-    msim_send_im,      /* send_im */
-    NULL,              /* set_info */
-    msim_send_typing,  /* send_typing */
-	msim_get_info, 	   /* get_info */
-    msim_set_status,   /* set_status */
-    msim_set_idle,     /* set_idle */
-    NULL,              /* change_passwd */
-    msim_add_buddy,    /* add_buddy */
-    NULL,              /* add_buddies */
-    msim_remove_buddy, /* remove_buddy */
-    NULL,              /* remove_buddies */
-    NULL,              /* add_permit */
-    NULL,              /* add_deny */
-    NULL,              /* rem_permit */
-    NULL,              /* rem_deny */
-    NULL,              /* set_permit_deny */
-    NULL,              /* join_chat */
-    NULL,              /* reject chat invite */
-    NULL,              /* get_chat_name */
-    NULL,              /* chat_invite */
-    NULL,              /* chat_leave */
-    NULL,              /* chat_whisper */
-    NULL,              /* chat_send */
-    NULL,              /* keepalive */
-    NULL,              /* register_user */
-    NULL,              /* get_cb_info */
-    NULL,              /* get_cb_away */
-    NULL,              /* alias_buddy */
-    NULL,              /* group_buddy */
-    NULL,              /* rename_group */
-    NULL,              /* buddy_free */
-    NULL,              /* convo_closed */
-    NULL,              /* normalize */
-    NULL,              /* set_buddy_icon */
-    NULL,              /* remove_group */
-    NULL,              /* get_cb_real_name */
-    NULL,              /* set_chat_topic */
-    NULL,              /* find_blist_chat */
-    NULL,              /* roomlist_get_list */
-    NULL,              /* roomlist_cancel */
-    NULL,              /* roomlist_expand_category */
-    NULL,              /* can_receive_file */
-    NULL,              /* send_file */
-    NULL,              /* new_xfer */
-    msim_offline_message, /* offline_message */
-    NULL,              /* whiteboard_prpl_ops */
-    msim_send_really_raw,     /* send_raw */
-    NULL,              /* roomlist_room_serialize */
-	NULL,			   /* _purple_reserved1 */
-	NULL,			   /* _purple_reserved2 */
-	NULL,			   /* _purple_reserved3 */
-	NULL 			   /* _purple_reserved4 */
-};
-
-
-
-/** Based on MSN's plugin info comments. */
-PurplePluginInfo info =
-{
-    PURPLE_PLUGIN_MAGIC,                                
-    PURPLE_MAJOR_VERSION,
-    PURPLE_MINOR_VERSION,
-    PURPLE_PLUGIN_PROTOCOL,                            /**< type           */
-    NULL,                                              /**< ui_requirement */
-    0,                                                 /**< flags          */
-    NULL,                                              /**< dependencies   */
-    PURPLE_PRIORITY_DEFAULT,                           /**< priority       */
-
-    "prpl-myspace",                                   /**< id             */
-    "MySpaceIM",                                      /**< name           */
-    "0.10",                                           /**< version        */
-                                                      /**  summary        */
-    "MySpaceIM Protocol Plugin",
-                                                      /**  description    */
-    "MySpaceIM Protocol Plugin",
-    "Jeff Connelly <jeff2@soc.pidgin.im>",         /**< author         */
-    "http://developer.pidgin.im/wiki/MySpaceIM/",     /**< homepage       */
-
-    msim_load,                                        /**< load           */
-    NULL,                                             /**< unload         */
-    NULL,                                             /**< destroy        */
-    NULL,                                             /**< ui_info        */
-    &prpl_info,                                       /**< extra_info     */
-    NULL,                                             /**< prefs_info     */
-
-    /* msim_actions */
-    NULL,
-
-	NULL,											  /**< reserved1      */
-	NULL,											  /**< reserved2      */
-	NULL,											  /**< reserved3      */
-	NULL 											  /**< reserved4      */
-};
-
-
-#ifdef MSIM_SELF_TEST
-/** Test functions.
- * Used to test or try out the internal workings of msimprpl. If you're reading
- * this code for the first time, these functions can be instructive in how
- * msimprpl is architected.
- */
-void 
-msim_test_all(void) 
-{
-	guint failures;
-
-
-	failures = 0;
-	failures += msim_test_xml();
-	failures += msim_test_msg();
-	failures += msim_test_escaping();
-
-	if (failures)
-	{
-		purple_debug_info("msim", "msim_test_all HAD FAILURES: %d\n", failures);
-	}
-	else
-	{
-		purple_debug_info("msim", "msim_test_all - all tests passed!\n");
-	}
-	exit(0);
-}
-
-int 
-msim_test_xml(void)
-{
-	gchar *msg_text;
-	xmlnode *root, *n;
-	guint failures;
-	char *s;
-	int len;
-
-	failures = 0;
-	
-	msg_text = "<p><f n=\"Arial\" h=\"12\">woo!</f>xxx<c v='black'>yyy</c></p>";
-
-	purple_debug_info("msim", "msim_test_xml: msg_text=%s\n", msg_text);
-
-	root = xmlnode_from_str(msg_text, -1);
-	if (!root)
-	{
-		purple_debug_info("msim", "there is no root\n");
-		exit(0);
-	}
-
-	purple_debug_info("msim", "root name=%s, child name=%s\n", root->name,
-			root->child->name);
-
-	purple_debug_info("msim", "last child name=%s\n", root->lastchild->name);
-	purple_debug_info("msim", "Root xml=%s\n", 
-			xmlnode_to_str(root, &len));
-	purple_debug_info("msim", "Child xml=%s\n", 
-			xmlnode_to_str(root->child, &len));
-	purple_debug_info("msim", "Lastchild xml=%s\n", 
-			xmlnode_to_str(root->lastchild, &len));
-	purple_debug_info("msim", "Next xml=%s\n", 
-			xmlnode_to_str(root->next, &len));
-	purple_debug_info("msim", "Next data=%s\n", 
-			xmlnode_get_data(root->next));
-	purple_debug_info("msim", "Child->next xml=%s\n", 
-			xmlnode_to_str(root->child->next, &len));
-
-	for (n = root->child; n; n = n->next)
-	{
-		if (n->name) 
-		{
-			purple_debug_info("msim", " ** n=%s\n",n->name);
-		} else {
-			purple_debug_info("msim", " ** n data=%s\n", n->data);
-		}
-	}
-
-	purple_debug_info("msim", "root data=%s, child data=%s, child 'h'=%s\n", 
-			xmlnode_get_data(root),
-			xmlnode_get_data(root->child),
-			xmlnode_get_attrib(root->child, "h"));
-
-
-	for (n = root->child;
-			n != NULL;
-			n = n->next)
-	{
-		purple_debug_info("msim", "next name=%s\n", n->name);
-	}
-
-	s = xmlnode_to_str(root, &len);
-	s[len] = 0;
-
-	purple_debug_info("msim", "str: %s\n", s);
-	g_free(s);
-
-	xmlnode_free(root);
-
-	exit(0);
-
-	return failures;
-}
-
-/** Test MsimMessage for basic functionality. */
-int 
-msim_test_msg(void)
-{
-	MsimMessage *msg, *msg_cloned;
-	gchar *packed, *packed_expected, *packed_cloned;
-	guint failures;
-
-	failures = 0;
-
-	purple_debug_info("msim", "\n\nTesting MsimMessage\n");
-	msg = msim_msg_new(FALSE);		/* Create a new, empty message. */
-
-	/* Append some new elements. */
-	msg = msim_msg_append(msg, "bx", MSIM_TYPE_BINARY, g_string_new_len(g_strdup("XXX"), 3));
-	msg = msim_msg_append(msg, "k1", MSIM_TYPE_STRING, g_strdup("v1"));
-	msg = msim_msg_append(msg, "k1", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(42));
-	msg = msim_msg_append(msg, "k1", MSIM_TYPE_STRING, g_strdup("v43"));
-	msg = msim_msg_append(msg, "k1", MSIM_TYPE_STRING, g_strdup("v52/xxx\\yyy"));
-	msg = msim_msg_append(msg, "k1", MSIM_TYPE_STRING, g_strdup("v7"));
-	msim_msg_dump("msg debug str=%s\n", msg);
-	packed = msim_msg_pack(msg);
-
-	purple_debug_info("msim", "msg packed=%s\n", packed);
-
-	packed_expected = "\\bx\\WFhY\\k1\\v1\\k1\\42\\k1"
-		"\\v43\\k1\\v52/1xxx/2yyy\\k1\\v7\\final\\";
-
-	if (0 != strcmp(packed, packed_expected))
-	{
-		purple_debug_info("msim", "!!!(%d), msim_msg_pack not what expected: %s != %s\n",
-				++failures, packed, packed_expected);
-	}
-
-
-	msg_cloned = msim_msg_clone(msg);
-	packed_cloned = msim_msg_pack(msg_cloned);
-
-	purple_debug_info("msim", "msg cloned=%s\n", packed_cloned);
-	if (0 != strcmp(packed, packed_cloned))
-	{
-		purple_debug_info("msim", "!!!(%d), msim_msg_pack on cloned message not equal to original: %s != %s\n",
-				++failures, packed_cloned, packed);
-	}
-
-	g_free(packed);
-	g_free(packed_cloned);
-	msim_msg_free(msg_cloned);
-	msim_msg_free(msg);
-
-	return failures;
-}
-
-/** Test protocol-level escaping/unescaping. */
-int 
-msim_test_escaping(void)
-{
-	guint failures;
-	gchar *raw, *escaped, *unescaped, *expected;
-
-	failures = 0;
-
-	purple_debug_info("msim", "\n\nTesting escaping\n");
-
-	raw = "hello/world\\hello/world";
-
-	escaped = msim_escape(raw);
-	purple_debug_info("msim", "msim_test_escaping: raw=%s, escaped=%s\n", raw, escaped);
-	expected = "hello/1world/2hello/1world";
-	if (0 != strcmp(escaped, expected))
-	{
-		purple_debug_info("msim", "!!!(%d), msim_escape failed: %s != %s\n",
-				++failures, escaped, expected);
-	}
-
-
-	unescaped = msim_unescape(escaped);
-	g_free(escaped);
-	purple_debug_info("msim", "msim_test_escaping: unescaped=%s\n", unescaped);
-	if (0 != strcmp(raw, unescaped))
-	{
-		purple_debug_info("msim", "!!!(%d), msim_unescape failed: %s != %s\n",
-				++failures, raw, unescaped);
-	}	
-
-	return failures;
-}
-#endif
-
-/** Initialize plugin. */
-void 
-init_plugin(PurplePlugin *plugin) 
-{
-	PurpleAccountOption *option;
-#ifdef MSIM_SELF_TEST
-	msim_test_all();
-#endif /* MSIM_SELF_TEST */
-
-	/* TODO: default to automatically try different ports. Make the user be
-	 * able to set the first port to try (like LastConnectedPort in Windows client).  */
-	option = purple_account_option_string_new(_("Connect server"), "server", MSIM_SERVER);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-
-	option = purple_account_option_int_new(_("Connect port"), "port", MSIM_PORT);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-
-	option = purple_account_option_bool_new(_("Sign on as hidden"), "hidden", FALSE);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-
-	option = purple_account_option_bool_new(_("Show display name in status text"), "show_display_name", TRUE);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-
-	option = purple_account_option_bool_new(_("Show headline in status text"), "show_headline", TRUE);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-}
-
-PURPLE_INIT_PLUGIN(myspace, init_plugin, info);
+/* MySpaceIM Protocol Plugin
+ *
+ * \author Jeff Connelly
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * Based on Purple's "C Plugin HOWTO" hello world example.
+ *
+ * Code also drawn from mockprpl:
+ *  http://snarfed.org/space/purple+mock+protocol+plugin
+ *  Copyright (C) 2004-2007, Ryan Barrett <mockprpl@ryanb.org>
+ *
+ * and some constructs also based on existing Purple plugins, which are:
+ *   Copyright (C) 2003, Robbert Haarman <purple@inglorion.net>
+ *   Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
+ *   Copyright (C) 2000-2003, Rob Flynn <rob@tgflinux.com>
+ *   Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
+ *
+ * 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
+ */
+
+#define PURPLE_PLUGIN
+
+#include "message.h"
+#include "persist.h"
+#include "myspace.h"
+
+/** 
+ * Load the plugin.
+ */
+gboolean 
+msim_load(PurplePlugin *plugin)
+{
+	/* If compiled to use RC4 from libpurple, check if it is really there. */
+	if (!purple_ciphers_find_cipher("rc4"))
+	{
+		purple_debug_error("msim", "rc4 not in libpurple, but it is required - not loading MySpaceIM plugin!\n");
+		purple_notify_error(plugin, _("Missing Cipher"), 
+				_("The RC4 cipher could not be found"),
+				_("Upgrade "
+					"to a libpurple with RC4 support (>= 2.0.1). MySpaceIM "
+					"plugin will not be loaded."));
+		return FALSE;
+	}
+	return TRUE;
+}
+
+/**
+ * Get possible user status types. Based on mockprpl.
+ *
+ * @return GList of status types.
+ */
+GList *
+msim_status_types(PurpleAccount *acct)
+{
+    GList *types;
+    PurpleStatusType *status;
+
+    purple_debug_info("myspace", "returning status types\n");
+
+    types = NULL;
+
+    status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, NULL, FALSE, TRUE, FALSE);
+    types = g_list_append(types, status);
+
+    status = purple_status_type_new_full(PURPLE_STATUS_AWAY, NULL, NULL, FALSE, TRUE, FALSE);
+    types = g_list_append(types, status);
+
+    status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, FALSE, TRUE, FALSE);
+    types = g_list_append(types, status);
+
+    status = purple_status_type_new_full(PURPLE_STATUS_INVISIBLE, NULL, NULL, FALSE, TRUE, FALSE);
+    types = g_list_append(types, status);
+
+    return types;
+}
+
+/**
+ * Return the icon name for a buddy and account.
+ *
+ * @param acct The account to find the icon for, or NULL for protocol icon.
+ * @param buddy The buddy to find the icon for, or NULL for the account icon.
+ *
+ * @return The base icon name string.
+ */
+const gchar *
+msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy)
+{
+    /* Use a MySpace icon submitted by hbons at
+     * http://developer.pidgin.im/wiki/MySpaceIM. */
+    return "myspace";
+}
+
+/* Replacement codes to be replaced with associated replacement text,
+ * used for protocol message escaping / unescaping. */
+static gchar* msim_replacement_code[] = { "/1", "/2", NULL };
+static gchar* msim_replacement_text[] = { "/", "\\", NULL };
+
+/**
+ * Unescape or escape a protocol message.
+ *
+ * @param msg The message to be unescaped or escaped. WILL BE FREED.
+ * @param escape TRUE to escape, FALSE to unescape.
+ *
+ * @return The unescaped or escaped message. Caller must g_free().
+ */
+gchar *
+msim_unescape_or_escape(gchar *msg, gboolean escape)
+{
+	gchar *tmp, *code, *text;
+	guint i;
+
+	/* Replace each code in msim_replacement_code with
+	 * corresponding entry in msim_replacement_text. */
+	for (i = 0; (code = msim_replacement_code[i])
+		   	&& (text = msim_replacement_text[i]); ++i)
+	{
+		if (escape)
+		{
+			tmp = str_replace(msg, text, code);
+		}
+		else
+		{
+			tmp = str_replace(msg, code, text);
+		}
+		g_free(msg);
+		msg = tmp;
+	}
+	
+	return msg;
+}
+
+/**
+ * Escape a protocol message.
+ *
+ * @return The escaped message. Caller must g_free().
+ */
+gchar *
+msim_escape(const gchar *msg)
+{
+	return msim_unescape_or_escape(g_strdup(msg), TRUE);
+}
+
+gchar *
+msim_unescape(const gchar *msg)
+{
+	return msim_unescape_or_escape(g_strdup(msg), FALSE);
+}
+
+/**
+ * Replace 'old' with 'new' in 'str'.
+ *
+ * @param str The original string.
+ * @param old The substring of 'str' to replace.
+ * @param new The replacement for 'old' within 'str'.
+ *
+ * @return A _new_ string, based on 'str', with 'old' replaced
+ * 		by 'new'. Must be g_free()'d by caller.
+ *
+ * This string replace method is based on
+ * http://mail.gnome.org/archives/gtk-app-devel-list/2000-July/msg00201.html
+ *
+ */
+gchar *
+str_replace(const gchar *str, const gchar *old, const gchar *new)
+{
+	gchar **items;
+	gchar *ret;
+
+	items = g_strsplit(str, old, -1);
+	ret = g_strjoinv(new, items);
+	g_free(items);
+	return ret;
+}
+
+#ifdef MSIM_DEBUG_MSG
+void 
+print_hash_item(gpointer key, gpointer value, gpointer user_data)
+{
+    purple_debug_info("msim", "%s=%s\n", (gchar *)key, (gchar *)value);
+}
+#endif
+
+/** 
+ * Send raw data (given as a NUL-terminated string) to the server.
+ *
+ * @param session 
+ * @param msg The raw data to send, in a NUL-terminated string.
+ *
+ * @return TRUE if succeeded, FALSE if not.
+ *
+ */
+gboolean 
+msim_send_raw(MsimSession *session, const gchar *msg)
+{
+	purple_debug_info("msim", "msim_send_raw: writing <%s>\n", msg);
+
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+    g_return_val_if_fail(msg != NULL, FALSE);
+
+	return msim_send_really_raw(session->gc, msg, strlen(msg)) ==
+		strlen(msg);
+}
+
+/** Send raw data to the server, possibly with embedded NULs. 
+ *
+ * Used in prpl_info struct, so that plugins can have the most possible
+ * control of what is sent over the connection. Inside this prpl, 
+ * msim_send_raw() is used, since it sends NUL-terminated strings (easier).
+ *
+ * @param gc PurpleConnection
+ * @param buf Buffer to send
+ * @param total_bytes Size of buffer to send
+ *
+ * @return Bytes successfully sent, or -1 on error.
+ */
+int 
+msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes)
+{
+	int total_bytes_sent;
+    MsimSession *session;
+
+    g_return_val_if_fail(gc != NULL, -1);
+    g_return_val_if_fail(buf != NULL, -1);
+    g_return_val_if_fail(total_bytes >= 0, -1);
+
+    session = (MsimSession *)(gc->proto_data);
+
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), -1);
+	
+	/* Loop until all data is sent, or a failure occurs. */
+	total_bytes_sent = 0;
+	do
+	{
+		int bytes_sent;
+
+		bytes_sent = send(session->fd, buf + total_bytes_sent, 
+                total_bytes - total_bytes_sent, 0);
+
+		if (bytes_sent < 0)
+		{
+			purple_debug_info("msim", "msim_send_raw(%s): send() failed: %s\n",
+					buf, g_strerror(errno));
+			return total_bytes_sent;
+		}
+		total_bytes_sent += bytes_sent;
+
+	} while(total_bytes_sent < total_bytes);
+
+	return total_bytes_sent;
+}
+
+
+/** 
+ * Start logging in to the MSIM servers.
+ * 
+ * @param acct Account information to use to login.
+ */
+void 
+msim_login(PurpleAccount *acct)
+{
+    PurpleConnection *gc;
+    const gchar *host;
+    int port;
+
+    g_return_if_fail(acct != NULL);
+    g_return_if_fail(acct->username != NULL);
+
+    purple_debug_info("myspace", "logging in %s\n", acct->username);
+
+    gc = purple_account_get_connection(acct);
+    gc->proto_data = msim_session_new(acct);
+
+    /* Passwords are limited in length. */
+	if (strlen(acct->password) > MSIM_MAX_PASSWORD_LENGTH)
+	{
+		gchar *str;
+
+		str = g_strdup_printf(
+				_("Sorry, passwords over %d characters in length (yours is "
+				"%d) are not supported by the MySpaceIM plugin."), 
+				MSIM_MAX_PASSWORD_LENGTH,
+				(int)strlen(acct->password));
+
+		/* Notify an error message also, because this is important! */
+		purple_notify_error(acct, g_strdup(_("MySpaceIM Error")), str, NULL);
+
+        purple_connection_error(gc, str);
+		
+		g_free(str);
+	}
+
+    /* 1. connect to server */
+    purple_connection_update_progress(gc, _("Connecting"),
+                                  0,   /* which connection step this is */
+                                  4);  /* total number of steps */
+
+    host = purple_account_get_string(acct, "server", MSIM_SERVER);
+    port = purple_account_get_int(acct, "port", MSIM_PORT);
+
+    /* From purple.sf.net/api:
+     * """Note that this function name can be misleading--although it is called 
+     * "proxy connect," it is used for establishing any outgoing TCP connection, 
+     * whether through a proxy or not.""" */
+
+    /* Calls msim_connect_cb when connected. */
+    if (purple_proxy_connect(gc, acct, host, port, msim_connect_cb, gc) == NULL)
+    {
+        /* TODO: try other ports if in auto mode, then save
+         * working port and try that first next time. */
+        purple_connection_error(gc, _("Couldn't create socket"));
+        return;
+    }
+}
+
+/**
+ * Process a login challenge, sending a response. 
+ *
+ * @param session 
+ * @param msg Login challenge message.
+ *
+ * @return TRUE if successful, FALSE if not
+ */
+gboolean 
+msim_login_challenge(MsimSession *session, MsimMessage *msg) 
+{
+    PurpleAccount *account;
+    const gchar *response;
+	guint response_len;
+	gchar *nc;
+	gsize nc_len;
+
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+    g_return_val_if_fail(msg != NULL, FALSE);
+
+    g_return_val_if_fail(msim_msg_get_binary(msg, "nc", &nc, &nc_len), FALSE);
+
+    account = session->account;
+
+	g_return_val_if_fail(account != NULL, FALSE);
+
+    purple_connection_update_progress(session->gc, _("Reading challenge"), 1, 4);
+
+    purple_debug_info("msim", "nc is %d bytes, decoded\n", nc_len);
+
+    if (nc_len != 0x40)
+    {
+        purple_debug_info("msim", "bad nc length: %x != 0x40\n", nc_len);
+        purple_connection_error(session->gc, _("Unexpected challenge length from server"));
+        return FALSE;
+    }
+
+    purple_connection_update_progress(session->gc, _("Logging in"), 2, 4);
+
+    response = msim_compute_login_response(nc, account->username, account->password, &response_len);
+
+    g_free(nc);
+
+	return msim_send(session, 
+			"login2", MSIM_TYPE_INTEGER, MSIM_AUTH_ALGORITHM,
+			/* This is actually user's email address. */
+			"username", MSIM_TYPE_STRING, g_strdup(account->username),
+			/* GString and gchar * response will be freed in msim_msg_free() in msim_send(). */
+			"response", MSIM_TYPE_BINARY, g_string_new_len(response, response_len),
+			"clientver", MSIM_TYPE_INTEGER, MSIM_CLIENT_VERSION,
+			"reconn", MSIM_TYPE_INTEGER, 0,
+			"status", MSIM_TYPE_INTEGER, 100,
+			"id", MSIM_TYPE_INTEGER, 1,
+			NULL);
+}
+
+/**
+ * Compute the base64'd login challenge response based on username, password, nonce, and IPs.
+ *
+ * @param nonce The base64 encoded nonce ('nc') field from the server.
+ * @param email User's email address (used as login name).
+ * @param password User's cleartext password.
+ * @param response_len Will be written with response length.
+ *
+ * @return Binary login challenge response, ready to send to the server. 
+ * Must be g_free()'d when finished. NULL if error.
+ */
+const gchar *
+msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], 
+		const gchar *email, const gchar *password, guint *response_len)
+{
+    PurpleCipherContext *key_context;
+    PurpleCipher *sha1;
+	PurpleCipherContext *rc4;
+
+    guchar hash_pw[HASH_SIZE];
+    guchar key[HASH_SIZE];
+    gchar *password_utf16le;
+    guchar *data;
+	guchar *data_out;
+	size_t data_len, data_out_len;
+	gsize conv_bytes_read, conv_bytes_written;
+	GError *conv_error;
+#ifdef MSIM_DEBUG_LOGIN_CHALLENGE
+	int i;
+#endif
+
+    g_return_val_if_fail(nonce != NULL, NULL);
+    g_return_val_if_fail(email != NULL, NULL);
+    g_return_val_if_fail(password != NULL, NULL);
+    g_return_val_if_fail(response_len != NULL, NULL);
+
+    /* Convert ASCII password to UTF16 little endian */
+    purple_debug_info("msim", "converting password to UTF-16LE\n");
+	conv_error = NULL;
+	password_utf16le = g_convert(password, -1, "UTF-16LE", "UTF-8", 
+			&conv_bytes_read, &conv_bytes_written, &conv_error);
+
+	g_return_val_if_fail(conv_bytes_read == strlen(password), NULL);
+
+	if (conv_error != NULL)
+	{
+		purple_debug_error("msim", 
+				"g_convert password UTF8->UTF16LE failed: %s",
+				conv_error->message);
+		g_error_free(conv_error);
+        return NULL;
+	}
+
+    /* Compute password hash */ 
+    purple_cipher_digest_region("sha1", (guchar *)password_utf16le, 
+			conv_bytes_written, sizeof(hash_pw), hash_pw, NULL);
+	g_free(password_utf16le);
+
+#ifdef MSIM_DEBUG_LOGIN_CHALLENGE
+    purple_debug_info("msim", "pwhash = ");
+    for (i = 0; i < sizeof(hash_pw); i++)
+        purple_debug_info("msim", "%.2x ", hash_pw[i]);
+    purple_debug_info("msim", "\n");
+#endif
+
+    /* key = sha1(sha1(pw) + nonce2) */
+    sha1 = purple_ciphers_find_cipher("sha1");
+    key_context = purple_cipher_context_new(sha1, NULL);
+    purple_cipher_context_append(key_context, hash_pw, HASH_SIZE);
+    purple_cipher_context_append(key_context, (guchar *)(nonce + NONCE_SIZE), NONCE_SIZE);
+    purple_cipher_context_digest(key_context, sizeof(key), key, NULL);
+
+#ifdef MSIM_DEBUG_LOGIN_CHALLENGE
+    purple_debug_info("msim", "key = ");
+    for (i = 0; i < sizeof(key); i++)
+    {
+        purple_debug_info("msim", "%.2x ", key[i]);
+    }
+    purple_debug_info("msim", "\n");
+#endif
+
+	rc4 = purple_cipher_context_new_by_name("rc4", NULL);
+
+    /* Note: 'key' variable is 0x14 bytes (from SHA-1 hash), 
+     * but only first 0x10 used for the RC4 key. */
+	purple_cipher_context_set_option(rc4, "key_len", (gpointer)0x10);
+	purple_cipher_context_set_key(rc4, key);
+
+    /* TODO: obtain IPs of network interfaces */
+
+    /* rc4 encrypt:
+     * nonce1+email+IP list */
+
+    data_len = NONCE_SIZE + strlen(email) + MSIM_LOGIN_IP_LIST_LEN;
+    data = g_new0(guchar, data_len);
+    memcpy(data, nonce, NONCE_SIZE);
+    memcpy(data + NONCE_SIZE, email, strlen(email));
+    memcpy(data + NONCE_SIZE + strlen(email), MSIM_LOGIN_IP_LIST, MSIM_LOGIN_IP_LIST_LEN);
+
+	data_out = g_new0(guchar, data_len);
+
+    purple_cipher_context_encrypt(rc4, (const guchar *)data, 
+			data_len, data_out, &data_out_len);
+	purple_cipher_context_destroy(rc4);
+
+	g_assert(data_out_len == data_len);
+
+#ifdef MSIM_DEBUG_LOGIN_CHALLENGE
+    purple_debug_info("msim", "response=<%s>\n", data_out);
+#endif
+
+	*response_len = data_out_len;
+
+    return (const gchar *)data_out;
+}
+
+/**
+ * Schedule an IM to be sent once the user ID is looked up. 
+ *
+ * @param gc Connection.
+ * @param who A user id, email, or username to send the message to.
+ * @param message Instant message text to send.
+ * @param flags Flags.
+ *
+ * @return 1 if successful or postponed, -1 if failed
+ *
+ * Allows sending to a user by username, email address, or userid. If
+ * a username or email address is given, the userid must be looked up.
+ * This function does that by calling msim_postprocess_outgoing().
+ */
+int 
+msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, 
+		PurpleMessageFlags flags)
+{
+    MsimSession *session;
+    
+    g_return_val_if_fail(gc != NULL, -1);
+    g_return_val_if_fail(who != NULL, -1);
+    g_return_val_if_fail(message != NULL, -1);
+
+	/* 'flags' has many options, not used here. */
+
+	session = (MsimSession *)gc->proto_data;
+
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), -1);
+
+	if (msim_send_bm(session, who, message, MSIM_BM_INSTANT))
+	{
+		/* Return 1 to have Purple show this IM as being sent, 0 to not. I always
+		 * return 1 even if the message could not be sent, since I don't know if
+		 * it has failed yet--because the IM is only sent after the userid is
+		 * retrieved from the server (which happens after this function returns).
+		 */
+        /* TODO: maybe if message is delayed, don't echo to conv window,
+         * but do echo it to conv window manually once it is actually
+         * sent? Would be complicated. */
+		return 1;
+	} else {
+		return -1;
+	}
+    /*
+     * In MySpace, you login with your email address, but don't talk to other
+     * users using their email address. So there is currently an asymmetry in the 
+     * IM windows when using this plugin:
+     *
+     * you@example.com: hello
+     * some_other_user: what's going on?
+     * you@example.com: just coding a prpl
+     *
+     * TODO: Make the sent IM's appear as from the user's username, instead of
+     * their email address. Purple uses the login (in MSIM, the email)--change this.
+     */
+}
+
+/** Send a buddy message of a given type.
+ *
+ * @param session
+ * @param who Username to send message to.
+ * @param text Message text to send. Not freed; will be copied.
+ * @param type A MSIM_BM_* constant.
+ *
+ * @return TRUE if success, FALSE if fail.
+ *
+ * Buddy messages ('bm') include instant messages, action messages, status messages, etc.
+ *
+ */
+gboolean 
+msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, 
+		int type)
+{
+	gboolean rc;
+	MsimMessage *msg;
+    const gchar *from_username;
+
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+    g_return_val_if_fail(who != NULL, FALSE);
+    g_return_val_if_fail(text != NULL, FALSE);
+   
+	from_username = session->account->username;
+
+    purple_debug_info("msim", "sending %d message from %s to %s: %s\n",
+                  type, from_username, who, text);
+
+	msg = msim_msg_new(TRUE,
+			"bm", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(type),
+			"sesskey", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(session->sesskey),
+			/* 't' will be inserted here */
+			"cv", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(MSIM_CLIENT_VERSION),
+			"msg", MSIM_TYPE_STRING, g_strdup(text),
+			NULL);
+
+	rc = msim_postprocess_outgoing(session, msg, who, "t", "cv");
+
+	msim_msg_free(msg);
+
+	return rc;
+}
+
+
+#ifdef MSIM_FONT_SIZE_WORKS
+/** Convert a msim markup font height to points. */
+static guint 
+msim_font_height_to_point(guint height)
+{
+	/* See also: libpurple/protocols/bonjour/jabber.c
+	 * _font_size_ichat_to_purple */
+	switch (height)
+	{
+	case 11: return 8;
+	case 12: return 9;
+	case 13: return 10;
+	case 14: 
+	case 15: return 11;
+	case 16: return 12;
+	case 17:
+	case 18: return 13;
+	case 19: return 14;
+	case 20: return 15;
+	case 21: return 16;	 
+	default: return 12;
+	}
+}
+#endif
+
+/** Convert the msim markup <f> (font) tag into HTML. */
+static void 
+msim_markup_f_to_html(xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *face, *height_str, *decor_str;
+	GString *gs_end, *gs_begin;
+	guint decor, height;
+
+	face = xmlnode_get_attrib(root, "f");	
+	height_str = xmlnode_get_attrib(root, "h");
+	decor_str = xmlnode_get_attrib(root, "s");
+
+	if (height_str)
+		height = atol(height_str);
+	else
+		height = 12;
+
+	if (decor_str)
+		decor = atol(decor_str);
+	else
+		decor = 0;
+
+	gs_begin = g_string_new("");
+#ifdef MSIM_FONT_SIZE_WORKS
+	/* TODO: get font size working */
+	if (!face)
+		g_string_printf(gs_begin, "<font size='%d'>",
+				msim_font_height_to_point(height)); 
+	else
+		g_string_printf(gs_begin, "<font face='%s' size='%d'>", face, 
+				msim_font_height_to_point(height)); 
+#else
+	if (face)
+	{
+		g_string_printf(gs_begin, "<font face='%s'>", face);
+	} else {
+		g_string_printf(gs_begin, "<font>");
+	}
+#endif
+
+
+	/* No support for font-size CSS? */
+	/* g_string_printf(gs_begin, "<span style='font-family: %s; font-size: %dpt'>", face, 
+			msim_font_height_to_point(height)); */
+
+	gs_end = g_string_new("</font>");
+
+	if (decor & MSIM_TEXT_BOLD)
+	{
+		g_string_append(gs_begin, "<b>");
+		g_string_prepend(gs_end, "</b>");
+	}
+
+	if (decor & MSIM_TEXT_ITALICS)
+	{
+		g_string_append(gs_begin, "<i>");
+		g_string_append(gs_end, "</i>");	
+	}
+
+	if (decor & MSIM_TEXT_UNDERLINE)
+	{
+		g_string_append(gs_begin, "<u>");
+		g_string_append(gs_end, "</u>");	
+	}
+
+
+	*begin = gs_begin->str;
+	*end = gs_end->str;
+}
+
+/** Convert a msim markup color to a color suitable for libpurple.
+  *
+  * @param msim Either a color name, or an rgb(x,y,z) code.
+  *
+  * @return A new string, either a color name or #rrggbb code. Must g_free(). 
+  */
+static char *
+msim_color_to_purple(const char *msim)
+{
+	guint red, green, blue;
+
+	if (!msim)
+		return g_strdup("black");
+
+	if (sscanf(msim, "rgb(%d,%d,%d)", &red, &green, &blue) != 3)
+	{
+		/* Color name. */
+		return g_strdup(msim);
+	}
+	/* TODO: rgba (alpha). */
+
+	return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue);
+}	
+
+/** Convert the msim markup <p> (paragraph) tag into HTML. */
+static void 
+msim_markup_p_to_html(xmlnode *root, gchar **begin, gchar **end)
+{
+    /* Just pass through unchanged. 
+	 *
+	 * Note: attributes currently aren't passed, if there are any. */
+	*begin = g_strdup("<p>");
+	*end = g_strdup("</p>");
+}
+
+/** Convert the msim markup <c> tag (text color) into HTML. TODO: Test */
+static void 
+msim_markup_c_to_html(xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *color;
+	gchar *purple_color;
+
+	color = xmlnode_get_attrib(root, "v");
+	if (!color)
+	{
+		purple_debug_info("msim", "msim_markup_c_to_html: <c> tag w/o v attr");
+		*begin = g_strdup("");
+		*end = g_strdup("");
+		/* TODO: log as unrecognized */
+		return;
+	}
+
+	purple_color = msim_color_to_purple(color);
+
+	*begin = g_strdup_printf("<font color='%s'>", purple_color); 
+
+	g_free(purple_color);
+
+	/* *begin = g_strdup_printf("<span style='color: %s'>", color); */
+	*end = g_strdup("</font>");
+}
+
+/** Convert the msim markup <b> tag (background color) into HTML. TODO: Test */
+static void 
+msim_markup_b_to_html(xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *color;
+	gchar *purple_color;
+
+	color = xmlnode_get_attrib(root, "v");
+	if (!color)
+	{
+		*begin = g_strdup("");
+		*end = g_strdup("");
+		purple_debug_info("msim", "msim_markup_b_to_html: <b> w/o v attr");
+		/* TODO: log as unrecognized. */
+		return;
+	}
+
+	purple_color = msim_color_to_purple(color);
+
+	/* TODO: find out how to set background color. */
+	*begin = g_strdup_printf("<span style='background-color: %s'>", 
+			purple_color);
+	g_free(purple_color);
+
+	*end = g_strdup("</p>");
+}
+
+/** Convert the msim markup <i> tag (emoticon image) into HTML. TODO: Test */
+static void 
+msim_markup_i_to_html(xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *name;
+
+	name = xmlnode_get_attrib(root, "n");
+	if (!name)
+	{
+		purple_debug_info("msim", "msim_markup_i_to_html: <i> w/o n");
+		*begin = g_strdup("");
+		*end = g_strdup("");
+		/* TODO: log as unrecognized */
+		return;
+	}
+
+	/* TODO: Support these emoticons:
+	 *
+	 * bigsmile growl mad scared tongue devil happy messed sidefrown upset 
+	 * frazzled heart nerd sinister wink geek laugh oops smirk worried 
+	 * googles mohawk pirate straight kiss 
+	 */
+
+	*begin = g_strdup_printf("<img id='%s'>", name);
+	*end = g_strdup("</p>");
+}
+
+
+/** Convert an xmlnode of msim markup to an HTML string.
+ * @return An HTML string. Caller frees.
+ */
+static gchar *
+msim_markup_xmlnode_to_html(xmlnode *root)
+{
+	xmlnode *node;
+	gchar *begin, *inner, *end;
+	gchar *final;
+
+	if (!root || !root->name)
+		return g_strdup("");
+
+	purple_debug_info("msim", "msim_markup_xmlnode_to_html: got root=%s\n",
+			root->name);
+
+	begin = inner = end = NULL;
+
+	if (!strcmp(root->name, "f"))
+	{
+		msim_markup_f_to_html(root, &begin, &end);
+	} else if (!strcmp(root->name, "p")) {
+		msim_markup_p_to_html(root, &begin, &end);
+	} else if (!strcmp(root->name, "c")) {
+		msim_markup_c_to_html(root, &begin, &end);
+	} else if (!strcmp(root->name, "b")) {
+		msim_markup_b_to_html(root, &begin, &end);
+	} else if (!strcmp(root->name, "i")) {
+		msim_markup_i_to_html(root, &begin, &end);
+	} else {
+		purple_debug_info("msim", "msim_markup_xmlnode_to_html: "
+				"unknown tag name=%s, ignoring", root->name);
+		begin = g_strdup("");
+		end = g_strdup("");
+	}
+
+	/* Loop over all child nodes. */
+ 	for (node = root->child; node != NULL; node = node->next)
+	{
+		switch (node->type)
+		{
+		case XMLNODE_TYPE_ATTRIB:
+			/* Attributes handled above. */
+			break;
+
+		case XMLNODE_TYPE_TAG:
+			/* A tag or tag with attributes. Recursively descend. */
+			inner = msim_markup_xmlnode_to_html(node);
+            g_return_val_if_fail(inner != NULL, NULL);
+
+			purple_debug_info("msim", " ** node name=%s\n", node->name);
+			break;
+	
+		case XMLNODE_TYPE_DATA:	
+			/* Literal text. */
+			inner = g_new0(char, node->data_sz + 1);
+			strncpy(inner, node->data, node->data_sz);
+			inner[node->data_sz + 1] = 0;
+
+			purple_debug_info("msim", " ** node data=%s\n", inner);
+			break;
+			
+		default:
+			purple_debug_info("msim",
+					"msim_markup_xmlnode_to_html: strange node\n");
+			inner = g_strdup("");
+		}
+	}
+
+	final = g_strconcat(begin, inner, end, NULL);
+	g_free(begin);
+	g_free(inner);
+	g_free(end);
+
+	purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n",
+			final);
+
+	return final;
+}
+
+/** Convert MySpaceIM markup to Purple (HTML) markup. 
+ *
+ * @return Purple markup string, must be g_free()'d. */
+gchar *
+msim_markup_to_html(const gchar *raw)
+{
+	xmlnode *root;
+	gchar *str;
+
+	root = xmlnode_from_str(raw, -1);
+	if (!root)
+	{
+		purple_debug_info("msim", "msim_markup_to_html: couldn't parse "
+				"%s as XML, returning raw\n", raw);
+		return g_strdup(raw);
+	}
+
+	str = msim_markup_xmlnode_to_html(root);
+	purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str);
+
+	xmlnode_free(root);
+
+	return g_strdup(str);
+}
+
+/**
+ * Handle an incoming instant message.
+ *
+ * @param session The session
+ * @param msg Message from the server, containing 'f' (userid from) and 'msg'. 
+ * 			  Should also contain username in _username from preprocessing.
+ *
+ * @return TRUE if successful.
+ */
+gboolean 
+msim_incoming_im(MsimSession *session, MsimMessage *msg)
+{
+    gchar *username, *msg_msim_markup, *msg_purple_markup;
+
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+    g_return_val_if_fail(msg != NULL, FALSE);
+
+    username = msim_msg_get_string(msg, "_username");
+    g_return_val_if_fail(username != NULL, FALSE);
+
+	msg_msim_markup = msim_msg_get_string(msg, "msg");
+    g_return_val_if_fail(msg_msim_markup != NULL, FALSE);
+
+	msg_purple_markup = msim_markup_to_html(msg_msim_markup);
+	g_free(msg_msim_markup);
+
+    serv_got_im(session->gc, username, msg_purple_markup, 
+			PURPLE_MESSAGE_RECV, time(NULL));
+
+	g_free(username);
+	g_free(msg_purple_markup);
+
+	return TRUE;
+}
+
+/**
+ * Process unrecognized information.
+ *
+ * @param session
+ * @param msg An MsimMessage that was unrecognized, or NULL.
+ * @param note Information on what was unrecognized, or NULL.
+ */
+void 
+msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note)
+{
+	/* TODO: Some more context, outwardly equivalent to a backtrace, 
+     * for helping figure out what this msg is for. What was going on?
+     * But not too much information so that a user
+	 * posting this dump reveals confidential information.
+	 */
+
+	/* TODO: dump unknown msgs to file, so user can send them to me
+	 * if they wish, to help add support for new messages (inspired
+	 * by Alexandr Shutko, who maintains OSCAR protocol documentation). */
+
+	purple_debug_info("msim", "Unrecognized data on account for %s\n", 
+            session->account->username);
+	if (note)
+	{
+		purple_debug_info("msim", "(Note: %s)\n", note);
+	}
+
+    if (msg)
+    {
+        msim_msg_dump("Unrecognized message dump: %s\n", msg);
+    }
+}
+
+/**
+ * Handle an incoming action message.
+ *
+ * @param session
+ * @param msg
+ *
+ * @return TRUE if successful.
+ *
+ * UNTESTED
+ */
+gboolean 
+msim_incoming_action(MsimSession *session, MsimMessage *msg)
+{
+	gchar *msg_text, *username;
+	gboolean rc;
+
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+    g_return_val_if_fail(msg != NULL, FALSE);
+
+	msg_text = msim_msg_get_string(msg, "msg");
+    g_return_val_if_fail(msg_text != NULL, FALSE);
+
+	username = msim_msg_get_string(msg, "_username");
+    g_return_val_if_fail(username != NULL, FALSE);
+
+	purple_debug_info("msim", "msim_incoming_action: action <%s> from <%d>\n", 
+            msg_text, username);
+
+	if (strcmp(msg_text, "%typing%") == 0)
+	{
+		/* TODO: find out if msim repeatedly sends typing messages, so we can 
+         * give it a timeout. Right now, there does seem to be an inordinately 
+         * amount of time between typing stopped-typing notifications. */
+		serv_got_typing(session->gc, username, 0, PURPLE_TYPING);
+		rc = TRUE;
+	} else if (strcmp(msg_text, "%stoptyping%") == 0) {
+		serv_got_typing_stopped(session->gc, username);
+		rc = TRUE;
+	} else {
+		msim_unrecognized(session, msg, 
+                "got to msim_incoming_action but unrecognized value for 'msg'");
+		rc = FALSE;
+	}
+
+	g_free(msg_text);
+	g_free(username);
+
+	return rc;
+}
+
+/** 
+ * Handle when our user starts or stops typing to another user.
+ *
+ * @param gc
+ * @param name The buddy name to which our user is typing to
+ * @param state PURPLE_TYPING, PURPLE_TYPED, PURPLE_NOT_TYPING
+ *
+ * @return 0
+ */
+unsigned int 
+msim_send_typing(PurpleConnection *gc, const gchar *name, 
+        PurpleTypingState state)
+{
+	const gchar *typing_str;
+	MsimSession *session;
+
+    g_return_val_if_fail(gc != NULL, 0);
+    g_return_val_if_fail(name != NULL, 0);
+
+	session = (MsimSession *)gc->proto_data;
+
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), 0);
+
+	switch (state)
+	{	
+		case PURPLE_TYPING: 
+			typing_str = "%typing%"; 
+			break;
+
+		case PURPLE_TYPED:
+		case PURPLE_NOT_TYPING:
+		default:
+			typing_str = "%stoptyping%";
+			break;
+	}
+
+	purple_debug_info("msim", "msim_send_typing(%s): %d (%s)\n", name, state, typing_str);
+	msim_send_bm(session, name, typing_str, MSIM_BM_ACTION);
+	return 0;
+}
+
+/** Callback for msim_get_info(), for when user info is received. */
+void 
+msim_get_info_cb(MsimSession *session, MsimMessage *user_info_msg, gpointer data)
+{
+	GHashTable *body;
+	gchar *body_str;
+	MsimMessage *msg;
+	gchar *user;
+	PurpleNotifyUserInfo *user_info;
+	PurpleBuddy *buddy;
+	const gchar *str, *str2;
+
+    g_return_if_fail(MSIM_SESSION_VALID(session));
+
+	/* Get user{name,id} from msim_get_info, passed as an MsimMessage for 
+	   orthogonality. */
+	msg = (MsimMessage *)data;
+    g_return_if_fail(msg != NULL);
+
+	user = msim_msg_get_string(msg, "user");
+	if (!user)
+	{
+		purple_debug_info("msim", "msim_get_info_cb: no 'user' in msg");
+		return;
+	}
+
+	msim_msg_free(msg);
+	purple_debug_info("msim", "msim_get_info_cb: got for user: %s\n", user);
+
+	body_str = msim_msg_get_string(user_info_msg, "body");
+    g_return_if_fail(body_str != NULL);
+	body = msim_parse_body(body_str);
+	g_free(body_str);
+
+	buddy = purple_find_buddy(session->account, user);
+	/* Note: don't assume buddy is non-NULL; will be if lookup random user 
+     * not on blist. */
+
+	user_info = purple_notify_user_info_new();
+
+	/* Identification */
+	purple_notify_user_info_add_pair(user_info, _("User"), user);
+
+	/* note: g_hash_table_lookup does not create a new string! */
+	str = g_hash_table_lookup(body, "UserID");
+	if (str)
+		purple_notify_user_info_add_pair(user_info, _("User ID"), 
+				g_strdup(str));
+
+	/* a/s/l...the vitals */	
+	str = g_hash_table_lookup(body, "Age");
+	if (str)
+		purple_notify_user_info_add_pair(user_info, _("Age"), g_strdup(str));
+
+	str = g_hash_table_lookup(body, "Gender");
+	if (str)
+		purple_notify_user_info_add_pair(user_info, _("Gender"), g_strdup(str));
+
+	str = g_hash_table_lookup(body, "Location");
+	if (str)
+		purple_notify_user_info_add_pair(user_info, _("Location"), 
+				g_strdup(str));
+
+	/* Other information */
+
+	/* Headline comes from buddy status messages */
+	if (buddy)
+	{
+		str = purple_blist_node_get_string(&buddy->node, "Headline");
+		if (str)
+			purple_notify_user_info_add_pair(user_info, "Headline", str);
+	}
+
+
+	str = g_hash_table_lookup(body, "BandName");
+	str2 = g_hash_table_lookup(body, "SongName");
+	if (str || str2)
+	{
+		purple_notify_user_info_add_pair(user_info, _("Song"), 
+			g_strdup_printf("%s - %s",
+				str ? str : "Unknown Artist",
+				str2 ? str2 : "Unknown Song"));
+	}
+
+
+	/* Total friends only available if looked up by uid, not username. */
+	str = g_hash_table_lookup(body, "TotalFriends");
+	if (str)
+		purple_notify_user_info_add_pair(user_info, _("Total Friends"), 
+			g_strdup(str));
+
+	purple_notify_userinfo(session->gc, user, user_info, NULL, NULL);
+	purple_debug_info("msim", "msim_get_info_cb: username=%s\n", user);
+	//purple_notify_user_info_destroy(user_info);
+	/* Do not free username, since it will be used by user_info. */
+
+	//g_hash_table_destroy(body);
+}
+
+/** Retrieve a user's profile. */
+void 
+msim_get_info(PurpleConnection *gc, const gchar *user)
+{
+	PurpleBuddy *buddy;
+	MsimSession *session;
+	guint uid;
+	gchar *user_to_lookup;
+	MsimMessage *user_msg;
+
+    g_return_if_fail(gc != NULL);
+    g_return_if_fail(user != NULL);
+
+	session = (MsimSession *)gc->proto_data;
+
+    g_return_if_fail(MSIM_SESSION_VALID(session));
+
+	/* Obtain uid of buddy. */
+	buddy = purple_find_buddy(session->account, user);
+	if (buddy)
+	{
+		uid = purple_blist_node_get_int(&buddy->node, "UserID");
+		if (!uid)
+		{
+			PurpleNotifyUserInfo *user_info;
+
+			user_info = purple_notify_user_info_new();
+			purple_notify_user_info_add_pair(user_info, NULL,
+					_("This buddy appears to not have a userid stored in the buddy list, can't look up. Is the user really on the buddy list?"));
+
+			purple_notify_userinfo(session->gc, user, user_info, NULL, NULL);
+			purple_notify_user_info_destroy(user_info);
+			return;
+		}
+
+		user_to_lookup = g_strdup_printf("%d", uid);
+	} else {
+
+		/* Looking up buddy not on blist. Lookup by whatever user entered. */
+		user_to_lookup = g_strdup(user);
+	}
+
+	/* Pass the username to msim_get_info_cb(), because since we lookup
+	 * by userid, the userinfo message will only contain the uid (not 
+	 * the username).
+	 */
+	user_msg = msim_msg_new(TRUE, 
+			"user", MSIM_TYPE_STRING, g_strdup(user),
+			NULL);
+	purple_debug_info("msim", "msim_get_info, setting up lookup, user=%s\n", user);
+
+	msim_lookup_user(session, user_to_lookup, msim_get_info_cb, user_msg);
+
+	g_free(user_to_lookup); 
+}
+
+/** Set your status - callback for when user manually sets it. */
+void
+msim_set_status(PurpleAccount *account, PurpleStatus *status)
+{
+	PurpleStatusType *type;
+	MsimSession *session;
+    guint status_code;
+
+	session = (MsimSession *)account->gc->proto_data;
+
+    g_return_if_fail(MSIM_SESSION_VALID(session));
+
+	type = purple_status_get_type(status);
+
+	switch (purple_status_type_get_primitive(type))
+	{
+		case PURPLE_STATUS_AVAILABLE:
+			status_code = MSIM_STATUS_CODE_ONLINE;
+			break;
+
+		case PURPLE_STATUS_INVISIBLE:
+			status_code = MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN;
+			break;
+
+		case PURPLE_STATUS_AWAY:
+			status_code = MSIM_STATUS_CODE_AWAY;
+			break;
+
+		default:
+			purple_debug_info("msim", "msim_set_status: unknown "
+					"status interpreting as online");
+			status_code = MSIM_STATUS_CODE_ONLINE;
+			break;
+	}
+
+
+    msim_set_status_code(session, status_code);
+}
+
+/** Go idle. */
+void
+msim_set_idle(PurpleConnection *gc, int time)
+{
+    MsimSession *session;
+
+    g_return_if_fail(gc != NULL);
+
+    session = (MsimSession *)gc->proto_data;
+
+    g_return_if_fail(MSIM_SESSION_VALID(session));
+
+    if (time == 0)
+    {
+        /* Going back from idle. In msim, idle is mutually exclusive 
+         * from the other states (you can only be away or idle, but not
+         * both, for example), so by going non-idle I go online.
+         */
+        msim_set_status_code(session, MSIM_STATUS_CODE_ONLINE);
+    } else {
+        /* msim doesn't support idle time, so just go idle */
+        msim_set_status_code(session, MSIM_STATUS_CODE_IDLE);
+    }
+}
+
+/** Set status using an MSIM_STATUS_CODE_* value. (TODO: also set message) */
+void 
+msim_set_status_code(MsimSession *session, guint status_code)
+{
+    g_return_if_fail(MSIM_SESSION_VALID(session));
+
+	if (!msim_send(session,
+			"status", MSIM_TYPE_INTEGER, status_code,
+			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
+			"statstring", MSIM_TYPE_STRING, g_strdup(""),
+			"locstring", MSIM_TYPE_STRING, g_strdup("")))
+	{
+		purple_debug_info("msim", "msim_set_status: failed to set status");
+	}
+
+}
+
+/** After a uid is resolved to username, tag it with the username and submit for processing. 
+ * 
+ * @param session
+ * @param userinfo Response messsage to resolving request.
+ * @param data MsimMessage *, the message to attach information to. 
+ */
+static void 
+msim_incoming_resolved(MsimSession *session, MsimMessage *userinfo, 
+		gpointer data)
+{
+	gchar *body_str;
+	GHashTable *body;
+	gchar *username;
+	MsimMessage *msg;
+
+    g_return_if_fail(MSIM_SESSION_VALID(session));
+    g_return_if_fail(userinfo != NULL);
+
+	body_str = msim_msg_get_string(userinfo, "body");
+	g_return_if_fail(body_str != NULL);
+	body = msim_parse_body(body_str);
+	g_return_if_fail(body != NULL);
+	g_free(body_str);
+
+	username = g_hash_table_lookup(body, "UserName");
+	g_return_if_fail(username != NULL);
+
+	msg = (MsimMessage *)data;
+    g_return_if_fail(msg != NULL);
+
+	/* Special elements name beginning with '_', we'll use internally within the
+	 * program (did not come from the wire). */
+	msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username));
+
+	msim_process(session, msg);
+
+	/* TODO: Free copy cloned from  msim_preprocess_incoming(). */
+	//XXX msim_msg_free(msg);
+	g_hash_table_destroy(body);
+}
+
+#if 0
+/* Lookup a username by userid, from buddy list. 
+ *
+ * @param wanted_uid
+ *
+ * @return Username of wanted_uid, if on blist, or NULL. Static string. 
+ *
+ * XXX WARNING: UNKNOWN MEMORY CORRUPTION HERE! 
+ */
+static const gchar *
+msim_uid2username_from_blist(MsimSession *session, guint wanted_uid)
+{
+	GSList *buddies, *cur;
+
+	buddies = purple_find_buddies(session->account, NULL); 
+
+	if (!buddies)
+	{
+		purple_debug_info("msim", "msim_uid2username_from_blist: no buddies?");
+		return NULL;
+	}
+
+	for (cur = buddies; cur != NULL; cur = g_slist_next(cur))
+	{
+		PurpleBuddy *buddy;
+		//PurpleBlistNode *node;
+		guint uid;
+		const gchar *name;
+
+
+		/* See finch/gnthistory.c */
+		buddy = cur->data;
+		//node  = cur->data;
+
+		uid = purple_blist_node_get_int(&buddy->node, "UserID");
+		//uid = purple_blist_node_get_int(node, "UserID");
+
+		/* name = buddy->name; */								/* crash */
+		/* name = PURPLE_BLIST_NODE_NAME(&buddy->node);  */		/* crash */
+
+		/* XXX Is this right? Memory corruption here somehow. Happens only
+		 * when return one of these values. */
+		name = purple_buddy_get_name(buddy); 					/* crash */
+		//name = purple_buddy_get_name((PurpleBuddy *)node); 	/* crash */
+		/* return name; */										/* crash (with above) */
+
+		/* name = NULL; */										/* no crash */
+		/* return NULL; */										/* no crash (with anything) */
+
+		/* crash =
+*** glibc detected *** pidgin: realloc(): invalid pointer: 0x0000000000d2aec0 ***
+======= Backtrace: =========
+/lib/libc.so.6(__libc_realloc+0x323)[0x2b7bfc012e03]
+/usr/lib/libglib-2.0.so.0(g_realloc+0x31)[0x2b7bfba79a41]
+/usr/lib/libgtk-x11-2.0.so.0(gtk_tree_path_append_index+0x3a)[0x2b7bfa110d5a]
+/usr/lib/libgtk-x11-2.0.so.0[0x2b7bfa1287dc]
+/usr/lib/libgtk-x11-2.0.so.0[0x2b7bfa128e56]
+/usr/lib/libgtk-x11-2.0.so.0[0x2b7bfa128efd]
+/usr/lib/libglib-2.0.so.0(g_main_context_dispatch+0x1b4)[0x2b7bfba72c84]
+/usr/lib/libglib-2.0.so.0[0x2b7bfba75acd]
+/usr/lib/libglib-2.0.so.0(g_main_loop_run+0x1ca)[0x2b7bfba75dda]
+/usr/lib/libgtk-x11-2.0.so.0(gtk_main+0xa3)[0x2b7bfa0475f3]
+pidgin(main+0x8be)[0x46b45e]
+/lib/libc.so.6(__libc_start_main+0xf4)[0x2b7bfbfbf0c4]
+pidgin(gtk_widget_grab_focus+0x39)[0x429ab9]
+
+or:
+ *** glibc detected *** /usr/local/bin/pidgin: malloc(): memory corruption (fast): 0x0000000000c10076 ***
+ (gdb) bt
+#0  0x00002b4074ecd47b in raise () from /lib/libc.so.6
+#1  0x00002b4074eceda0 in abort () from /lib/libc.so.6
+#2  0x00002b4074f0453b in __fsetlocking () from /lib/libc.so.6
+#3  0x00002b4074f0c810 in free () from /lib/libc.so.6
+#4  0x00002b4074f0d6dd in malloc () from /lib/libc.so.6
+#5  0x00002b4074974b5b in g_malloc () from /usr/lib/libglib-2.0.so.0
+#6  0x00002b40749868bf in g_strdup () from /usr/lib/libglib-2.0.so.0
+#7  0x00002b407810969f in msim_parse (
+    raw=0xd2a910 "\\bm\\100\\f\\3656574\\msg\\|s|0|ss|Offline")
+	    at message.c:648
+#8  0x00002b407810889c in msim_input_cb (gc_uncasted=0xcf92c0, 
+    source=<value optimized out>, cond=<value optimized out>) at myspace.c:1478
+
+
+	Why is it crashing in msim_parse()'s g_strdup()?
+*/
+		purple_debug_info("msim", "msim_uid2username_from_blist: %s's uid=%d (want %d)\n",
+				name, uid, wanted_uid);
+
+		if (uid == wanted_uid)
+		{
+			gchar *ret;
+
+			ret = g_strdup(name);
+
+			g_slist_free(buddies);
+
+			return ret;
+		}
+	}
+
+	g_slist_free(buddies);
+	return NULL;
+}
+#endif
+
+/** Preprocess incoming messages, resolving as needed, calling msim_process() when ready to process.
+ *
+ * @param session
+ * @param msg MsimMessage *, freed by caller.
+ */
+gboolean 
+msim_preprocess_incoming(MsimSession *session, MsimMessage *msg)
+{
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+    g_return_val_if_fail(msg != NULL, FALSE);
+
+	if (msim_msg_get(msg, "bm") && msim_msg_get(msg, "f"))
+	{
+		guint uid;
+		const gchar *username;
+
+		/* 'f' = userid message is from, in buddy messages */
+		uid = msim_msg_get_integer(msg, "f");
+
+		/* TODO: Make caching work. Currently it is commented out because
+		 * it crashes for unknown reasons, memory realloc error. */
+#if 0
+		username = msim_uid2username_from_blist(session, uid); 
+#else
+		username = NULL; 
+#endif
+
+		if (username)
+		{
+			/* Know username already, use it. */
+			purple_debug_info("msim", "msim_preprocess_incoming: tagging with _username=%s\n",
+					username);
+			msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username));
+			return msim_process(session, msg);
+
+		} else {
+			gchar *from;
+
+			/* Send lookup request. */
+			/* XXX: where is msim_msg_get_string() freed? make _strdup and _nonstrdup. */
+			purple_debug_info("msim", "msim_incoming: sending lookup, setting up callback\n");
+			from = msim_msg_get_string(msg, "f");
+			msim_lookup_user(session, from, msim_incoming_resolved, msim_msg_clone(msg)); 
+			g_free(from);
+
+			/* indeterminate */
+			return TRUE;
+		}
+	} else {
+		/* Nothing to resolve - send directly to processing. */
+		return msim_process(session, msg);
+	}
+}
+
+/** Check if the connection is still alive, based on last communication. */
+gboolean
+msim_check_alive(gpointer data)
+{
+    MsimSession *session;
+    time_t delta;
+    gchar *errmsg;
+
+    session = (MsimSession *)data;
+
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+
+    delta = time(NULL) - session->last_comm;
+    purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta);
+    if (delta >= MSIM_KEEPALIVE_INTERVAL)
+    {
+        errmsg = g_strdup_printf(_("Connection to server lost (no data received within %d seconds)"), delta);
+
+        purple_debug_info("msim", "msim_check_alive: %s > interval of %d, presumed dead\n",
+                errmsg, MSIM_KEEPALIVE_INTERVAL);
+        purple_connection_error(session->gc, errmsg);
+
+        purple_notify_error(session->gc, NULL, errmsg, NULL);
+
+        g_free(errmsg);
+
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+/** Called when the session key arrives. */
+gboolean
+msim_we_are_logged_on(MsimSession *session, MsimMessage *msg)
+{
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+    g_return_val_if_fail(msg != NULL, FALSE);
+
+    purple_connection_update_progress(session->gc, _("Connected"), 3, 4);
+
+    session->sesskey = msim_msg_get_integer(msg, "sesskey");
+    purple_debug_info("msim", "SESSKEY=<%d>\n", session->sesskey);
+
+    /* Comes with: proof,profileid,userid,uniquenick -- all same values
+     * some of the time, but can vary. This is our own user ID. */
+    session->userid = msim_msg_get_integer(msg, "userid");
+
+    purple_connection_set_state(session->gc, PURPLE_CONNECTED);
+
+    /* We now know are our own username, only after we're logged in..
+     * which is weird, but happens because you login with your email
+     * address and not username. Will be freed in msim_session_destroy(). */
+    session->username = msim_msg_get_string(msg, "uniquenick");
+
+#ifdef MSIM_FAKE_SELF_ONLINE
+    /* Fake our self coming online. */
+    purple_prpl_got_user_status(session->account, session->username, purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE), NULL);
+#endif
+
+
+    /* Set status to current status. */
+    msim_set_status(session->account,
+            purple_account_get_active_status(session->account));
+
+    purple_timeout_add(MSIM_KEEPALIVE_INTERVAL_CHECK, msim_check_alive, session);
+
+    return TRUE;
+}
+
+/**
+ * Process a message. 
+ *
+ * @param session
+ * @param msg A message from the server, ready for processing (possibly with resolved username information attached). Caller frees.
+ *
+ * @return TRUE if successful. FALSE if processing failed.
+ */
+gboolean 
+msim_process(MsimSession *session, MsimMessage *msg)
+{
+    g_return_val_if_fail(session != NULL, FALSE);
+    g_return_val_if_fail(msg != NULL, FALSE);
+
+#ifdef MSIM_DEBUG_MSG
+	{
+		msim_msg_dump("ready to process: %s\n", msg);
+	}
+#endif
+
+    if (msim_msg_get(msg, "nc"))
+    {
+        return msim_login_challenge(session, msg);
+    } else if (msim_msg_get(msg, "sesskey")) {
+        return msim_we_are_logged_on(session, msg);
+    } else if (msim_msg_get(msg, "bm"))  {
+        guint bm;
+       
+        bm = msim_msg_get_integer(msg, "bm");
+        switch (bm)
+        {
+            case MSIM_BM_STATUS:
+                return msim_status(session, msg);
+            case MSIM_BM_INSTANT:
+                return msim_incoming_im(session, msg);
+			case MSIM_BM_ACTION:
+				return msim_incoming_action(session, msg);
+            default:
+                /* Not really an IM, but show it for informational 
+                 * purposes during development. */
+                return msim_incoming_im(session, msg);
+        }
+    } else if (msim_msg_get(msg, "rid")) {
+        return msim_process_reply(session, msg);
+    } else if (msim_msg_get(msg, "error")) {
+        return msim_error(session, msg);
+    } else if (msim_msg_get(msg, "ka")) {
+		/* TODO: Setup a timer, if keep-alive is not received within ~3 minutes, then 
+		 * disconnect the user. As it stands, if Internet connection goes out (this
+		 * just happened here), msimprpl will appear to be connected forever, while
+		 * other plugins (oscar, etc.) will time out. Msimprpl should timeout too. */
+        purple_debug_info("msim", "msim_process: got keep alive\n");
+        return TRUE;
+    } else {
+		msim_unrecognized(session, msg, "in msim_process");
+        return FALSE;
+    }
+}
+
+/** Store an field of information about a buddy. */
+void 
+msim_store_buddy_info_each(gpointer key, gpointer value, gpointer user_data)
+{
+	PurpleBuddy *buddy;
+	gchar *key_str, *value_str;
+
+	buddy = (PurpleBuddy *)user_data;
+	key_str = (gchar *)key;
+	value_str = (gchar *)value;
+
+	if (strcmp(key_str, "UserID") == 0 ||
+			strcmp(key_str, "Age") == 0 ||
+			strcmp(key_str, "TotalFriends") == 0)
+	{
+		/* Certain fields get set as integers, instead of strings, for
+		 * convenience. May not be the best way to do it, but having at least
+		 * UserID as an integer is convenient...until it overflows! */
+		purple_blist_node_set_int(&buddy->node, key_str, atol(value_str));
+	} else {
+		purple_blist_node_set_string(&buddy->node, key_str, value_str);
+	}
+}
+
+/** Save buddy information to the buddy list from a user info reply message.
+ *
+ * @param session
+ * @param msg The user information reply, with any amount of information.
+ *
+ * The information is saved to the buddy's blist node, which ends up in blist.xml.
+ */
+gboolean 
+msim_store_buddy_info(MsimSession *session, MsimMessage *msg)
+{
+	GHashTable *body;
+	gchar *username, *body_str, *uid;
+	PurpleBuddy *buddy;
+	guint rid;
+
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+    g_return_val_if_fail(msg != NULL, FALSE);
+ 
+	rid = msim_msg_get_integer(msg, "rid");
+	
+	g_return_val_if_fail(rid != 0, FALSE);
+
+	body_str = msim_msg_get_string(msg, "body");
+	g_return_val_if_fail(body_str != NULL, FALSE);
+	body = msim_parse_body(body_str);
+	g_free(body_str);
+
+	/* TODO: implement a better hash-like interface, and use it. */
+	username = g_hash_table_lookup(body, "UserName");
+
+	if (!username)
+	{
+		purple_debug_info("msim", 
+			"msim_process_reply: not caching body, no UserName\n");
+		return FALSE;
+	}
+
+	uid = g_hash_table_lookup(body, "UserID");
+	g_return_val_if_fail(uid, FALSE);
+
+	purple_debug_info("msim", "associating uid %d with username %s\n", uid, username);
+
+	buddy = purple_find_buddy(session->account, username);
+	if (buddy)
+	{
+		g_hash_table_foreach(body, msim_store_buddy_info_each, buddy);
+	}
+
+	return TRUE;
+}
+
+/**
+ * Process a persistance message reply from the server.
+ *
+ * @param session 
+ * @param msg Message reply from server.
+ *
+ * @return TRUE if successful.
+ */
+gboolean 
+msim_process_reply(MsimSession *session, MsimMessage *msg)
+{
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+    g_return_val_if_fail(msg != NULL, FALSE);
+    
+    if (msim_msg_get(msg, "rid"))  /* msim_lookup_user sets callback for here */
+    {
+        MSIM_USER_LOOKUP_CB cb;
+        gpointer data;
+        guint rid;
+
+		msim_store_buddy_info(session, msg);		
+
+		rid = msim_msg_get_integer(msg, "rid");
+
+        /* If a callback is registered for this userid lookup, call it. */
+        cb = g_hash_table_lookup(session->user_lookup_cb, GUINT_TO_POINTER(rid));
+        data = g_hash_table_lookup(session->user_lookup_cb_data, GUINT_TO_POINTER(rid));
+
+        if (cb)
+        {
+            purple_debug_info("msim", 
+					"msim_process_body: calling callback now\n");
+			/* Clone message, so that the callback 'cb' can use it (needs to free it also). */
+            cb(session, msim_msg_clone(msg), data);
+            g_hash_table_remove(session->user_lookup_cb, GUINT_TO_POINTER(rid));
+            g_hash_table_remove(session->user_lookup_cb_data, GUINT_TO_POINTER(rid));
+        } else {
+            purple_debug_info("msim", 
+					"msim_process_body: no callback for rid %d\n", rid);
+        }
+    }
+
+	return TRUE;
+}
+
+/**
+ * Handle an error from the server.
+ *
+ * @param session 
+ * @param msg The message.
+ *
+ * @return TRUE if successfully reported error.
+ */
+gboolean 
+msim_error(MsimSession *session, MsimMessage *msg)
+{
+    gchar *errmsg, *full_errmsg;
+	guint err;
+
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+    g_return_val_if_fail(msg != NULL, FALSE);
+
+    err = msim_msg_get_integer(msg, "err");
+    errmsg = msim_msg_get_string(msg, "errmsg");
+
+    full_errmsg = g_strdup_printf(_("Protocol error, code %d: %s"), err, 
+			errmsg ? errmsg : "no 'errmsg' given");
+
+	g_free(errmsg);
+
+    purple_debug_info("msim", "msim_error: %s\n", full_errmsg);
+
+    purple_notify_error(session->account, g_strdup(_("MySpaceIM Error")), 
+            full_errmsg, NULL);
+
+	/* Destroy session if fatal. */
+    if (msim_msg_get(msg, "fatal"))
+    {
+        purple_debug_info("msim", "fatal error, closing\n");
+        purple_connection_error(session->gc, full_errmsg);
+    }
+
+    return TRUE;
+}
+
+/**
+ * Process incoming status messages.
+ *
+ * @param session
+ * @param msg Status update message. Caller frees.
+ *
+ * @return TRUE if successful.
+ */
+gboolean 
+msim_status(MsimSession *session, MsimMessage *msg)
+{
+    PurpleBuddyList *blist;
+    PurpleBuddy *buddy;
+    //PurpleStatus *status;
+    gchar **status_array;
+    GList *list;
+    gchar *status_headline;
+    gchar *status_str;
+    gint i, status_code, purple_status_code;
+    gchar *username;
+
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+    g_return_val_if_fail(msg != NULL, FALSE);
+
+    status_str = msim_msg_get_string(msg, "msg");
+	g_return_val_if_fail(status_str != NULL, FALSE);
+
+	msim_msg_dump("msim_status msg=%s\n", msg);
+
+	/* Helpfully looked up by msim_incoming_resolve() for us. */
+    username = msim_msg_get_string(msg, "_username");
+    /* Note: DisplayName doesn't seem to be resolvable. It could be displayed on
+     * the buddy list, if the UserID was stored along with it. */
+
+	if (username == NULL)
+	{
+		g_free(status_str);
+		g_return_val_if_fail(NULL, FALSE);
+	}
+
+    purple_debug_info("msim", 
+			"msim_status: updating status for <%s> to <%s>\n", 
+			username, status_str);
+
+    /* TODO: generic functions to split into a GList, part of MsimMessage */
+    status_array = g_strsplit(status_str, "|", 0);
+    for (list = NULL, i = 0;
+            status_array[i];
+            i++)
+    {
+		/* Note: this adds the 0th ordinal too, which might not be a value
+		 * at all (the 0 in the 0|1|2|3... status fields, but 0 always appears blank).
+		 */
+        list = g_list_append(list, status_array[i]);
+    }
+
+    /* Example fields: 
+	 *  |s|0|ss|Offline 
+	 *  |s|1|ss|:-)|ls||ip|0|p|0 
+	 *
+	 * TODO: write list support in MsimMessage, and use it here.
+	 */
+
+    status_code = atoi(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE));
+	purple_debug_info("msim", "msim_status: %s's status code = %d\n", username, status_code);
+    status_headline = g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE);
+
+    blist = purple_get_blist();
+
+    /* Add buddy if not found */
+    buddy = purple_find_buddy(session->account, username);
+    if (!buddy)
+    {
+        purple_debug_info("msim", 
+				"msim_status: making new buddy for %s\n", username);
+        buddy = purple_buddy_new(session->account, username, NULL);
+
+        purple_blist_add_buddy(buddy, NULL, NULL, NULL);
+
+		/* All buddies on list should have 'uid' integer associated with them. */
+		purple_blist_node_set_int(&buddy->node, "UserID", msim_msg_get_integer(msg, "f"));
+		
+		msim_store_buddy_info(session, msg);
+    } else {
+        purple_debug_info("msim", "msim_status: found buddy %s\n", username);
+    }
+
+	purple_blist_node_set_string(&buddy->node, "Headline", status_headline);
+  
+    /* Set user status */	
+    switch (status_code)
+	{
+		case MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN: 
+			purple_status_code = PURPLE_STATUS_OFFLINE;	
+			break;
+
+		case MSIM_STATUS_CODE_ONLINE: 
+			purple_status_code = PURPLE_STATUS_AVAILABLE;
+			break;
+
+		case MSIM_STATUS_CODE_AWAY:
+			purple_status_code = PURPLE_STATUS_AWAY;
+			break;
+
+        case MSIM_STATUS_CODE_IDLE:
+            /* will be handled below */
+            purple_status_code = -1;
+            break;
+
+		default:
+				purple_debug_info("msim", "msim_status for %s, unknown status code %d, treating as available\n",
+						username, status_code);
+				purple_status_code = PURPLE_STATUS_AVAILABLE;
+	}
+
+#ifdef MSIM_FAKE_SELF_ONLINE
+	if (!strcmp(username, session->username) && purple_status_code == PURPLE_STATUS_OFFLINE)
+	{
+		/* Hack to ignore offline status notices on self. */
+	} else if (status_code != MSIM_STATUS_CODE_IDLE) {
+#endif
+		purple_prpl_got_user_status(session->account, username, purple_primitive_get_id_from_type(purple_status_code), NULL);
+#ifdef MSIM_FAKE_SELF_ONLINE
+	}
+#endif
+
+    if (status_code == MSIM_STATUS_CODE_IDLE)
+    {
+        purple_debug_info("msim", "msim_status: got idle: %s\n", username);
+        purple_prpl_got_user_idle(session->account, username, TRUE, time(NULL));
+    } else {
+        /* All other statuses indicate going back to non-idle. */
+        purple_prpl_got_user_idle(session->account, username, FALSE, time(NULL));
+    }
+
+    g_strfreev(status_array);
+	g_free(status_str);
+	g_free(username);
+    g_list_free(list);
+
+    return TRUE;
+}
+
+/** Add a buddy to user's buddy list. */
+void 
+msim_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
+{
+	MsimSession *session;
+	MsimMessage *msg;
+    /* MsimMessage	*msg_blocklist; */
+
+	session = (MsimSession *)gc->proto_data;
+	purple_debug_info("msim", "msim_add_buddy: want to add %s to %s\n", buddy->name,
+			group ? group->name : "(no group)");
+
+	msg = msim_msg_new(TRUE,
+			"addbuddy", MSIM_TYPE_BOOLEAN, TRUE,
+			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
+			/* "newprofileid" will be inserted here with uid. */
+			"reason", MSIM_TYPE_STRING, g_strdup(""),
+			NULL);
+
+	if (!msim_postprocess_outgoing(session, msg, buddy->name, "newprofileid", "reason"))
+	{
+		purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("'addbuddy' command failed."));
+		msim_msg_free(msg);
+		return;
+	}
+	msim_msg_free(msg);
+	
+	/* TODO: if addbuddy fails ('error' message is returned), delete added buddy from
+	 * buddy list since Purple adds it locally. */
+
+	/* TODO: Update blocklist. */
+#if 0
+	msg_blocklist = msim_msg_new(TRUE,
+		"persist", MSIM_TYPE_INTEGER, 1,
+		"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
+		"cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_PUT,
+		"dsn", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_DSN,
+		"lid", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_LID,
+		/* TODO: Use msim_new_reply_callback to get rid. */
+		"rid", MSIM_TYPE_INTEGER, session->next_rid++,
+		"body", MSIM_TYPE_STRING,
+			g_strdup_printf("ContactID=<uid>\034"
+			"GroupName=%s\034"
+			"Position=1000\034"
+			"Visibility=1\034"
+			"NickName=\034"
+			"NameSelect=0",
+			"Friends" /*group->name*/ ));
+
+	if (!msim_postprocess_outgoing(session, msg, buddy->name, "body", NULL))
+	{
+		purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("persist command failed"));
+		msim_msg_free(msg_blocklist);
+		return;
+	}
+	msim_msg_free(msg_blocklist);
+#endif
+}
+
+/** Perform actual postprocessing on a message, adding userid as specified.
+ *
+ * @param msg The message to postprocess.
+ * @param uid_before Name of field where to insert new field before, or NULL for end.
+ * @param uid_field_name Name of field to add uid to.
+ * @param uid The userid to insert.
+ *
+ * If the field named by uid_field_name already exists, then its string contents will
+ * be used for the field, except "<uid>" will be replaced by the userid.
+ *
+ * If the field named by uid_field_name does not exist, it will be added before the
+ * field named by uid_before, as an integer, with the userid.
+ *
+ * Does not handle sending, or scheduling userid lookup. For that, see msim_postprocess_outgoing().
+ */ 
+MsimMessage *
+msim_do_postprocessing(MsimMessage *msg, const gchar *uid_before, 
+		const gchar *uid_field_name, guint uid)
+{	
+	purple_debug_info("msim", "msim_do_postprocessing called with ufn=%s, ub=%s, uid=%d\n",
+			uid_field_name, uid_before, uid);
+	msim_msg_dump("msim_do_postprocessing msg: %s\n", msg);
+
+	/* First, check - if the field already exists, treat it as a format string. */
+	if (msim_msg_get(msg, uid_field_name))
+	{
+		MsimMessageElement *elem;
+		gchar *fmt_string;
+		gchar *uid_str;
+
+		/* Warning: this probably violates the encapsulation of MsimMessage */
+
+		elem = msim_msg_get(msg, uid_field_name);
+		g_return_val_if_fail(elem->type == MSIM_TYPE_STRING, NULL);
+
+		/* Get the raw string, not with msim_msg_get_string() since that copies it. 
+		 * Want the original string so can free it. */
+		fmt_string = (gchar *)(elem->data);
+
+		uid_str = g_strdup_printf("%d", uid);
+		elem->data = str_replace(fmt_string, "<uid>", uid_str);
+		g_free(uid_str);
+		g_free(fmt_string);
+
+		purple_debug_info("msim", "msim_postprocess_outgoing_cb: formatted new string, %s\n",
+				elem->data);
+
+	} else {
+		/* Otherwise, insert new field into outgoing message. */
+		msg = msim_msg_insert_before(msg, uid_before, uid_field_name, MSIM_TYPE_INTEGER, GUINT_TO_POINTER(uid));
+	}
+
+	return msg;
+}	
+
+/** Callback for msim_postprocess_outgoing() to add a userid to a message, and send it (once receiving userid).
+ *
+ * @param session
+ * @param userinfo The user information reply message, containing the user ID
+ * @param data The message to postprocess and send.
+ *
+ * The data message should contain these fields:
+ *
+ *  _uid_field_name: string, name of field to add with userid from userinfo message
+ *  _uid_before: string, name of field before field to insert, or NULL for end
+ *
+ *
+*/
+void 
+msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, gpointer data)
+{
+	gchar *body_str;
+	GHashTable *body;
+	gchar *uid, *uid_field_name, *uid_before;
+	MsimMessage *msg;
+
+	msg = (MsimMessage *)data;
+
+	msim_msg_dump("msim_postprocess_outgoing_cb() got msg=%s\n", msg);
+
+	/* Obtain userid from userinfo message. */
+	body_str = msim_msg_get_string(userinfo, "body");
+	g_return_if_fail(body_str != NULL);
+	body = msim_parse_body(body_str);
+	g_free(body_str);
+
+	uid = g_strdup(g_hash_table_lookup(body, "UserID"));
+	g_hash_table_destroy(body);
+
+	uid_field_name = msim_msg_get_string(msg, "_uid_field_name");
+	uid_before = msim_msg_get_string(msg, "_uid_before");
+
+	msg = msim_do_postprocessing(msg, uid_before, uid_field_name, atol(uid));
+
+	/* Send */
+	if (!msim_msg_send(session, msg))
+	{
+		purple_debug_info("msim", "msim_postprocess_outgoing_cb: sending failed for message: %s\n", msg);
+	}
+
+
+	/* Free field names AFTER sending message, because MsimMessage does NOT copy
+	 * field names - instead, treats them as static strings (which they usually are).
+	 */
+	g_free(uid_field_name);
+	g_free(uid_before);
+
+	//msim_msg_free(msg);
+}
+
+/** Postprocess and send a message.
+ *
+ * @param session
+ * @param msg Message to postprocess. Will NOT be freed.
+ * @param username Username to resolve. Assumed to be a static string (will not be freed or copied).
+ * @param uid_field_name Name of new field to add, containing uid of username. Static string.
+ * @param uid_before Name of existing field to insert username field before. Static string.
+ *
+ * @return Postprocessed message.
+ */
+gboolean 
+msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, 
+		const gchar *username, const gchar *uid_field_name, 
+		const gchar *uid_before)
+{
+    PurpleBuddy *buddy;
+	guint uid;
+	gboolean rc;
+
+	/* Store information for msim_postprocess_outgoing_cb(). */
+	purple_debug_info("msim", "msim_postprocess_outgoing(u=%s,ufn=%s,ub=%s)\n",
+			username, uid_field_name, uid_before);
+	msim_msg_dump("msim_postprocess_outgoing: msg before=%s\n", msg);
+	msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username));
+	msg = msim_msg_append(msg, "_uid_field_name", MSIM_TYPE_STRING, g_strdup(uid_field_name));
+	msg = msim_msg_append(msg, "_uid_before", MSIM_TYPE_STRING, g_strdup(uid_before));
+
+	/* First, try the most obvious. If numeric userid is given, use that directly. */
+    if (msim_is_userid(username))
+    {
+		uid = atol(username);
+    } else {
+		/* Next, see if on buddy list and know uid. */
+		buddy = purple_find_buddy(session->account, username);
+		if (buddy)
+		{
+			uid = purple_blist_node_get_int(&buddy->node, "UserID");
+		} else {
+			uid = 0;
+		}
+
+		if (!buddy || !uid)
+		{
+			/* Don't have uid offhand - need to ask for it, and wait until hear back before sending. */
+			purple_debug_info("msim", ">>> msim_postprocess_outgoing: couldn't find username %s in blist\n",
+					username);
+			msim_msg_dump("msim_postprocess_outgoing - scheduling lookup, msg=%s\n", msg);
+			/* TODO: where is cloned message freed? Should be in _cb. */
+			msim_lookup_user(session, username, msim_postprocess_outgoing_cb, msim_msg_clone(msg));
+			return TRUE;		/* not sure of status yet - haven't sent! */
+		}
+	}
+	
+	/* Already have uid, postprocess and send msg immediately. */
+	purple_debug_info("msim", "msim_postprocess_outgoing: found username %s has uid %d\n",
+			username, uid);
+
+	msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid);
+
+	msim_msg_dump("msim_postprocess_outgoing: msg after (uid immediate)=%s\n", msg);
+	
+	rc = msim_msg_send(session, msg);
+
+	//msim_msg_free(msg);
+
+	return rc;
+}
+
+/** Remove a buddy from the user's buddy list. */
+void 
+msim_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
+{
+	MsimSession *session;
+	MsimMessage *delbuddy_msg;
+	MsimMessage *persist_msg;
+	MsimMessage *blocklist_msg;
+
+	session = (MsimSession *)gc->proto_data;
+
+	delbuddy_msg = msim_msg_new(TRUE,
+				"delbuddy", MSIM_TYPE_BOOLEAN, TRUE,
+				"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
+				/* 'delprofileid' with uid will be inserted here. */
+				NULL);
+	/* TODO: free msg */
+	if (!msim_postprocess_outgoing(session, delbuddy_msg, buddy->name, "delprofileid", NULL))
+	{
+		purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("'delbuddy' command failed"));
+		return;
+	}
+
+	persist_msg = msim_msg_new(TRUE, 
+			"persist", MSIM_TYPE_INTEGER, 1,
+			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
+			"cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_DELETE,
+			"dsn", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_DSN,
+			"lid", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_LID,
+			"uid", MSIM_TYPE_INTEGER, session->userid,
+			"rid", MSIM_TYPE_INTEGER, session->next_rid++,
+			/* <uid> will be replaced by postprocessing */
+			"body", MSIM_TYPE_STRING, g_strdup("ContactID=<uid>"),
+			NULL);
+
+	/* TODO: free msg */
+	if (!msim_postprocess_outgoing(session, persist_msg, buddy->name, "body", NULL))
+	{
+		purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("persist command failed"));	
+		return;
+	}
+
+	blocklist_msg = msim_msg_new(TRUE,
+			"blocklist", MSIM_TYPE_BOOLEAN, TRUE,
+			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
+			/* TODO: MsimMessage lists */
+			"idlist", MSIM_TYPE_STRING, g_strdup("a-|<uid>|b-|<uid>"),
+			NULL);
+
+	if (!msim_postprocess_outgoing(session, blocklist_msg, buddy->name, "idlist", NULL))
+	{
+		purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("blocklist command failed"));
+		return;
+	}
+}
+
+/** Return whether the buddy can be messaged while offline.
+ *
+ * The protocol supports offline messages in just the same way as online
+ * messages.
+ */
+gboolean 
+msim_offline_message(const PurpleBuddy *buddy)
+{
+	return TRUE;
+}
+
+/**
+ * Callback when input available.
+ *
+ * @param gc_uncasted A PurpleConnection pointer.
+ * @param source File descriptor.
+ * @param cond PURPLE_INPUT_READ
+ *
+ * Reads the input, and calls msim_preprocess_incoming() to handle it.
+ */
+void 
+msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond)
+{
+    PurpleConnection *gc;
+    PurpleAccount *account;
+    MsimSession *session;
+    gchar *end;
+    int n;
+
+    g_return_if_fail(gc_uncasted != NULL);
+    g_return_if_fail(source >= 0);  /* Note: 0 is a valid fd */
+
+    gc = (PurpleConnection *)(gc_uncasted);
+    account = purple_connection_get_account(gc);
+    session = gc->proto_data;
+
+    g_return_if_fail(cond == PURPLE_INPUT_READ);
+	g_return_if_fail(MSIM_SESSION_VALID(session));
+
+    /* Mark down that we got data, so don't timeout. */
+    session->last_comm = time(NULL);
+
+    /* Only can handle so much data at once... 
+     * If this happens, try recompiling with a higher MSIM_READ_BUF_SIZE.
+     * Should be large enough to hold the largest protocol message.
+     */
+    if (session->rxoff == MSIM_READ_BUF_SIZE)
+    {
+        purple_debug_error("msim", "msim_input_cb: %d-byte read buffer full!\n",
+                MSIM_READ_BUF_SIZE);
+        purple_connection_error(gc, _("Read buffer full"));
+        return;
+    }
+
+    purple_debug_info("msim", "buffer at %d (max %d), reading up to %d\n",
+            session->rxoff, MSIM_READ_BUF_SIZE, 
+			MSIM_READ_BUF_SIZE - session->rxoff);
+
+    /* Read into buffer. On Win32, need recv() not read(). session->fd also holds
+     * the file descriptor, but it sometimes differs from the 'source' parameter.
+     */
+    n = recv(session->fd, session->rxbuf + session->rxoff, MSIM_READ_BUF_SIZE - session->rxoff, 0);
+
+    if (n < 0 && errno == EAGAIN)
+    {
+        return;
+    }
+    else if (n < 0)
+    {
+        purple_debug_error("msim", "msim_input_cb: read error, ret=%d, "
+			"error=%s, source=%d, fd=%d (%X))\n", 
+			n, strerror(errno), source, session->fd, session->fd);
+        purple_connection_error(gc, _("Read error"));
+        return;
+    } 
+    else if (n == 0)
+    {
+        purple_debug_info("msim", "msim_input_cb: server disconnected\n");
+        purple_connection_error(gc, _("Server has disconnected"));
+        return;
+    }
+
+    /* Null terminate */
+    session->rxbuf[session->rxoff + n] = 0;
+
+#ifdef MSIM_CHECK_EMBEDDED_NULLS
+    /* Check for embedded NULs. I don't handle them, and they shouldn't occur. */
+    if (strlen(session->rxbuf + session->rxoff) != n)
+    {
+        /* Occurs after login, but it is not a null byte. */
+        purple_debug_info("msim", "msim_input_cb: strlen=%d, but read %d bytes"
+                "--null byte encountered?\n", 
+				strlen(session->rxbuf + session->rxoff), n);
+        //purple_connection_error(gc, "Invalid message - null byte on input");
+        return;
+    }
+#endif
+
+    session->rxoff += n;
+    purple_debug_info("msim", "msim_input_cb: read=%d\n", n);
+
+#ifdef MSIM_DEBUG_RXBUF
+    purple_debug_info("msim", "buf=<%s>\n", session->rxbuf);
+#endif
+
+    /* Look for \\final\\ end markers. If found, process message. */
+    while((end = strstr(session->rxbuf, MSIM_FINAL_STRING)))
+    {
+        MsimMessage *msg;
+
+#ifdef MSIM_DEBUG_RXBUF
+        purple_debug_info("msim", "in loop: buf=<%s>\n", session->rxbuf);
+#endif
+        *end = 0;
+        msg = msim_parse(g_strdup(session->rxbuf));
+        if (!msg)
+        {
+            purple_debug_info("msim", "msim_input_cb: couldn't parse <%s>\n", 
+					session->rxbuf);
+            purple_connection_error(gc, _("Unparseable message"));
+        }
+        else
+        {
+            /* Process message and then free it (processing function should
+			 * clone message if it wants to keep it afterwards.) */
+            if (!msim_preprocess_incoming(session, msg))
+			{
+				msim_msg_dump("msim_input_cb: preprocessing message failed on msg: %s\n", msg);
+			}
+			msim_msg_free(msg);
+        }
+
+        /* Move remaining part of buffer to beginning. */
+        session->rxoff -= strlen(session->rxbuf) + strlen(MSIM_FINAL_STRING);
+        memmove(session->rxbuf, end + strlen(MSIM_FINAL_STRING), 
+                MSIM_READ_BUF_SIZE - (end + strlen(MSIM_FINAL_STRING) - session->rxbuf));
+
+        /* Clear end of buffer */
+        //memset(end, 0, MSIM_READ_BUF_SIZE - (end - session->rxbuf));
+    }
+}
+
+/* Setup a callback, to be called when a reply is received with the returned rid.
+ *
+ * @param cb The callback, an MSIM_USER_LOOKUP_CB.
+ * @param data Arbitrary user data to be passed to callback (probably an MsimMessage *).
+ *
+ * @return The request/reply ID, used to link replies with requests. Put the rid in your request.
+ *
+ * TODO: Make more generic and more specific:
+ * 1) MSIM_USER_LOOKUP_CB - make it for PERSIST_REPLY, not just user lookup
+ * 2) data - make it an MsimMessage?
+ */
+guint 
+msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb, 
+		gpointer data)
+{
+	guint rid;
+
+	rid = session->next_rid++;
+
+    g_hash_table_insert(session->user_lookup_cb, GUINT_TO_POINTER(rid), cb);
+    g_hash_table_insert(session->user_lookup_cb_data, GUINT_TO_POINTER(rid), data);
+
+	return rid;
+}
+
+/**
+ * Callback when connected. Sets up input handlers.
+ *
+ * @param data A PurpleConnection pointer.
+ * @param source File descriptor.
+ * @param error_message
+ */
+void 
+msim_connect_cb(gpointer data, gint source, const gchar *error_message)
+{
+    PurpleConnection *gc;
+    MsimSession *session;
+
+    g_return_if_fail(data != NULL);
+
+    gc = (PurpleConnection *)data;
+    session = (MsimSession *)gc->proto_data;
+
+    if (source < 0)
+    {
+        purple_connection_error(gc, _("Couldn't connect to host"));
+        purple_connection_error(gc, g_strdup_printf(
+					_("Couldn't connect to host: %s (%d)"), 
+                    error_message ? error_message : "no message given", 
+					source));
+        return;
+    }
+
+    session->fd = source; 
+
+    gc->inpa = purple_input_add(source, PURPLE_INPUT_READ, msim_input_cb, gc);
+
+
+}
+
+/* Session methods */
+
+/**
+ * Create a new MSIM session.
+ *
+ * @param acct The account to create the session from.
+ *
+ * @return Pointer to a new session. Free with msim_session_destroy.
+ */
+MsimSession *
+msim_session_new(PurpleAccount *acct)
+{
+    MsimSession *session;
+
+    g_return_val_if_fail(acct != NULL, NULL);
+
+    session = g_new0(MsimSession, 1);
+
+    session->magic = MSIM_SESSION_STRUCT_MAGIC;
+    session->account = acct;
+    session->gc = purple_account_get_connection(acct);
+	session->sesskey = 0;
+	session->userid = 0;
+	session->username = NULL;
+    session->fd = -1;
+
+	/* TODO: Remove. */
+    session->user_lookup_cb = g_hash_table_new_full(g_direct_hash, 
+			g_direct_equal, NULL, NULL);  /* do NOT free function pointers! (values) */
+    session->user_lookup_cb_data = g_hash_table_new_full(g_direct_hash, 
+			g_direct_equal, NULL, NULL);/* TODO: we don't know what the values are,
+											 they could be integers inside gpointers
+											 or strings, so I don't freed them.
+											 Figure this out, once free cache. */
+    session->rxoff = 0;
+    session->rxbuf = g_new0(gchar, MSIM_READ_BUF_SIZE);
+	session->next_rid = 1;
+    session->last_comm = time(NULL);
+    
+    return session;
+}
+
+/**
+ * Free a session.
+ *
+ * @param session The session to destroy.
+ */
+void 
+msim_session_destroy(MsimSession *session)
+{
+    g_return_if_fail(MSIM_SESSION_VALID(session));
+	
+    session->magic = -1;
+
+    g_free(session->rxbuf);
+	g_free(session->username);
+
+	/* TODO: Remove. */
+	g_hash_table_destroy(session->user_lookup_cb);
+	g_hash_table_destroy(session->user_lookup_cb_data);
+	
+    g_free(session);
+}
+                 
+/** 
+ * Close the connection.
+ * 
+ * @param gc The connection.
+ */
+void 
+msim_close(PurpleConnection *gc)
+{
+	MsimSession *session;
+
+	if (gc == NULL)
+		return;
+
+	session = (MsimSession *)gc->proto_data;
+	if (session == NULL)
+		return;
+
+	gc->proto_data = NULL;
+
+	if (!MSIM_SESSION_VALID(session))
+		return;
+
+    if (session->gc->inpa)
+		purple_input_remove(session->gc->inpa);
+
+    msim_session_destroy(session);
+}
+
+
+/**
+ * Check if a string is a userid (all numeric).
+ *
+ * @param user The user id, email, or name.
+ *
+ * @return TRUE if is userid, FALSE if not.
+ */
+gboolean 
+msim_is_userid(const gchar *user)
+{
+    g_return_val_if_fail(user != NULL, FALSE);
+
+    return strspn(user, "0123456789") == strlen(user);
+}
+
+/**
+ * Check if a string is an email address (contains an @).
+ *
+ * @param user The user id, email, or name.
+ *
+ * @return TRUE if is an email, FALSE if not.
+ *
+ * This function is not intended to be used as a generic
+ * means of validating email addresses, but to distinguish
+ * between a user represented by an email address from
+ * other forms of identification.
+ */ 
+gboolean 
+msim_is_email(const gchar *user)
+{
+    g_return_val_if_fail(user != NULL, FALSE);
+
+    return strchr(user, '@') != NULL;
+}
+
+
+/**
+ * Asynchronously lookup user information, calling callback when receive result.
+ *
+ * @param session
+ * @param user The user id, email address, or username. Not freed.
+ * @param cb Callback, called with user information when available.
+ * @param data An arbitray data pointer passed to the callback.
+ */
+/* TODO: change to not use callbacks */
+void 
+msim_lookup_user(MsimSession *session, const gchar *user, 
+		MSIM_USER_LOOKUP_CB cb, gpointer data)
+{
+    gchar *field_name;
+    guint rid, cmd, dsn, lid;
+
+    g_return_if_fail(MSIM_SESSION_VALID(session));
+    g_return_if_fail(user != NULL);
+    g_return_if_fail(cb != NULL);
+
+    purple_debug_info("msim", "msim_lookup_userid: "
+			"asynchronously looking up <%s>\n", user);
+
+	msim_msg_dump("msim_lookup_user: data=%s\n", (MsimMessage *)data);
+
+    /* Setup callback. Response will be associated with request using 'rid'. */
+    rid = msim_new_reply_callback(session, cb, data);
+
+    /* Send request */
+
+    cmd = MSIM_CMD_GET;
+
+    if (msim_is_userid(user))
+    {
+        field_name = "UserID";
+        dsn = MG_MYSPACE_INFO_BY_ID_DSN; 
+        lid = MG_MYSPACE_INFO_BY_ID_LID; 
+    } else if (msim_is_email(user)) {
+        field_name = "Email";
+        dsn = MG_MYSPACE_INFO_BY_STRING_DSN;
+        lid = MG_MYSPACE_INFO_BY_STRING_LID;
+    } else {
+        field_name = "UserName";
+        dsn = MG_MYSPACE_INFO_BY_STRING_DSN;
+        lid = MG_MYSPACE_INFO_BY_STRING_LID;
+    }
+
+
+	g_return_if_fail(msim_send(session,
+			"persist", MSIM_TYPE_INTEGER, 1,
+			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
+			"cmd", MSIM_TYPE_INTEGER, 1,
+			"dsn", MSIM_TYPE_INTEGER, dsn,
+			"uid", MSIM_TYPE_INTEGER, session->userid,
+			"lid", MSIM_TYPE_INTEGER, lid,
+			"rid", MSIM_TYPE_INTEGER, rid,
+			/* TODO: dictionary field type */
+			"body", MSIM_TYPE_STRING, 
+				g_strdup_printf("%s=%s", field_name, user),
+			NULL));
+} 
+
+
+/**
+ * Obtain the status text for a buddy.
+ *
+ * @param buddy The buddy to obtain status text for.
+ *
+ * @return Status text, or NULL if error. Caller g_free()'s.
+ *
+ */
+char *
+msim_status_text(PurpleBuddy *buddy)
+{
+    MsimSession *session;
+	const gchar *display_name, *headline;
+
+    g_return_val_if_fail(buddy != NULL, NULL);
+
+    session = (MsimSession *)buddy->account->gc->proto_data;
+    g_return_val_if_fail(MSIM_SESSION_VALID(session), NULL);
+
+	display_name = headline = NULL;
+
+	/* Retrieve display name and/or headline, depending on user preference. */
+    if (purple_account_get_bool(session->account, "show_display_name", TRUE))
+	{
+		display_name = purple_blist_node_get_string(&buddy->node, "DisplayName");
+	} 
+
+    if (purple_account_get_bool(session->account, "show_headline", FALSE))
+	{
+		headline = purple_blist_node_get_string(&buddy->node, "Headline");
+	}
+
+	/* Return appropriate combination of display name and/or headline, or neither. */
+
+	if (display_name && headline)
+		return g_strconcat(display_name, " ", headline, NULL);
+
+	if (display_name)
+		return g_strdup(display_name);
+
+	if (headline)
+		return g_strdup(headline);
+
+	return NULL;
+}
+
+/**
+ * Obtain the tooltip text for a buddy.
+ *
+ * @param buddy Buddy to obtain tooltip text on.
+ * @param user_info Variable modified to have the tooltip text.
+ * @param full TRUE if should obtain full tooltip text.
+ *
+ */
+void 
+msim_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, 
+		gboolean full)
+{
+	const gchar *str, *str2;
+	gint n;
+
+    g_return_if_fail(buddy != NULL);
+    g_return_if_fail(user_info != NULL);
+
+    if (PURPLE_BUDDY_IS_ONLINE(buddy))
+    {
+        MsimSession *session;
+
+        session = (MsimSession *)buddy->account->gc->proto_data;
+
+        g_return_if_fail(MSIM_SESSION_VALID(session));
+
+        /* TODO: if (full), do something different */
+		
+		/* Useful to identify the account the tooltip refers to. 
+		 *  Other prpls show this. */
+		str = purple_blist_node_get_string(&buddy->node, "UserName"); 
+		if (str)
+			purple_notify_user_info_add_pair(user_info, _("User Name"), str);
+
+		/* a/s/l...the vitals */	
+		n = purple_blist_node_get_int(&buddy->node, "Age");
+		if (n)
+			purple_notify_user_info_add_pair(user_info, _("Age"),
+					g_strdup_printf("%d", n));
+
+		str = purple_blist_node_get_string(&buddy->node, "Gender");
+		if (str)
+			purple_notify_user_info_add_pair(user_info, _("Gender"), str);
+
+		str = purple_blist_node_get_string(&buddy->node, "Location");
+		if (str)
+			purple_notify_user_info_add_pair(user_info, _("Location"), str);
+
+		/* Other information */
+ 		str = purple_blist_node_get_string(&buddy->node, "Headline");
+		if (str)
+			purple_notify_user_info_add_pair(user_info, _("Headline"), str);
+
+		str = purple_blist_node_get_string(&buddy->node, "BandName");
+		str2 = purple_blist_node_get_string(&buddy->node, "SongName");
+		if (str || str2)
+			purple_notify_user_info_add_pair(user_info, _("Song"), 
+                g_strdup_printf("%s - %s",
+					str ? str : _("Unknown Artist"),
+					str2 ? str2 : _("Unknown Song")));
+
+		n = purple_blist_node_get_int(&buddy->node, "TotalFriends");
+		if (n)
+			purple_notify_user_info_add_pair(user_info, _("Total Friends"),
+				g_strdup_printf("%d", n));
+
+    }
+}
+
+/** Callbacks called by Purple, to access this plugin. */
+PurplePluginProtocolInfo prpl_info =
+{
+	/* options */
+    OPT_PROTO_USE_POINTSIZE		/* specify font size in sane point size */
+	/* | OPT_PROTO_MAIL_CHECK - TODO: myspace will notify of mail */
+	/* | OPT_PROTO_IM_IMAGE - TODO: direct images. */	
+	,
+    NULL,              /* user_splits */
+    NULL,              /* protocol_options */
+    NO_BUDDY_ICONS,    /* icon_spec - TODO: eventually should add this */
+    msim_list_icon,    /* list_icon */
+    NULL,              /* list_emblems */
+    msim_status_text,  /* status_text */
+    msim_tooltip_text, /* tooltip_text */
+    msim_status_types, /* status_types */
+    NULL,              /* blist_node_menu */
+    NULL,              /* chat_info */
+    NULL,              /* chat_info_defaults */
+    msim_login,        /* login */
+    msim_close,        /* close */
+    msim_send_im,      /* send_im */
+    NULL,              /* set_info */
+    msim_send_typing,  /* send_typing */
+	msim_get_info, 	   /* get_info */
+    msim_set_status,   /* set_status */
+    msim_set_idle,     /* set_idle */
+    NULL,              /* change_passwd */
+    msim_add_buddy,    /* add_buddy */
+    NULL,              /* add_buddies */
+    msim_remove_buddy, /* remove_buddy */
+    NULL,              /* remove_buddies */
+    NULL,              /* add_permit */
+    NULL,              /* add_deny */
+    NULL,              /* rem_permit */
+    NULL,              /* rem_deny */
+    NULL,              /* set_permit_deny */
+    NULL,              /* join_chat */
+    NULL,              /* reject chat invite */
+    NULL,              /* get_chat_name */
+    NULL,              /* chat_invite */
+    NULL,              /* chat_leave */
+    NULL,              /* chat_whisper */
+    NULL,              /* chat_send */
+    NULL,              /* keepalive */
+    NULL,              /* register_user */
+    NULL,              /* get_cb_info */
+    NULL,              /* get_cb_away */
+    NULL,              /* alias_buddy */
+    NULL,              /* group_buddy */
+    NULL,              /* rename_group */
+    NULL,              /* buddy_free */
+    NULL,              /* convo_closed */
+    NULL,              /* normalize */
+    NULL,              /* set_buddy_icon */
+    NULL,              /* remove_group */
+    NULL,              /* get_cb_real_name */
+    NULL,              /* set_chat_topic */
+    NULL,              /* find_blist_chat */
+    NULL,              /* roomlist_get_list */
+    NULL,              /* roomlist_cancel */
+    NULL,              /* roomlist_expand_category */
+    NULL,              /* can_receive_file */
+    NULL,              /* send_file */
+    NULL,              /* new_xfer */
+    msim_offline_message, /* offline_message */
+    NULL,              /* whiteboard_prpl_ops */
+    msim_send_really_raw,     /* send_raw */
+    NULL,              /* roomlist_room_serialize */
+	NULL,			   /* _purple_reserved1 */
+	NULL,			   /* _purple_reserved2 */
+	NULL,			   /* _purple_reserved3 */
+	NULL 			   /* _purple_reserved4 */
+};
+
+
+
+/** Based on MSN's plugin info comments. */
+PurplePluginInfo info =
+{
+    PURPLE_PLUGIN_MAGIC,                                
+    PURPLE_MAJOR_VERSION,
+    PURPLE_MINOR_VERSION,
+    PURPLE_PLUGIN_PROTOCOL,                            /**< type           */
+    NULL,                                              /**< ui_requirement */
+    0,                                                 /**< flags          */
+    NULL,                                              /**< dependencies   */
+    PURPLE_PRIORITY_DEFAULT,                           /**< priority       */
+
+    "prpl-myspace",                                   /**< id             */
+    "MySpaceIM",                                      /**< name           */
+    "0.10",                                           /**< version        */
+                                                      /**  summary        */
+    "MySpaceIM Protocol Plugin",
+                                                      /**  description    */
+    "MySpaceIM Protocol Plugin",
+    "Jeff Connelly <jeff2@soc.pidgin.im>",         /**< author         */
+    "http://developer.pidgin.im/wiki/MySpaceIM/",     /**< homepage       */
+
+    msim_load,                                        /**< load           */
+    NULL,                                             /**< unload         */
+    NULL,                                             /**< destroy        */
+    NULL,                                             /**< ui_info        */
+    &prpl_info,                                       /**< extra_info     */
+    NULL,                                             /**< prefs_info     */
+
+    /* msim_actions */
+    NULL,
+
+	NULL,											  /**< reserved1      */
+	NULL,											  /**< reserved2      */
+	NULL,											  /**< reserved3      */
+	NULL 											  /**< reserved4      */
+};
+
+
+#ifdef MSIM_SELF_TEST
+/** Test functions.
+ * Used to test or try out the internal workings of msimprpl. If you're reading
+ * this code for the first time, these functions can be instructive in how
+ * msimprpl is architected.
+ */
+void 
+msim_test_all(void) 
+{
+	guint failures;
+
+
+	failures = 0;
+	failures += msim_test_xml();
+	failures += msim_test_msg();
+	failures += msim_test_escaping();
+
+	if (failures)
+	{
+		purple_debug_info("msim", "msim_test_all HAD FAILURES: %d\n", failures);
+	}
+	else
+	{
+		purple_debug_info("msim", "msim_test_all - all tests passed!\n");
+	}
+	exit(0);
+}
+
+int 
+msim_test_xml(void)
+{
+	gchar *msg_text;
+	xmlnode *root, *n;
+	guint failures;
+	char *s;
+	int len;
+
+	failures = 0;
+	
+	msg_text = "<p><f n=\"Arial\" h=\"12\">woo!</f>xxx<c v='black'>yyy</c></p>";
+
+	purple_debug_info("msim", "msim_test_xml: msg_text=%s\n", msg_text);
+
+	root = xmlnode_from_str(msg_text, -1);
+	if (!root)
+	{
+		purple_debug_info("msim", "there is no root\n");
+		exit(0);
+	}
+
+	purple_debug_info("msim", "root name=%s, child name=%s\n", root->name,
+			root->child->name);
+
+	purple_debug_info("msim", "last child name=%s\n", root->lastchild->name);
+	purple_debug_info("msim", "Root xml=%s\n", 
+			xmlnode_to_str(root, &len));
+	purple_debug_info("msim", "Child xml=%s\n", 
+			xmlnode_to_str(root->child, &len));
+	purple_debug_info("msim", "Lastchild xml=%s\n", 
+			xmlnode_to_str(root->lastchild, &len));
+	purple_debug_info("msim", "Next xml=%s\n", 
+			xmlnode_to_str(root->next, &len));
+	purple_debug_info("msim", "Next data=%s\n", 
+			xmlnode_get_data(root->next));
+	purple_debug_info("msim", "Child->next xml=%s\n", 
+			xmlnode_to_str(root->child->next, &len));
+
+	for (n = root->child; n; n = n->next)
+	{
+		if (n->name) 
+		{
+			purple_debug_info("msim", " ** n=%s\n",n->name);
+		} else {
+			purple_debug_info("msim", " ** n data=%s\n", n->data);
+		}
+	}
+
+	purple_debug_info("msim", "root data=%s, child data=%s, child 'h'=%s\n", 
+			xmlnode_get_data(root),
+			xmlnode_get_data(root->child),
+			xmlnode_get_attrib(root->child, "h"));
+
+
+	for (n = root->child;
+			n != NULL;
+			n = n->next)
+	{
+		purple_debug_info("msim", "next name=%s\n", n->name);
+	}
+
+	s = xmlnode_to_str(root, &len);
+	s[len] = 0;
+
+	purple_debug_info("msim", "str: %s\n", s);
+	g_free(s);
+
+	xmlnode_free(root);
+
+	exit(0);
+
+	return failures;
+}
+
+/** Test MsimMessage for basic functionality. */
+int 
+msim_test_msg(void)
+{
+	MsimMessage *msg, *msg_cloned;
+	gchar *packed, *packed_expected, *packed_cloned;
+	guint failures;
+
+	failures = 0;
+
+	purple_debug_info("msim", "\n\nTesting MsimMessage\n");
+	msg = msim_msg_new(FALSE);		/* Create a new, empty message. */
+
+	/* Append some new elements. */
+	msg = msim_msg_append(msg, "bx", MSIM_TYPE_BINARY, g_string_new_len(g_strdup("XXX"), 3));
+	msg = msim_msg_append(msg, "k1", MSIM_TYPE_STRING, g_strdup("v1"));
+	msg = msim_msg_append(msg, "k1", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(42));
+	msg = msim_msg_append(msg, "k1", MSIM_TYPE_STRING, g_strdup("v43"));
+	msg = msim_msg_append(msg, "k1", MSIM_TYPE_STRING, g_strdup("v52/xxx\\yyy"));
+	msg = msim_msg_append(msg, "k1", MSIM_TYPE_STRING, g_strdup("v7"));
+	msim_msg_dump("msg debug str=%s\n", msg);
+	packed = msim_msg_pack(msg);
+
+	purple_debug_info("msim", "msg packed=%s\n", packed);
+
+	packed_expected = "\\bx\\WFhY\\k1\\v1\\k1\\42\\k1"
+		"\\v43\\k1\\v52/1xxx/2yyy\\k1\\v7\\final\\";
+
+	if (0 != strcmp(packed, packed_expected))
+	{
+		purple_debug_info("msim", "!!!(%d), msim_msg_pack not what expected: %s != %s\n",
+				++failures, packed, packed_expected);
+	}
+
+
+	msg_cloned = msim_msg_clone(msg);
+	packed_cloned = msim_msg_pack(msg_cloned);
+
+	purple_debug_info("msim", "msg cloned=%s\n", packed_cloned);
+	if (0 != strcmp(packed, packed_cloned))
+	{
+		purple_debug_info("msim", "!!!(%d), msim_msg_pack on cloned message not equal to original: %s != %s\n",
+				++failures, packed_cloned, packed);
+	}
+
+	g_free(packed);
+	g_free(packed_cloned);
+	msim_msg_free(msg_cloned);
+	msim_msg_free(msg);
+
+	return failures;
+}
+
+/** Test protocol-level escaping/unescaping. */
+int 
+msim_test_escaping(void)
+{
+	guint failures;
+	gchar *raw, *escaped, *unescaped, *expected;
+
+	failures = 0;
+
+	purple_debug_info("msim", "\n\nTesting escaping\n");
+
+	raw = "hello/world\\hello/world";
+
+	escaped = msim_escape(raw);
+	purple_debug_info("msim", "msim_test_escaping: raw=%s, escaped=%s\n", raw, escaped);
+	expected = "hello/1world/2hello/1world";
+	if (0 != strcmp(escaped, expected))
+	{
+		purple_debug_info("msim", "!!!(%d), msim_escape failed: %s != %s\n",
+				++failures, escaped, expected);
+	}
+
+
+	unescaped = msim_unescape(escaped);
+	g_free(escaped);
+	purple_debug_info("msim", "msim_test_escaping: unescaped=%s\n", unescaped);
+	if (0 != strcmp(raw, unescaped))
+	{
+		purple_debug_info("msim", "!!!(%d), msim_unescape failed: %s != %s\n",
+				++failures, raw, unescaped);
+	}	
+
+	return failures;
+}
+#endif
+
+/** Initialize plugin. */
+void 
+init_plugin(PurplePlugin *plugin) 
+{
+	PurpleAccountOption *option;
+#ifdef MSIM_SELF_TEST
+	msim_test_all();
+#endif /* MSIM_SELF_TEST */
+
+	/* TODO: default to automatically try different ports. Make the user be
+	 * able to set the first port to try (like LastConnectedPort in Windows client).  */
+	option = purple_account_option_string_new(_("Connect server"), "server", MSIM_SERVER);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = purple_account_option_int_new(_("Connect port"), "port", MSIM_PORT);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = purple_account_option_bool_new(_("Show display name in status text"), "show_display_name", TRUE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = purple_account_option_bool_new(_("Show headline in status text"), "show_headline", TRUE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+}
+
+PURPLE_INIT_PLUGIN(myspace, init_plugin, info);