Index: protocols/bee.h
===================================================================
--- protocols/bee.h	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/bee.h	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -154,5 +154,5 @@
 G_MODULE_EXPORT void imcb_buddy_times(struct im_connection *ic, const char *handle, time_t login, time_t idle);
 /* Call when a handle says something. 'flags' and 'sent_at may be just 0. */
-G_MODULE_EXPORT void imcb_buddy_msg(struct im_connection *ic, const char *handle, char *msg, guint32 flags,
+G_MODULE_EXPORT void imcb_buddy_msg(struct im_connection *ic, const char *handle, const char *msg, guint32 flags,
                                     time_t sent_at);
 
Index: protocols/bee_user.c
===================================================================
--- protocols/bee_user.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/bee_user.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -68,4 +68,6 @@
 	}
 
+	bee->users = g_slist_remove(bee->users, bu);
+
 	g_free(bu->handle);
 	g_free(bu->fullname);
@@ -74,6 +76,4 @@
 	g_free(bu->status_msg);
 	g_free(bu);
-
-	bee->users = g_slist_remove(bee->users, bu);
 
 	return 1;
@@ -247,5 +247,5 @@
 }
 
-void imcb_buddy_msg(struct im_connection *ic, const char *handle, char *msg, uint32_t flags, time_t sent_at)
+void imcb_buddy_msg(struct im_connection *ic, const char *handle, const char *msg, uint32_t flags, time_t sent_at)
 {
 	bee_t *bee = ic->bee;
Index: protocols/jabber/Makefile
===================================================================
--- protocols/jabber/Makefile	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/jabber/Makefile	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -13,5 +13,5 @@
 
 # [SH] Program variables
-objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o s5bytestream.o sasl.o si.o
+objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o s5bytestream.o sasl.o si.o hipchat.o
 
 LFLAGS += -r
Index: protocols/jabber/conference.c
===================================================================
--- protocols/jabber/conference.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/jabber/conference.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -359,5 +359,5 @@
 
 	if (subject && chat) {
-		s = bud ? strchr(bud->ext_jid, '/') : NULL;
+		s = (bud && bud->ext_jid) ? strchr(bud->ext_jid, '/') : NULL;
 		if (s) {
 			*s = 0;
@@ -419,5 +419,5 @@
 	}
 	if (body && body->text_len > 0) {
-		s = strchr(bud->ext_jid, '/');
+		s = (bud->ext_jid) ? strchr(bud->ext_jid, '/') : NULL;
 		if (s) {
 			*s = 0;
Index: protocols/jabber/hipchat.c
===================================================================
--- protocols/jabber/hipchat.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
+++ protocols/jabber/hipchat.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -0,0 +1,93 @@
+/***************************************************************************\
+*                                                                           *
+*  BitlBee - An IRC to IM gateway                                           *
+*  Jabber module - HipChat specific functions                               *
+*                                                                           *
+*  Copyright 2015 Xamarin Inc                                               *
+*                                                                           *
+*  This program is free software; you can redistribute it and/or modify     *
+*  it under the terms of the GNU General Public License as published by     *
+*  the Free Software Foundation; either version 2 of the License, or        *
+*  (at your option) any later version.                                      *
+*                                                                           *
+*  This program is distributed in the hope that it will be useful,          *
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+*  GNU General Public License for more details.                             *
+*                                                                           *
+*  You should have received a copy of the GNU General Public License along  *
+*  with this program; if not, write to the Free Software Foundation, Inc.,  *
+*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
+*                                                                           *
+\***************************************************************************/
+
+#include "jabber.h"
+
+xt_status hipchat_handle_success(struct im_connection *ic, struct xt_node *node)
+{
+	struct jabber_data *jd = ic->proto_data;
+	char *sep, *jid;
+
+	jid = xt_find_attr(node, "jid");
+
+	sep = strchr(jid, '/');
+	if (sep) {
+		*sep = '\0';
+	}
+
+	jabber_set_me(ic, jid);
+	imcb_log(ic, "Setting Hipchat JID to %s", jid);
+
+	if (sep) {
+		*sep = '/';
+	}
+
+	/* Hipchat's auth doesn't expect a restart here */
+	jd->flags &= ~JFLAG_STREAM_RESTART;
+
+	if (!jabber_get_roster(ic) ||
+	    !jabber_iq_disco_server(ic) ||
+	    !jabber_get_hipchat_profile(ic)) {
+		return XT_ABORT;
+	}
+
+	return XT_HANDLED;
+}
+
+int jabber_get_hipchat_profile(struct im_connection *ic)
+{
+	struct jabber_data *jd = ic->proto_data;
+	struct xt_node *node;
+	int st;
+
+	imcb_log(ic, "Fetching hipchat profile for %s", jd->me);
+
+	node = xt_new_node("query", NULL, NULL);
+	xt_add_attr(node, "xmlns", XMLNS_HIPCHAT_PROFILE);
+	node = jabber_make_packet("iq", "get", jd->me, node);
+
+	jabber_cache_add(ic, node, jabber_parse_hipchat_profile);
+	st = jabber_write_packet(ic, node);
+
+	return st;
+}
+
+xt_status jabber_parse_hipchat_profile(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
+{
+	struct xt_node *query, *name_node;
+
+	if (!(query = xt_find_node(node->children, "query"))) {
+		imcb_log(ic, "Warning: Received NULL profile packet");
+		return XT_ABORT;
+	}
+
+	name_node = xt_find_node(query->children, "name");
+	if (!name_node) {
+		imcb_log(ic, "Warning: Can't find real name in profile. Joining groupchats will not be possible.");
+		return XT_ABORT;
+	}
+
+	set_setstr(&ic->acc->set, "display_name", name_node->text);
+	return XT_HANDLED;
+
+}
Index: protocols/jabber/iq.c
===================================================================
--- protocols/jabber/iq.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/jabber/iq.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -27,5 +27,4 @@
 static xt_status jabber_parse_roster(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
 static xt_status jabber_iq_display_vcard(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
-static int jabber_iq_disco_server(struct im_connection *ic);
 
 xt_status jabber_pkt_iq(struct xt_node *node, gpointer data)
@@ -374,4 +373,5 @@
 static xt_status jabber_parse_roster(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
 {
+	struct jabber_data *jd = ic->proto_data;
 	struct xt_node *query, *c;
 	int initial = (orig != NULL);
@@ -388,4 +388,5 @@
 		char *name = xt_find_attr(c, "name");
 		char *sub = xt_find_attr(c, "subscription");
+		char *mention_name = xt_find_attr(c, "mention_name");
 
 		if (jid && sub) {
@@ -396,4 +397,10 @@
 				if (name) {
 					imcb_rename_buddy(ic, jid, name);
+				}
+
+				/* This could also be used to set the full name as nick for fb/gtalk,
+				 * but i'm keeping the old (ugly?) default behavior just to be safe */
+				if (mention_name && (jd->flags & JFLAG_HIPCHAT)) {
+					imcb_buddy_nick_hint(ic, jid, mention_name);
 				}
 			} else if (strcmp(sub, "remove") == 0) {
@@ -855,5 +862,5 @@
                                                  struct xt_node *node, struct xt_node *orig);
 
-static int jabber_iq_disco_server(struct im_connection *ic)
+int jabber_iq_disco_server(struct im_connection *ic)
 {
 	struct xt_node *node, *iq;
Index: protocols/jabber/jabber.c
===================================================================
--- protocols/jabber/jabber.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/jabber/jabber.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -61,6 +61,4 @@
 	s = set_add(&acc->set, "activity_timeout", "600", set_eval_int, acc);
 
-	s = set_add(&acc->set, "oauth", "false", set_eval_oauth, acc);
-
 	s = set_add(&acc->set, "display_name", NULL, NULL, acc);
 
@@ -83,4 +81,10 @@
 	s = set_add(&acc->set, "server", NULL, set_eval_account, acc);
 	s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY | SET_NULL_OK;
+
+	if (strcmp(acc->prpl->name, "hipchat") == 0) {
+		set_setstr(&acc->set, "server", "chat.hipchat.com");
+	} else {
+		s = set_add(&acc->set, "oauth", "false", set_eval_oauth, acc);
+	}
 
 	s = set_add(&acc->set, "ssl", "false", set_eval_bool, acc);
@@ -121,4 +125,8 @@
 
 	jd->fd = jd->r_inpa = jd->w_inpa = -1;
+
+	if (strcmp(acc->prpl->name, "hipchat") == 0) {
+		jd->flags |= JFLAG_HIPCHAT;
+	}
 
 	if (jd->server == NULL) {
@@ -656,4 +664,5 @@
 {
 	struct prpl *ret = g_new0(struct prpl, 1);
+	struct prpl *hipchat = NULL;
 
 	ret->name = "jabber";
@@ -686,3 +695,8 @@
 
 	register_protocol(ret);
-}
+
+	/* Another one for hipchat, which has completely different logins */
+	hipchat = g_memdup(ret, sizeof(struct prpl));
+	hipchat->name = "hipchat";
+	register_protocol(hipchat);
+}
Index: protocols/jabber/jabber.h
===================================================================
--- protocols/jabber/jabber.h	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/jabber/jabber.h	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -48,4 +48,5 @@
 
 	JFLAG_GTALK =  0x100000,        /* Is Google Talk, as confirmed by iq discovery */
+	JFLAG_HIPCHAT = 0x200000,       /* Is hipchat, because prpl->name says so */
 
 	JFLAG_SASL_FB = 0x10000,        /* Trying Facebook authentication. */
@@ -235,4 +236,8 @@
 #define XMLNS_IBB          "http://jabber.org/protocol/ibb"                      /* XEP-0047 */
 
+/* Hipchat protocol extensions*/
+#define XMLNS_HIPCHAT         "http://hipchat.com"
+#define XMLNS_HIPCHAT_PROFILE "http://hipchat.com/protocol/profile"
+
 /* jabber.c */
 void jabber_connect(struct im_connection *ic);
@@ -249,4 +254,5 @@
 xt_status jabber_iq_query_server(struct im_connection *ic, char *jid, char *xmlns);
 void jabber_iq_version_send(struct im_connection *ic, struct jabber_buddy *bud, void *data);
+int jabber_iq_disco_server(struct im_connection *ic);
 
 /* si.c */
@@ -341,3 +347,8 @@
 void jabber_chat_invite(struct groupchat *c, char *who, char *message);
 
+/* hipchat.c */
+int jabber_get_hipchat_profile(struct im_connection *ic);
+xt_status jabber_parse_hipchat_profile(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
+xt_status hipchat_handle_success(struct im_connection *ic, struct xt_node *node);
+
 #endif
Index: protocols/jabber/jabber_util.c
===================================================================
--- protocols/jabber/jabber_util.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/jabber/jabber_util.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -566,4 +566,5 @@
 
 	if ((s = strchr(jid, '=')) == NULL) {
+		g_free(jid);
 		return NULL;
 	}
Index: protocols/jabber/s5bytestream.c
===================================================================
--- protocols/jabber/s5bytestream.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/jabber/s5bytestream.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -23,5 +23,4 @@
 
 #include "jabber.h"
-#include "sha1.h"
 #include "lib/ftutil.h"
 #include <poll.h>
@@ -42,5 +41,5 @@
 
 	/* SHA1( SID + Initiator JID + Target JID) */
-	char *pseudoadr;
+	char *pseudoaddr;
 
 	gint connect_timeout;
@@ -130,5 +129,5 @@
 	}
 
-	g_free(bt->pseudoadr);
+	g_free(bt->pseudoaddr);
 
 	while (bt->streamhosts) {
@@ -254,4 +253,29 @@
 }
 
+void jabber_bs_remove_events(struct bs_transfer *bt)
+{
+	struct jabber_transfer *tf = bt->tf;
+
+	if (tf->watch_out) {
+		b_event_remove(tf->watch_out);
+		tf->watch_out = 0;
+	}
+
+	if (tf->watch_in) {
+		b_event_remove(tf->watch_in);
+		tf->watch_in = 0;
+	}
+
+	if (tf->fd != -1) {
+		closesocket(tf->fd);
+		tf->fd = -1;
+	}
+
+	if (bt->connect_timeout) {
+		b_event_remove(bt->connect_timeout);
+		bt->connect_timeout = 0;
+	}
+}
+
 /* Bad luck */
 void jabber_bs_canceled(file_transfer_t *ft, char *reason)
@@ -260,4 +284,35 @@
 
 	imcb_log(tf->ic, "File transfer aborted: %s", reason);
+}
+
+static struct jabber_transfer *get_ft_by_sid(GSList *tflist, char *sid) {
+	GSList *l;
+	for (l = tflist; l; l = g_slist_next(l)) {
+		struct jabber_transfer *tft = l->data;
+		if ((strcmp(tft->sid, sid) == 0)) { 
+			return tft;
+		}
+	}
+	return NULL;
+}
+
+/* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value
+ * Returns a newly allocated string */
+static char *generate_pseudoaddr(char *sid, char *ini_jid, char *tgt_jid) {
+	char *contents = g_strconcat(sid, ini_jid, tgt_jid, NULL);
+	char *hash_hex = g_compute_checksum_for_string(G_CHECKSUM_SHA1, contents, -1);
+	g_free(contents);
+	return hash_hex;
+}
+
+static jabber_streamhost_t *jabber_streamhost_new(char *jid, char *host, int port)
+{
+	jabber_streamhost_t *sh = g_new0(jabber_streamhost_t, 1);
+	sh->jid = g_strdup(jid);
+	sh->host = g_strdup(host);
+	if (port) {
+		g_snprintf(sh->port, sizeof(sh->port), "%u", port);
+	}
+	return sh;
 }
 
@@ -270,13 +325,7 @@
 	struct jabber_data *jd = ic->proto_data;
 	struct jabber_transfer *tf = NULL;
-	GSList *tflist;
 	struct bs_transfer *bt;
 	GSList *shlist = NULL;
 	struct xt_node *shnode;
-
-	sha1_state_t sha;
-	char hash_hex[41];
-	unsigned char hash[20];
-	int i;
 
 	if (!(iq_id   = xt_find_attr(node, "id")) ||
@@ -303,9 +352,5 @@
 		    (port_s = xt_find_attr(shnode, "port")) &&
 		    (sscanf(port_s, "%d", &port) == 1)) {
-			jabber_streamhost_t *sh = g_new0(jabber_streamhost_t, 1);
-			sh->jid = g_strdup(jid);
-			sh->host = g_strdup(host);
-			sprintf(sh->port, "%u", port);
-			shlist = g_slist_append(shlist, sh);
+			shlist = g_slist_append(shlist, jabber_streamhost_new(jid, host, port));
 		}
 		shnode = shnode->next;
@@ -317,17 +362,5 @@
 	}
 
-	/* Let's see if we can find out what this bytestream should be for... */
-
-	for (tflist = jd->filetransfers; tflist; tflist = g_slist_next(tflist)) {
-		struct jabber_transfer *tft = tflist->data;
-		if ((strcmp(tft->sid, sid) == 0) &&
-		    (strcmp(tft->ini_jid, ini_jid) == 0) &&
-		    (strcmp(tft->tgt_jid, tgt_jid) == 0)) {
-			tf = tft;
-			break;
-		}
-	}
-
-	if (!tf) {
+	if (!(tf = get_ft_by_sid(jd->filetransfers, sid))) {
 		imcb_log(ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid);
 		return XT_HANDLED;
@@ -339,15 +372,4 @@
 
 	tf->ft->canceled = jabber_bs_canceled;
-
-	/* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */
-	sha1_init(&sha);
-	sha1_append(&sha, (unsigned char *) sid, strlen(sid));
-	sha1_append(&sha, (unsigned char *) ini_jid, strlen(ini_jid));
-	sha1_append(&sha, (unsigned char *) tgt_jid, strlen(tgt_jid));
-	sha1_finish(&sha, hash);
-
-	for (i = 0; i < 20; i++) {
-		sprintf(hash_hex + i * 2, "%02x", hash[i]);
-	}
 
 	bt = g_new0(struct bs_transfer, 1);
@@ -356,5 +378,5 @@
 	bt->sh = shlist->data;
 	bt->phase = BS_PHASE_CONNECT;
-	bt->pseudoadr = g_strdup(hash_hex);
+	bt->pseudoaddr = generate_pseudoaddr(sid, ini_jid, tgt_jid);
 	tf->streamhandle = bt;
 	tf->ft->free = jabber_bs_free_transfer;
@@ -451,5 +473,5 @@
 			.rsv = 0,
 			.atyp = 0x03,
-			.addrlen = strlen(bt->pseudoadr),
+			.addrlen = strlen(bt->pseudoaddr),
 			.port = 0
 		};
@@ -468,5 +490,5 @@
 
 		/* copy hash into connect message */
-		memcpy(socks5_connect.address, bt->pseudoadr, socks5_connect.addrlen);
+		memcpy(socks5_connect.address, bt->pseudoaddr, socks5_connect.addrlen);
 
 		ASSERTSOCKOP(send(fd, &socks5_connect, sizeof(struct socks5_message), 0), "Sending SOCKS5 Connect");
@@ -558,4 +580,5 @@
 	if (shlist && shlist->next) {
 		bt->sh = shlist->next->data;
+		jabber_bs_remove_events(bt);
 		return jabber_bs_recv_handshake(bt, -1, 0);
 	}
@@ -731,5 +754,4 @@
 	struct jabber_data *jd = ic->proto_data;
 	struct bs_transfer *bt;
-	GSList *tflist;
 	struct xt_node *c;
 	char *sid, *jid;
@@ -750,13 +772,5 @@
 	/* Let's see if we can find out what this bytestream should be for... */
 
-	for (tflist = jd->filetransfers; tflist; tflist = g_slist_next(tflist)) {
-		struct jabber_transfer *tft = tflist->data;
-		if ((strcmp(tft->sid, sid) == 0)) {
-			tf = tft;
-			break;
-		}
-	}
-
-	if (!tf) {
+	if (!(tf = get_ft_by_sid(jd->filetransfers, sid))) {
 		imcb_log(ic, "WARNING: Received SOCKS5 bytestream reply to unknown request");
 		return XT_HANDLED;
@@ -776,18 +790,5 @@
 		/* using a proxy, abort listen */
 
-		if (tf->watch_in) {
-			b_event_remove(tf->watch_in);
-			tf->watch_in = 0;
-		}
-
-		if (tf->fd != -1) {
-			closesocket(tf->fd);
-			tf->fd = -1;
-		}
-
-		if (bt->connect_timeout) {
-			b_event_remove(bt->connect_timeout);
-			bt->connect_timeout = 0;
-		}
+		jabber_bs_remove_events(bt);
 
 		GSList *shlist;
@@ -838,5 +839,4 @@
 {
 	char *sid;
-	GSList *tflist;
 	struct jabber_transfer *tf = NULL;
 	struct xt_node *query;
@@ -846,13 +846,5 @@
 	sid = xt_find_attr(query, "sid");
 
-	for (tflist = jd->filetransfers; tflist; tflist = g_slist_next(tflist)) {
-		struct jabber_transfer *tft = tflist->data;
-		if ((strcmp(tft->sid, sid) == 0)) {
-			tf = tft;
-			break;
-		}
-	}
-
-	if (!tf) {
+	if (!(tf = get_ft_by_sid(jd->filetransfers, sid))) {
 		imcb_log(ic, "WARNING: Received SOCKS5 bytestream activation for unknown stream");
 		return XT_HANDLED;
@@ -883,8 +875,6 @@
 	*port++ = '\0';
 
-	sh = g_new0(jabber_streamhost_t, 1);
-	sh->jid = g_strdup(jid);
-	sh->host = g_strdup(host);
-	g_snprintf(sh->port, sizeof(sh->port), "%s", port);
+	sh = jabber_streamhost_new(jid, host, 0);
+	strncpy(sh->port, port, sizeof(sh->port));
 
 	return sh;
@@ -910,8 +900,6 @@
 		if (strcmp(proxy, "<local>") == 0) {
 			if ((tf->fd = ft_listen(&tf->saddr, host, port, jd->fd, FALSE, &errmsg)) != -1) {
-				sh = g_new0(jabber_streamhost_t, 1);
-				sh->jid = g_strdup(tf->ini_jid);
-				sh->host = g_strdup(host);
-				g_snprintf(sh->port, sizeof(sh->port), "%s", port);
+				sh = jabber_streamhost_new(tf->ini_jid, host, 0);
+				strncpy(sh->port, port, sizeof(sh->port));
 				bt->streamhosts = g_slist_append(bt->streamhosts, sh);
 
@@ -948,24 +936,9 @@
 {
 	struct bs_transfer *bt;
-	sha1_state_t sha;
-	char hash_hex[41];
-	unsigned char hash[20];
-	int i, ret;
-
-	/* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */
-	sha1_init(&sha);
-	sha1_append(&sha, (unsigned char *) tf->sid, strlen(tf->sid));
-	sha1_append(&sha, (unsigned char *) tf->ini_jid, strlen(tf->ini_jid));
-	sha1_append(&sha, (unsigned char *) tf->tgt_jid, strlen(tf->tgt_jid));
-	sha1_finish(&sha, hash);
-
-	for (i = 0; i < 20; i++) {
-		sprintf(hash_hex + i * 2, "%02x", hash[i]);
-	}
 
 	bt = g_new0(struct bs_transfer, 1);
 	bt->tf = tf;
 	bt->phase = BS_PHASE_CONNECT;
-	bt->pseudoadr = g_strdup(hash_hex);
+	bt->pseudoaddr = generate_pseudoaddr(tf->sid, tf->ini_jid, tf->tgt_jid);
 	tf->streamhandle = bt;
 	tf->ft->free = jabber_bs_free_transfer;
@@ -974,7 +947,5 @@
 	jabber_si_set_proxies(bt);
 
-	ret = jabber_bs_send_request(tf, bt->streamhosts);
-
-	return ret;
+	return jabber_bs_send_request(tf, bt->streamhosts);
 }
 
@@ -1139,5 +1110,5 @@
 			                       socks5_connect.atyp);
 		}
-		if (!(memcmp(socks5_connect.address, bt->pseudoadr, 40) == 0)) {
+		if (!(memcmp(socks5_connect.address, bt->pseudoaddr, 40) == 0)) {
 			return jabber_bs_abort(bt, "SOCKS5 Connect message contained wrong digest");
 		}
Index: protocols/jabber/sasl.c
===================================================================
--- protocols/jabber/sasl.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/jabber/sasl.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -42,5 +42,5 @@
 	"https://www.facebook.com/dialog/oauth",
 	"https://graph.facebook.com/oauth/access_token",
-	"http://www.bitlbee.org/main.php/Facebook/oauth2.html",
+	"https://www.bitlbee.org/main.php/Facebook/oauth2.html",
 	"offline_access,xmpp_login",
 	"126828914005625",
@@ -55,5 +55,5 @@
 	char *s;
 	int sup_plain = 0, sup_digest = 0, sup_gtalk = 0, sup_fb = 0;
-	int want_oauth = FALSE;
+	int want_oauth = FALSE, want_hipchat = FALSE;
 	GString *mechs;
 
@@ -75,4 +75,5 @@
 
 	want_oauth = set_getbool(&ic->acc->set, "oauth");
+	want_hipchat = (jd->flags & JFLAG_HIPCHAT);
 
 	mechs = g_string_new("");
@@ -111,5 +112,9 @@
 
 	reply = xt_new_node("auth", NULL, NULL);
-	xt_add_attr(reply, "xmlns", XMLNS_SASL);
+	if (!want_hipchat) {
+		xt_add_attr(reply, "xmlns", XMLNS_SASL);
+	} else {
+		xt_add_attr(reply, "xmlns", XMLNS_HIPCHAT);
+	}
 
 	if (sup_gtalk && want_oauth) {
@@ -143,13 +148,31 @@
 	} else if (sup_plain) {
 		int len;
-
-		xt_add_attr(reply, "mechanism", "PLAIN");
+		GString *gs;
+		char *username;
+
+		if (!want_hipchat) {
+			xt_add_attr(reply, "mechanism", "PLAIN");
+			username = jd->username;
+		} else {
+			username = jd->me;
+		}
+
+		/* set an arbitrary initial size to avoid reallocations */
+		gs = g_string_sized_new(128);
 
 		/* With SASL PLAIN in XMPP, the text should be b64(\0user\0pass) */
-		len = strlen(jd->username) + strlen(ic->acc->pass) + 2;
-		s = g_malloc(len + 1);
-		s[0] = 0;
-		strcpy(s + 1, jd->username);
-		strcpy(s + 2 + strlen(jd->username), ic->acc->pass);
+		g_string_append_c(gs, '\0');
+		g_string_append(gs, username);
+		g_string_append_c(gs, '\0');
+		g_string_append(gs, ic->acc->pass);
+		if (want_hipchat) {
+			/* Hipchat's variation adds \0resource at the end */
+			g_string_append_c(gs, '\0');
+			g_string_append(gs, set_getstr(&ic->acc->set, "resource"));
+		}
+
+		len = gs->len;
+		s = g_string_free(gs, FALSE);
+
 		reply->text = base64_encode((unsigned char *) s, len);
 		reply->text_len = strlen(reply->text);
@@ -397,4 +420,8 @@
 		imcb_log(ic, "Authentication finished");
 		jd->flags |= JFLAG_AUTHENTICATED | JFLAG_STREAM_RESTART;
+
+		if (jd->flags & JFLAG_HIPCHAT) {
+			return hipchat_handle_success(ic, node);
+		}
 	} else if (strcmp(node->name, "failure") == 0) {
 		imcb_error(ic, "Authentication failure");
Index: protocols/msn/Makefile
===================================================================
--- protocols/msn/Makefile	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/msn/Makefile	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -13,5 +13,5 @@
 
 # [SH] Program variables
-objects = msn.o msn_util.o ns.o sb.o soap.o tables.o
+objects = msn.o msn_util.o ns.o soap.o tables.o gw.o
 
 LFLAGS += -r
Index: protocols/msn/gw.c
===================================================================
--- protocols/msn/gw.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
+++ protocols/msn/gw.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -0,0 +1,196 @@
+#include "bitlbee.h"
+#include "lib/http_client.h"
+#include "msn.h"
+
+#define GATEWAY_HOST "geo.gateway.messenger.live.com"
+#define GATEWAY_PORT 443
+
+#define REQUEST_TEMPLATE \
+	"POST /gateway/gateway.dll?SessionID=%s&%s HTTP/1.1\r\n" \
+	"Host: %s\r\n" \
+	"Content-Length: %zd\r\n" \
+	"\r\n" \
+	"%s"
+
+static gboolean msn_gw_poll_timeout(gpointer data, gint source, b_input_condition cond);
+
+struct msn_gw *msn_gw_new(struct im_connection *ic)
+{
+	struct msn_gw *gw = g_new0(struct msn_gw, 1);
+	gw->last_host = g_strdup(GATEWAY_HOST);
+	gw->port = GATEWAY_PORT;
+	gw->ssl = (GATEWAY_PORT == 443);
+	gw->poll_timeout = -1;
+	gw->ic = ic;
+	gw->md = ic->proto_data;
+	gw->in = g_byte_array_new();
+	gw->out = g_byte_array_new();
+	return gw;
+}
+
+void msn_gw_free(struct msn_gw *gw)
+{
+	if (gw->poll_timeout != -1) {
+		b_event_remove(gw->poll_timeout);
+	}
+	g_byte_array_free(gw->in, TRUE);
+	g_byte_array_free(gw->out, TRUE);
+	g_free(gw->session_id);
+	g_free(gw->last_host);
+	g_free(gw);
+}
+
+static struct msn_gw *msn_gw_from_ic(struct im_connection *ic)
+{
+	if (g_slist_find(msn_connections, ic) == NULL) {
+		return NULL;
+	} else {
+		struct msn_data *md = ic->proto_data;
+		return md->gw;
+	}
+}
+
+static gboolean msn_gw_parse_session_header(struct msn_gw *gw, char *value)
+{
+	int i;
+	char **subvalues;
+	gboolean closed = FALSE;
+
+	subvalues = g_strsplit(value, "; ", 0);
+
+	for (i = 0; subvalues[i]; i++) {
+		if (strcmp(subvalues[i], "Session=close") == 0) {
+			/* gateway closed, signal the death of the socket */
+			closed = TRUE;
+		} else if (g_str_has_prefix(subvalues[i], "SessionID=")) {
+			/* copy the part after the = to session_id*/
+			g_free(gw->session_id);
+			gw->session_id = g_strdup(subvalues[i] + 10);
+		}
+	}
+
+	g_strfreev(subvalues);
+
+	return !closed;
+}
+
+void msn_gw_callback(struct http_request *req)
+{
+	struct msn_gw *gw;
+	char *value;
+
+	if (!(gw = msn_gw_from_ic(req->data))) {
+		return;
+	}
+
+	gw->waiting = FALSE;
+	gw->polling = FALSE;
+
+	if (getenv("BITLBEE_DEBUG")) {
+		fprintf(stderr, "\n\x1b[90mHTTP:%s\n", req->reply_body);
+		fprintf(stderr, "\n\x1b[97m\n");
+	}
+
+	if (req->status_code != 200) {
+		gw->callback(gw->md, -1, B_EV_IO_READ);
+		return;
+	}
+
+	if ((value = get_rfc822_header(req->reply_headers, "X-MSN-Messenger", 0))) {
+		if (!msn_gw_parse_session_header(gw, value)) {
+			gw->callback(gw->md, -1, B_EV_IO_READ);
+			g_free(value);
+			return;
+		}
+		g_free(value);
+	}
+	
+	if ((value = get_rfc822_header(req->reply_headers, "X-MSN-Host", 0))) {
+		g_free(gw->last_host);
+		gw->last_host = value; /* transfer */
+	}
+
+	if (req->body_size) {
+		g_byte_array_append(gw->in, (const guint8 *) req->reply_body, req->body_size);
+		gw->callback(gw->md, -1, B_EV_IO_READ);
+	}
+
+	if (gw->poll_timeout != -1) {
+		b_event_remove(gw->poll_timeout);
+	}
+	gw->poll_timeout = b_timeout_add(500, msn_gw_poll_timeout, gw->ic);
+
+}
+
+void msn_gw_dorequest(struct msn_gw *gw, char *args)
+{
+	char *request = NULL;
+	char *body = NULL;
+	size_t bodylen = 0;
+
+	if (gw->out) {
+		bodylen = gw->out->len;
+		g_byte_array_append(gw->out, (guint8 *) "", 1); /* nullnullnull */
+		body = (char *) g_byte_array_free(gw->out, FALSE);
+		gw->out = g_byte_array_new();
+	}
+
+	if (!bodylen && !args) {
+		args = "Action=poll&Lifespan=60";
+		gw->polling = TRUE;
+	}
+
+	request = g_strdup_printf(REQUEST_TEMPLATE,
+		gw->session_id ? : "", args ? : "", gw->last_host, bodylen, body ? : "");
+
+	http_dorequest(gw->last_host, gw->port, gw->ssl, request, msn_gw_callback, gw->ic);
+	gw->open = TRUE;
+	gw->waiting = TRUE;
+
+	g_free(body);
+	g_free(request);
+}
+
+void msn_gw_open(struct msn_gw *gw)
+{
+	msn_gw_dorequest(gw, "Action=open&Server=NS");
+}
+
+static gboolean msn_gw_poll_timeout(gpointer data, gint source, b_input_condition cond)
+{
+	struct msn_gw *gw;
+
+	if (!(gw = msn_gw_from_ic(data))) {
+		return FALSE;
+	}
+
+	gw->poll_timeout = -1;
+	if (!gw->waiting) {
+		msn_gw_dorequest(gw, NULL);
+	}
+	return FALSE;
+}
+
+ssize_t msn_gw_read(struct msn_gw *gw, char **buf)
+{
+	size_t bodylen;
+	if (!gw->open) {
+		return 0;
+	}
+
+	bodylen = gw->in->len;
+	g_byte_array_append(gw->in, (guint8 *) "", 1); /* nullnullnull */
+	*buf = (char *) g_byte_array_free(gw->in, FALSE);
+	gw->in = g_byte_array_new();
+	return bodylen;
+}
+
+void msn_gw_write(struct msn_gw *gw, char *buf, size_t len)
+{
+	g_byte_array_append(gw->out, (const guint8 *) buf, len);
+	if (!gw->open) {
+		msn_gw_open(gw);
+	} else if (gw->polling || !gw->waiting) {
+		msn_gw_dorequest(gw, NULL);
+	}
+}
Index: protocols/msn/invitation.c
===================================================================
--- protocols/msn/invitation.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ 	(revision )
@@ -1,665 +1,0 @@
-/********************************************************************\
-* BitlBee -- An IRC to other IM-networks gateway                     *
-*                                                                    *
-* Copyright 2008 Uli Meis					     *
-* Copyright 2006 Marijn Kruisselbrink and others                     *
-\********************************************************************/
-
-/* MSN module - File transfer support             */
-
-/*
- 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 with
- the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
- if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
- Fifth Floor, Boston, MA  02110-1301  USA
- */
-
-#include "bitlbee.h"
-#include "invitation.h"
-#include "msn.h"
-#include "lib/ftutil.h"
-
-#ifdef debug
-#undef debug
-#endif
-#define debug(msg ...) log_message(LOGLVL_INFO, msg)
-
-static void msn_ftp_free(file_transfer_t *file);
-static void msn_ftpr_accept(file_transfer_t *file);
-static void msn_ftp_finished(file_transfer_t *file);
-static void msn_ftp_canceled(file_transfer_t *file, char *reason);
-static gboolean msn_ftpr_write_request(file_transfer_t *file);
-
-static gboolean msn_ftp_connected(gpointer data, gint fd, b_input_condition cond);
-static gboolean msn_ftp_read(gpointer data, gint fd, b_input_condition cond);
-gboolean msn_ftps_write(file_transfer_t *file, char *buffer, unsigned int len);
-
-/*
- * Vararg wrapper for imcb_file_canceled().
- */
-gboolean msn_ftp_abort(file_transfer_t *file, char *format, ...)
-{
-	va_list params;
-
-	va_start(params, format);
-	char error[128];
-
-	if (vsnprintf(error, 128, format, params) < 0) {
-		sprintf(error, "internal error parsing error string (BUG)");
-	}
-	va_end(params);
-	imcb_file_canceled(file, error);
-	return FALSE;
-}
-
-/* very useful */
-#define ASSERTSOCKOP(op, msg) \
-	if ((op) == -1) { \
-		return msn_ftp_abort(file, msg ": %s", strerror(errno)); }
-
-void msn_ftp_invitation_cmd(struct im_connection *ic, char *who, int cookie, char *icmd,
-                            char *trailer)
-{
-	struct msn_message *m = g_new0(struct msn_message, 1);
-
-	m->text = g_strdup_printf("%s"
-	                          "Invitation-Command: %s\r\n"
-	                          "Invitation-Cookie: %u\r\n"
-	                          "%s",
-	                          MSN_INVITE_HEADERS,
-	                          icmd,
-	                          cookie,
-	                          trailer);
-
-	m->who = g_strdup(who);
-
-	msn_sb_write_msg(ic, m);
-}
-
-void msn_ftp_cancel_invite(struct im_connection *ic, char *who,  int cookie, char *code)
-{
-	char buf[64];
-
-	g_snprintf(buf, sizeof(buf), "Cancel-Code: %s\r\n", code);
-	msn_ftp_invitation_cmd(ic, who, cookie, "CANCEL", buf);
-}
-
-void msn_ftp_transfer_request(struct im_connection *ic, file_transfer_t *file, char *who)
-{
-	unsigned int cookie = time(NULL);   /* TODO: randomize */
-	char buf[2048];
-
-	msn_filetransfer_t *msn_file = g_new0(msn_filetransfer_t, 1);
-
-	file->data = msn_file;
-	file->free = msn_ftp_free;
-	file->canceled = msn_ftp_canceled;
-	file->write = msn_ftps_write;
-	msn_file->md = ic->proto_data;
-	msn_file->invite_cookie = cookie;
-	msn_file->handle = g_strdup(who);
-	msn_file->dcc = file;
-	msn_file->md->filetransfers = g_slist_prepend(msn_file->md->filetransfers, msn_file->dcc);
-	msn_file->fd = -1;
-	msn_file->sbufpos = 3;
-
-	g_snprintf(buf, sizeof(buf),
-	           "Application-Name: File Transfer\r\n"
-	           "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n"
-	           "Application-File: %s\r\n"
-	           "Application-FileSize: %zd\r\n",
-	           file->file_name,
-	           file->file_size);
-
-	msn_ftp_invitation_cmd(msn_file->md->ic, msn_file->handle, cookie, "INVITE", buf);
-
-	imcb_file_recv_start(file);
-}
-
-void msn_invitation_invite(struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen)
-{
-	char *itype = msn_findheader(body, "Application-GUID:", blen);
-	char *name, *size, *invitecookie, *reject = NULL;
-	user_t *u;
-	size_t isize;
-	file_transfer_t *file;
-
-	if (!itype || strcmp(itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}") != 0) {
-		/* Don't know what that is - don't care */
-		char *iname = msn_findheader(body, "Application-Name:", blen);
-		imcb_log(sb->ic, "Received unknown MSN invitation %s (%s) from %s",
-		         itype ? : "with no GUID", iname ? iname : "no application name", handle);
-		g_free(iname);
-		reject = "REJECT_NOT_INSTALLED";
-	} else if (
-	        !(name = msn_findheader(body, "Application-File:", blen)) ||
-	        !(size = msn_findheader(body, "Application-FileSize:", blen)) ||
-	        !(invitecookie = msn_findheader(body, "Invitation-Cookie:", blen)) ||
-	        !(isize = atoll(size))) {
-		imcb_log(sb->ic, "Received corrupted transfer request from %s"
-		         "(name=%s, size=%s, invitecookie=%s)",
-		         handle, name, size, invitecookie);
-		reject = "REJECT";
-	} else if (!(u = user_findhandle(sb->ic, handle))) {
-		imcb_log(sb->ic, "Error in parsing transfer request, User '%s'"
-		         "is not in contact list", handle);
-		reject = "REJECT";
-	} else if (!(file = imcb_file_send_start(sb->ic, handle, name, isize))) {
-		imcb_log(sb->ic, "Error initiating transfer for request from %s for %s",
-		         handle, name);
-		reject = "REJECT";
-	} else {
-		msn_filetransfer_t *msn_file = g_new0(msn_filetransfer_t, 1);
-		file->data = msn_file;
-		file->accept = msn_ftpr_accept;
-		file->free = msn_ftp_free;
-		file->finished = msn_ftp_finished;
-		file->canceled = msn_ftp_canceled;
-		file->write_request = msn_ftpr_write_request;
-		msn_file->md = sb->ic->proto_data;
-		msn_file->invite_cookie = cookie;
-		msn_file->handle = g_strdup(handle);
-		msn_file->dcc = file;
-		msn_file->md->filetransfers = g_slist_prepend(msn_file->md->filetransfers, msn_file->dcc);
-		msn_file->fd = -1;
-	}
-
-	if (reject) {
-		msn_ftp_cancel_invite(sb->ic, sb->who, cookie, reject);
-	}
-
-	g_free(name);
-	g_free(size);
-	g_free(invitecookie);
-	g_free(itype);
-}
-
-msn_filetransfer_t* msn_find_filetransfer(struct msn_data *md, unsigned int cookie, char *handle)
-{
-	GSList *l;
-
-	for (l = md->filetransfers; l; l = l->next) {
-		msn_filetransfer_t *file = ((file_transfer_t *) l->data)->data;
-		if (file->invite_cookie == cookie && strcmp(handle, file->handle) == 0) {
-			return file;
-		}
-	}
-	return NULL;
-}
-
-gboolean msn_ftps_connected(gpointer data, gint fd, b_input_condition cond)
-{
-	file_transfer_t *file = data;
-	msn_filetransfer_t *msn_file = file->data;
-	struct sockaddr_storage clt_addr;
-	socklen_t ssize = sizeof(clt_addr);
-
-	debug("Connected to MSNFTP client");
-
-	ASSERTSOCKOP(msn_file->fd = accept(fd, (struct sockaddr *) &clt_addr, &ssize), "Accepting connection");
-
-	closesocket(fd);
-	fd = msn_file->fd;
-	sock_make_nonblocking(fd);
-
-	msn_file->r_event_id = b_input_add(fd, B_EV_IO_READ, msn_ftp_read, file);
-
-	return FALSE;
-}
-
-void msn_invitations_accept(msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie,
-                            char *body, int blen)
-{
-	file_transfer_t *file = msn_file->dcc;
-	char buf[1024];
-	unsigned int acookie = time(NULL);
-	char host[HOST_NAME_MAX + 1];
-	char port[6];
-	char *errmsg;
-
-	msn_file->auth_cookie = acookie;
-
-	if ((msn_file->fd = ft_listen(NULL, host, port, FALSE, &errmsg)) == -1) {
-		msn_ftp_abort(file, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s",
-		              errmsg);
-		return;
-	}
-
-	msn_file->r_event_id = b_input_add(msn_file->fd, B_EV_IO_READ, msn_ftps_connected, file);
-
-	g_snprintf(buf, sizeof(buf),
-	           "IP-Address: %s\r\n"
-	           "Port: %s\r\n"
-	           "AuthCookie: %d\r\n"
-	           "Launch-Application: FALSE\r\n"
-	           "Request-Data: IP-Address:\r\n\r\n",
-	           host,
-	           port,
-	           msn_file->auth_cookie);
-
-	msn_ftp_invitation_cmd(msn_file->md->ic, handle, msn_file->invite_cookie, "ACCEPT", buf);
-}
-
-void msn_invitationr_accept(msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie,
-                            char *body, int blen)
-{
-	file_transfer_t *file = msn_file->dcc;
-	char *authcookie, *ip, *port;
-
-	if (!(authcookie = msn_findheader(body, "AuthCookie:", blen)) ||
-	    !(ip = msn_findheader(body, "IP-Address:", blen)) ||
-	    !(port = msn_findheader(body, "Port:", blen))) {
-		msn_ftp_abort(file, "Received invalid accept reply");
-	} else if (
-	        (msn_file->fd = proxy_connect(ip, atoi(port), msn_ftp_connected, file))
-	        < 0) {
-		msn_ftp_abort(file, "Error connecting to MSN client");
-	} else {
-		msn_file->auth_cookie = strtoul(authcookie, NULL, 10);
-	}
-
-	g_free(authcookie);
-	g_free(ip);
-	g_free(port);
-}
-
-void msn_invitation_accept(struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen)
-{
-	msn_filetransfer_t *msn_file = msn_find_filetransfer(sb->ic->proto_data, cookie, handle);
-	file_transfer_t *file = msn_file ? msn_file->dcc : NULL;
-
-	if (!msn_file) {
-		imcb_log(sb->ic, "Received invitation ACCEPT message for unknown invitation (already aborted?)");
-	} else if (file->sending) {
-		msn_invitations_accept(msn_file, sb, handle, cookie, body, blen);
-	} else {
-		msn_invitationr_accept(msn_file, sb, handle, cookie, body, blen);
-	}
-}
-
-void msn_invitation_cancel(struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen)
-{
-	msn_filetransfer_t *msn_file = msn_find_filetransfer(sb->ic->proto_data, cookie, handle);
-
-	if (!msn_file) {
-		imcb_log(sb->ic, "Received invitation CANCEL message for unknown invitation (already aborted?)");
-	} else {
-		msn_ftp_abort(msn_file->dcc, msn_findheader(body, "Cancel-Code:", blen));
-	}
-}
-
-int msn_ftp_write(file_transfer_t *file, char *format, ...)
-{
-	msn_filetransfer_t *msn_file = file->data;
-	va_list params;
-	int st;
-	char *s;
-
-	va_start(params, format);
-	s = g_strdup_vprintf(format, params);
-	va_end(params);
-
-	st = write(msn_file->fd, s, strlen(s));
-	if (st != strlen(s)) {
-		return msn_ftp_abort(file, "Error sending data over MSNFTP connection: %s",
-		                     strerror(errno));
-	}
-
-	g_free(s);
-	return 1;
-}
-
-gboolean msn_ftp_connected(gpointer data, gint fd, b_input_condition cond)
-{
-	file_transfer_t *file = data;
-	msn_filetransfer_t *msn_file = file->data;
-
-	debug("Connected to MSNFTP server, starting authentication");
-	if (!msn_ftp_write(file, "VER MSNFTP\r\n")) {
-		return FALSE;
-	}
-
-	sock_make_nonblocking(msn_file->fd);
-	msn_file->r_event_id = b_input_add(msn_file->fd, B_EV_IO_READ, msn_ftp_read, file);
-
-	return FALSE;
-}
-
-gboolean msn_ftp_handle_command(file_transfer_t *file, char* line)
-{
-	msn_filetransfer_t *msn_file = file->data;
-	char **cmd = msn_linesplit(line);
-	int count = 0;
-
-	if (cmd[0]) {
-		while (cmd[++count]) {
-			;
-		}
-	}
-
-	if (count < 1) {
-		return msn_ftp_abort(file, "Missing command in MSNFTP communication");
-	}
-
-	if (strcmp(cmd[0], "VER") == 0) {
-		if (strcmp(cmd[1], "MSNFTP") != 0) {
-			return msn_ftp_abort(file, "Unsupported filetransfer protocol: %s", cmd[1]);
-		}
-		if (file->sending) {
-			msn_ftp_write(file, "VER MSNFTP\r\n");
-		} else {
-			msn_ftp_write(file, "USR %s %u\r\n", msn_file->md->ic->acc->user, msn_file->auth_cookie);
-		}
-	} else if (strcmp(cmd[0], "FIL") == 0) {
-		if (strtoul(cmd[1], NULL, 10) != file->file_size) {
-			return msn_ftp_abort(file,
-			                     "FIL reply contains a different file size than the size in the invitation");
-		}
-		msn_ftp_write(file, "TFR\r\n");
-		msn_file->status |= MSN_TRANSFER_RECEIVING;
-	} else if (strcmp(cmd[0], "USR") == 0) {
-		if ((strcmp(cmd[1], msn_file->handle) != 0) ||
-		    (strtoul(cmd[2], NULL, 10) != msn_file->auth_cookie)) {
-			msn_ftp_abort(file, "Authentication failed. "
-			              "Expected handle: %s (got %s), cookie: %u (got %s)",
-			              msn_file->handle, cmd[1],
-			              msn_file->auth_cookie, cmd[2]);
-		}
-		msn_ftp_write(file, "FIL %zu\r\n", file->file_size);
-	} else if (strcmp(cmd[0], "TFR") == 0) {
-		file->write_request(file);
-	} else if (strcmp(cmd[0], "BYE") == 0) {
-		unsigned int retcode = count > 1 ? atoi(cmd[1]) : 1;
-
-		if ((retcode == 16777989) || (retcode == 16777987)) {
-			imcb_file_finished(file);
-		} else if (retcode == 2147942405) {
-			imcb_file_canceled(file, "Failure: receiver is out of disk space");
-		} else if (retcode == 2164261682) {
-			imcb_file_canceled(file, "Failure: receiver cancelled the transfer");
-		} else if (retcode == 2164261683) {
-			imcb_file_canceled(file, "Failure: sender has cancelled the transfer");
-		} else if (retcode == 2164261694) {
-			imcb_file_canceled(file, "Failure: connection is blocked");
-		} else {
-			char buf[128];
-
-			sprintf(buf, "Failure: unknown BYE code: %d", retcode);
-			imcb_file_canceled(file, buf);
-		}
-	} else if (strcmp(cmd[0], "CCL") == 0) {
-		imcb_file_canceled(file, "Failure: receiver cancelled the transfer");
-	} else {
-		msn_ftp_abort(file, "Received invalid command %s from msn client", cmd[0]);
-	}
-	return TRUE;
-}
-
-gboolean msn_ftp_send(gpointer data, gint fd, b_input_condition cond)
-{
-	file_transfer_t *file = data;
-	msn_filetransfer_t *msn_file = file->data;
-
-	msn_file->w_event_id = 0;
-
-	file->write_request(file);
-
-	return FALSE;
-}
-
-/*
- * This should only be called if we can write, so just do it.
- * Add a write watch so we can write more during the next cycle (if possible).
- * This got a bit complicated because (at least) amsn expects packets of size 2045.
- */
-gboolean msn_ftps_write(file_transfer_t *file, char *buffer, unsigned int len)
-{
-	msn_filetransfer_t *msn_file = file->data;
-	int ret, overflow;
-
-	/* what we can't send now */
-	overflow = msn_file->sbufpos + len - MSNFTP_PSIZE;
-
-	/* append what we can do the send buffer */
-	memcpy(msn_file->sbuf + msn_file->sbufpos, buffer, MIN(len, MSNFTP_PSIZE - msn_file->sbufpos));
-	msn_file->sbufpos += MIN(len, MSNFTP_PSIZE - msn_file->sbufpos);
-
-	/* if we don't have enough for a full packet and there's more wait for it */
-	if ((msn_file->sbufpos < MSNFTP_PSIZE) &&
-	    (msn_file->data_sent + msn_file->sbufpos - 3 < file->file_size)) {
-		if (!msn_file->w_event_id) {
-			msn_file->w_event_id = b_input_add(msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file);
-		}
-		return TRUE;
-	}
-
-	/* Accumulated enough data, lets send something out */
-
-	msn_file->sbuf[0] = 0;
-	msn_file->sbuf[1] = (msn_file->sbufpos - 3) & 0xff;
-	msn_file->sbuf[2] = ((msn_file->sbufpos - 3) >> 8) & 0xff;
-
-	ASSERTSOCKOP(ret = send(msn_file->fd, msn_file->sbuf, msn_file->sbufpos, 0), "Sending");
-
-	msn_file->data_sent += ret - 3;
-
-	/* TODO: this should really not be fatal */
-	if (ret < msn_file->sbufpos) {
-		return msn_ftp_abort(file, "send() sent %d instead of %d (send buffer full!)", ret, msn_file->sbufpos);
-	}
-
-	msn_file->sbufpos = 3;
-
-	if (overflow > 0) {
-		while (overflow > (MSNFTP_PSIZE - 3)) {
-			if (!msn_ftps_write(file, buffer + len - overflow, MSNFTP_PSIZE - 3)) {
-				return FALSE;
-			}
-			overflow -= MSNFTP_PSIZE - 3;
-		}
-		return msn_ftps_write(file, buffer + len - overflow, overflow);
-	}
-
-	if (msn_file->data_sent == file->file_size) {
-		if (msn_file->w_event_id) {
-			b_event_remove(msn_file->w_event_id);
-			msn_file->w_event_id = 0;
-		}
-	} else {
-		/* we might already be listening if this is data from an overflow */
-		if (!msn_file->w_event_id) {
-			msn_file->w_event_id = b_input_add(msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file);
-		}
-	}
-
-	return TRUE;
-}
-
-/* Binary part of the file transfer protocol */
-gboolean msn_ftpr_read(file_transfer_t *file)
-{
-	msn_filetransfer_t *msn_file = file->data;
-	int st;
-	unsigned char buf[3];
-
-	if (msn_file->data_remaining) {
-		msn_file->r_event_id = 0;
-
-		ASSERTSOCKOP(st = read(msn_file->fd, file->buffer, MIN(sizeof(file->buffer),
-		                                                       msn_file->data_remaining)), "Receiving");
-
-		if (st == 0) {
-			return msn_ftp_abort(file, "Remote end closed connection");
-		}
-
-		msn_file->data_sent += st;
-
-		msn_file->data_remaining -= st;
-
-		file->write(file, file->buffer, st);
-
-		if (msn_file->data_sent >= file->file_size) {
-			imcb_file_finished(file);
-		}
-
-		return FALSE;
-	} else {
-		ASSERTSOCKOP(st = read(msn_file->fd, buf, 1), "Receiving");
-		if (st == 0) {
-			return msn_ftp_abort(file, "read returned EOF while reading data header from msn client");
-		} else if (buf[0] == '\r' || buf[0] == '\n') {
-			debug("Discarding extraneous newline");
-		} else if (buf[0] != 0) {
-			msn_ftp_abort(file, "Remote end canceled the transfer");
-			/* don't really care about these last 2 (should be 0,0) */
-			read(msn_file->fd, buf, 2);
-			return FALSE;
-		} else {
-			unsigned int size;
-			ASSERTSOCKOP(st = read(msn_file->fd, buf, 2), "Receiving");
-			if (st < 2) {
-				return msn_ftp_abort(file,
-				                     "read returned EOF while reading data header from msn client");
-			}
-
-			size = buf[0] + ((unsigned int) buf[1] << 8);
-			msn_file->data_remaining = size;
-		}
-	}
-	return TRUE;
-}
-
-/* Text mode part of the file transfer protocol */
-gboolean msn_ftp_txtproto(file_transfer_t *file)
-{
-	msn_filetransfer_t *msn_file = file->data;
-	int i = msn_file->tbufpos, st;
-	char *tbuf = msn_file->tbuf;
-
-	ASSERTSOCKOP(st = read(msn_file->fd,
-	                       tbuf + msn_file->tbufpos,
-	                       sizeof(msn_file->tbuf) - msn_file->tbufpos),
-	             "Receiving");
-
-	if (st == 0) {
-		return msn_ftp_abort(file, "read returned EOF while reading text from msn client");
-	}
-
-	msn_file->tbufpos += st;
-
-	do {
-		for (; i < msn_file->tbufpos; i++) {
-			if (tbuf[i] == '\n' || tbuf[i] == '\r') {
-				tbuf[i] = '\0';
-				if (i > 0) {
-					msn_ftp_handle_command(file, tbuf);
-				} else {
-					while (tbuf[i] == '\n' || tbuf[i] == '\r') {
-						i++;
-					}
-				}
-				memmove(tbuf, tbuf + i + 1, msn_file->tbufpos - i - 1);
-				msn_file->tbufpos -= i + 1;
-				i = 0;
-				break;
-			}
-		}
-	} while (i < msn_file->tbufpos);
-
-	if (msn_file->tbufpos == sizeof(msn_file->tbuf)) {
-		return msn_ftp_abort(file,
-		                     "Line exceeded %d bytes in text protocol",
-		                     sizeof(msn_file->tbuf));
-	}
-	return TRUE;
-}
-
-gboolean msn_ftp_read(gpointer data, gint fd, b_input_condition cond)
-{
-	file_transfer_t *file = data;
-	msn_filetransfer_t *msn_file = file->data;
-
-	if (msn_file->status & MSN_TRANSFER_RECEIVING) {
-		return msn_ftpr_read(file);
-	} else {
-		return msn_ftp_txtproto(file);
-	}
-}
-
-void msn_ftp_free(file_transfer_t *file)
-{
-	msn_filetransfer_t *msn_file = file->data;
-
-	if (msn_file->r_event_id) {
-		b_event_remove(msn_file->r_event_id);
-	}
-
-	if (msn_file->w_event_id) {
-		b_event_remove(msn_file->w_event_id);
-	}
-
-	if (msn_file->fd != -1) {
-		closesocket(msn_file->fd);
-	}
-
-	msn_file->md->filetransfers = g_slist_remove(msn_file->md->filetransfers, msn_file->dcc);
-
-	g_free(msn_file->handle);
-
-	g_free(msn_file);
-}
-
-void msn_ftpr_accept(file_transfer_t *file)
-{
-	msn_filetransfer_t *msn_file = file->data;
-
-	msn_ftp_invitation_cmd(msn_file->md->ic, msn_file->handle, msn_file->invite_cookie, "ACCEPT",
-	                       "Launch-Application: FALSE\r\n"
-	                       "Request-Data: IP-Address:\r\n");
-}
-
-void msn_ftp_finished(file_transfer_t *file)
-{
-	msn_ftp_write(file, "BYE 16777989\r\n");
-}
-
-void msn_ftp_canceled(file_transfer_t *file, char *reason)
-{
-	msn_filetransfer_t *msn_file = file->data;
-
-	msn_ftp_cancel_invite(msn_file->md->ic, msn_file->handle,
-	                      msn_file->invite_cookie,
-	                      file->status & FT_STATUS_TRANSFERRING ?
-	                      "FTTIMEOUT" :
-	                      "FAIL");
-
-	imcb_log(msn_file->md->ic, "File transfer aborted: %s", reason);
-}
-
-gboolean msn_ftpr_write_request(file_transfer_t *file)
-{
-	msn_filetransfer_t *msn_file = file->data;
-
-	if (msn_file->r_event_id != 0) {
-		msn_ftp_abort(file,
-		              "BUG in MSN file transfer:"
-		              "write_request called when"
-		              "already watching for input");
-		return FALSE;
-	}
-
-	msn_file->r_event_id =
-	        b_input_add(msn_file->fd, B_EV_IO_READ, msn_ftp_read, file);
-
-	return TRUE;
-}
Index: protocols/msn/invitation.h
===================================================================
--- protocols/msn/invitation.h	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ 	(revision )
@@ -1,81 +1,0 @@
-/********************************************************************\
-* BitlBee -- An IRC to other IM-networks gateway                     *
-*                                                                    *
-* Copyright 2006 Marijn Kruisselbrink and others                     *
-\********************************************************************/
-
-/* MSN module - File transfer support             */
-
-/*
- 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 with
- the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
- if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
- Fifth Floor, Boston, MA  02110-1301  USA
- */
-
-#ifndef _MSN_INVITATION_H
-#define _MSN_INVITATION_H
-
-#include "msn.h"
-
-#define MSN_INVITE_HEADERS      "MIME-Version: 1.0\r\n" \
-	"Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" \
-	"\r\n"
-
-#define MSNFTP_PSIZE 2048
-
-typedef enum {
-	MSN_TRANSFER_RECEIVING  = 1,
-	MSN_TRANSFER_SENDING    = 2
-} msn_filetransfer_status_t;
-
-typedef struct msn_filetransfer {
-/* Generic invitation data */
-	/* msn_data instance this invitation was received with. */
-	struct msn_data *md;
-	/* Cookie specifying this invitation. */
-	unsigned int invite_cookie;
-	/* Handle of user that started this invitation. */
-	char *handle;
-
-/* File transfer specific data */
-	/* Current status of the file transfer. */
-	msn_filetransfer_status_t status;
-	/* Pointer to the dcc structure for this transfer. */
-	file_transfer_t *dcc;
-	/* Socket the transfer is taking place over. */
-	int fd;
-	/* Cookie received in the original invitation, this must be sent as soon as
-	   a connection has been established. */
-	unsigned int auth_cookie;
-	/* Data remaining to be received in the current packet. */
-	unsigned int data_remaining;
-	/* Buffer containing received, but unprocessed text. */
-	char tbuf[256];
-	unsigned int tbufpos;
-
-	unsigned int data_sent;
-
-	gint r_event_id;
-	gint w_event_id;
-
-	unsigned char sbuf[2048];
-	int sbufpos;
-
-} msn_filetransfer_t;
-
-void msn_invitation_invite(struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen);
-void msn_invitation_accept(struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen);
-void msn_invitation_cancel(struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen);
-
-#endif
Index: protocols/msn/msn.c
===================================================================
--- protocols/msn/msn.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/msn/msn.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -30,5 +30,4 @@
 int msn_chat_id;
 GSList *msn_connections;
-GSList *msn_switchboards;
 
 static char *set_eval_display_name(set_t *set, char *value);
@@ -41,5 +40,5 @@
 	s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY;
 
-	s = set_add(&acc->set, "server", MSN_NS_HOST, set_eval_account, acc);
+	s = set_add(&acc->set, "server", NULL, set_eval_account, acc);
 	s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY;
 
@@ -48,5 +47,4 @@
 
 	set_add(&acc->set, "mail_notifications", "false", set_eval_bool, acc);
-	set_add(&acc->set, "switchboard_keepalives", "false", set_eval_bool, acc);
 
 	acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE |
@@ -58,7 +56,12 @@
 	struct im_connection *ic = imcb_new(acc);
 	struct msn_data *md = g_new0(struct msn_data, 1);
+	char *server = set_getstr(&ic->acc->set, "server");
 
 	ic->proto_data = md;
 	ic->flags |= OPT_PONGS | OPT_PONGED;
+
+	if (!server) {
+		server = "geo.gateway.messenger.live.com";
+	}
 
 	if (strchr(acc->user, '@') == NULL) {
@@ -71,11 +74,11 @@
 	md->away_state = msn_away_state_list;
 	md->domaintree = g_tree_new(msn_domaintree_cmp);
-	md->ns->fd = -1;
+	md->fd = -1;
+	md->is_http = TRUE;
 
 	msn_connections = g_slist_prepend(msn_connections, ic);
 
 	imcb_log(ic, "Connecting");
-	msn_ns_connect(ic, md->ns,
-	               set_getstr(&ic->acc->set, "server"),
+	msn_ns_connect(ic, server,
 	               set_getint(&ic->acc->set, "port"));
 }
@@ -88,17 +91,6 @@
 
 	if (md) {
-		/** Disabling MSN ft support for now.
-		while( md->filetransfers ) {
-		        imcb_file_canceled( md->filetransfers->data, "Closing connection" );
-		}
-		*/
-
-		msn_ns_close(md->ns);
-
-		while (md->switchboards) {
-			msn_sb_destroy(md->switchboards->data);
-		}
-
-		msn_msgq_purge(ic, &md->msgq);
+		msn_ns_close(md);
+
 		msn_soapq_flush(ic, FALSE);
 
@@ -112,8 +104,8 @@
 		while (md->groups) {
 			struct msn_group *mg = md->groups->data;
+			md->groups = g_slist_remove(md->groups, mg);
 			g_free(mg->id);
 			g_free(mg->name);
 			g_free(mg);
-			md->groups = g_slist_remove(md->groups, mg);
 		}
 
@@ -127,8 +119,8 @@
 		while (md->grpq) {
 			struct msn_groupadd *ga = md->grpq->data;
+			md->grpq = g_slist_remove(md->grpq, ga);
 			g_free(ga->group);
 			g_free(ga->who);
 			g_free(ga);
-			md->grpq = g_slist_remove(md->grpq, ga);
 		}
 
@@ -152,27 +144,13 @@
 {
 	struct bee_user *bu = bee_user_by_handle(ic->bee, ic, who);
-	struct msn_buddy_data *bd = bu ? bu->data : NULL;
-	struct msn_switchboard *sb;
 
 #ifdef DEBUG
 	if (strcmp(who, "raw") == 0) {
 		msn_ns_write(ic, -1, "%s\r\n", message);
-	} else
+		return 0;
+	}
 #endif
-	if (bd && bd->flags & MSN_BUDDY_FED) {
-		msn_ns_sendmessage(ic, bu, message);
-	} else if ((sb = msn_sb_by_handle(ic, who))) {
-		return(msn_sb_sendmessage(sb, message));
-	} else {
-		struct msn_message *m;
-
-		/* Create a message. We have to arrange a usable switchboard, and send the message later. */
-		m = g_new0(struct msn_message, 1);
-		m->who = g_strdup(who);
-		m->text = g_strdup(message);
-
-		return msn_sb_write_msg(ic, m);
-	}
-
+
+	msn_ns_sendmessage(ic, bu, message);
 	return(0);
 }
@@ -196,6 +174,6 @@
 static void msn_set_away(struct im_connection *ic, char *state, char *message)
 {
-	char *uux;
 	struct msn_data *md = ic->proto_data;
+	char *nick, *psm, *idle, *statecode, *body, *buf;
 
 	if (state == NULL) {
@@ -205,30 +183,21 @@
 	}
 
-	if (!msn_ns_write(ic, -1, "CHG %d %s %d:%02d\r\n", ++md->trId, md->away_state->code, MSN_CAP1, MSN_CAP2)) {
-		return;
-	}
-
-	uux = g_markup_printf_escaped("<EndpointData><Capabilities>%d:%02d"
-	                              "</Capabilities></EndpointData>",
-	                              MSN_CAP1, MSN_CAP2);
-	msn_ns_write(ic, -1, "UUX %d %zd\r\n%s", ++md->trId, strlen(uux), uux);
-	g_free(uux);
-
-	uux = g_markup_printf_escaped("<PrivateEndpointData><EpName>%s</EpName>"
-	                              "<Idle>%s</Idle><ClientType>%d</ClientType>"
-	                              "<State>%s</State></PrivateEndpointData>",
-	                              md->uuid,
-	                              strcmp(md->away_state->code, "IDL") ? "false" : "true",
-	                              1,  /* ? */
-	                              md->away_state->code);
-	msn_ns_write(ic, -1, "UUX %d %zd\r\n%s", ++md->trId, strlen(uux), uux);
-	g_free(uux);
-
-	uux = g_markup_printf_escaped("<Data><DDP></DDP><PSM>%s</PSM>"
-	                              "<CurrentMedia></CurrentMedia>"
-	                              "<MachineGuid>%s</MachineGuid></Data>",
-	                              message ? message : "", md->uuid);
-	msn_ns_write(ic, -1, "UUX %d %zd\r\n%s", ++md->trId, strlen(uux), uux);
-	g_free(uux);
+	statecode = (char *) md->away_state->code;
+	nick = set_getstr(&ic->acc->set, "display_name");
+	psm = message ? message : "";
+	idle = (strcmp(statecode, "IDL") == 0) ? "false" : "true";
+
+	body = g_markup_printf_escaped(MSN_PUT_USER_BODY,
+		nick, psm, psm, md->uuid, statecode, md->uuid, idle, statecode,
+		MSN_CAP1, MSN_CAP2, MSN_CAP1, MSN_CAP2
+	);
+
+	buf = g_strdup_printf(MSN_PUT_HEADERS, ic->acc->user, ic->acc->user, md->uuid,
+		"/user", "application/user+xml",
+		strlen(body), body);
+	msn_ns_write(ic, -1, "PUT %d %zd\r\n%s", ++md->trId, strlen(buf), buf);
+
+	g_free(buf);
+	g_free(body);
 }
 
@@ -256,51 +225,22 @@
 static void msn_chat_msg(struct groupchat *c, char *message, int flags)
 {
-	struct msn_switchboard *sb = msn_sb_by_chat(c);
-
-	if (sb) {
-		msn_sb_sendmessage(sb, message);
-	}
-	/* FIXME: Error handling (although this can't happen unless something's
-	   already severely broken) disappeared here! */
+	/* TODO: groupchats*/
 }
 
 static void msn_chat_invite(struct groupchat *c, char *who, char *message)
 {
-	struct msn_switchboard *sb = msn_sb_by_chat(c);
-
-	if (sb) {
-		msn_sb_write(sb, "CAL %d %s\r\n", ++sb->trId, who);
-	}
+	/* TODO: groupchats*/
 }
 
 static void msn_chat_leave(struct groupchat *c)
 {
-	struct msn_switchboard *sb = msn_sb_by_chat(c);
-
-	if (sb) {
-		msn_sb_write(sb, "OUT\r\n");
-	}
+	/* TODO: groupchats*/
 }
 
 static struct groupchat *msn_chat_with(struct im_connection *ic, char *who)
 {
-	struct msn_switchboard *sb;
+	/* TODO: groupchats*/
 	struct groupchat *c = imcb_chat_new(ic, who);
-
-	if ((sb = msn_sb_by_handle(ic, who))) {
-		debug("Converting existing switchboard to %s to a groupchat", who);
-		return msn_sb_to_chat(sb);
-	} else {
-		struct msn_message *m;
-
-		/* Create a magic message. This is quite hackish, but who cares? :-P */
-		m = g_new0(struct msn_message, 1);
-		m->who = g_strdup(who);
-		m->text = g_strdup(GROUPCHAT_SWITCHBOARD_MESSAGE);
-
-		msn_sb_write_msg(ic, m);
-
-		return c;
-	}
+	return c;
 }
 
@@ -322,12 +262,5 @@
 static void msn_add_deny(struct im_connection *ic, char *who)
 {
-	struct msn_switchboard *sb;
-
 	msn_buddy_list_add(ic, MSN_BUDDY_BL, who, who, NULL);
-
-	/* If there's still a conversation with this person, close it. */
-	if ((sb = msn_sb_by_handle(ic, who))) {
-		msn_sb_destroy(sb);
-	}
 }
 
@@ -462,6 +395,4 @@
 	ret->buddy_action = msn_buddy_action;
 
-	//ret->transfer_request = msn_ftp_transfer_request;
-
 	register_protocol(ret);
 }
Index: protocols/msn/msn.h
===================================================================
--- protocols/msn/msn.h	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/msn/msn.h	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -32,5 +32,4 @@
 #define NUDGE_MESSAGE "\r\r\rSHAKE THAT THING\r\r\r"
 #define GROUPCHAT_SWITCHBOARD_MESSAGE "\r\r\rME WANT TALK TO MANY PEOPLE\r\r\r"
-#define SB_KEEPALIVE_MESSAGE "\r\r\rDONT HANG UP ON ME!\r\r\r"
 
 #ifdef DEBUG_MSN
@@ -60,5 +59,5 @@
 #define MSNP11_PROD_KEY "C1BX{V4W}Q3*10SM"
 #define MSNP11_PROD_ID  "PROD0120PW!CCV9@"
-#define MSNP_VER        "MSNP18"
+#define MSNP_VER        "MSNP21"
 #define MSNP_BUILD      "14.0.8117.416"
 
@@ -68,9 +67,39 @@
 #define MSN_CAP2        0x0000
 
-#define MSN_MESSAGE_HEADERS "MIME-Version: 1.0\r\n" \
+#define MSN_BASE_HEADERS \
+	"Routing: 1.0\r\n" \
+	"To: 1:%s\r\n" \
+	"From: 1:%s;epid={%s}\r\n" \
+	"\r\n" \
+	"Reliability: 1.0\r\n" \
+	"\r\n"
+
+#define MSN_MESSAGE_HEADERS MSN_BASE_HEADERS \
+	"Messaging: 2.0\r\n" \
+	"Message-Type: Text\r\n" \
+	"Content-Length: %zd\r\n" \
 	"Content-Type: text/plain; charset=UTF-8\r\n" \
-	"User-Agent: BitlBee " BITLBEE_VERSION "\r\n" \
-	"X-MMS-IM-Format: FN=MS%20Shell%20Dlg; EF=; CO=0; CS=0; PF=0\r\n" \
-	"\r\n"
+	"X-MMS-IM-Format: FN=Segoe%%20UI; EF=; CO=0; CS=0; PF=0\r\n" \
+	"\r\n" \
+	"%s"
+
+#define MSN_PUT_HEADERS MSN_BASE_HEADERS \
+	"Publication: 1.0\r\n" \
+	"Uri: %s\r\n" \
+	"Content-Type: %s\r\n" \
+	"Content-Length: %zd\r\n" \
+	"\r\n" \
+	"%s"
+
+#define MSN_PUT_USER_BODY \
+	"<user>" \
+	"<s n=\"PE\"><UserTileLocation></UserTileLocation><FriendlyName>%s</FriendlyName><PSM>%s</PSM><DDP></DDP>" \
+	"<Scene></Scene><ASN></ASN><ColorScheme>-3</ColorScheme><BDG></BDG><RUM>%s</RUM><RUL></RUL><RLT>0</RLT>" \
+	"<RID></RID><SUL></SUL><MachineGuid>%s</MachineGuid></s>" \
+	"<s n=\"IM\"><Status>%s</Status><CurrentMedia></CurrentMedia></s>" \
+	"<sep n=\"PD\"><ClientType>1</ClientType><EpName>%s</EpName><Idle>%s</Idle><State>%s</State></sep>" \
+	"<sep n=\"PE\"><VER>BitlBee:" BITLBEE_VERSION "</VER><TYP>1</TYP><Capabilities>%d:%d</Capabilities></sep>" \
+	"<sep n=\"IM\"><Capabilities>%d:%d</Capabilities></sep>" \
+	"</user>"
 
 #define MSN_TYPING_HEADERS "MIME-Version: 1.0\r\n" \
@@ -84,8 +113,4 @@
 	"ID: 1\r\n" \
 	"\r\n"
-
-#define MSN_SB_KEEPALIVE_HEADERS "MIME-Version: 1.0\r\n" \
-	"Content-Type: text/x-ping\r\n" \
-	"\r\n\r\n"
 
 #define PROFILE_URL "http://members.msn.com/"
@@ -99,5 +124,27 @@
 } msn_flags_t;
 
-struct msn_handler_data {
+struct msn_gw {
+	char *last_host;
+	int port;
+	gboolean ssl;
+
+	char *session_id;
+
+	GByteArray *in;
+	GByteArray *out;
+
+	int poll_timeout;
+
+	b_event_handler callback;
+
+	struct im_connection *ic;
+	struct msn_data *md;
+
+	gboolean open;
+	gboolean waiting;
+	gboolean polling;
+};
+
+struct msn_data {
 	int fd, inpa;
 	int rxlen;
@@ -107,15 +154,6 @@
 	char *cmd_text;
 
-	/* Either ic or sb */
-	gpointer data;
-
-	int (*exec_command) (struct msn_handler_data *handler, char **cmd, int count);
-	int (*exec_message) (struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int count);
-};
-
-struct msn_data {
 	struct im_connection *ic;
 
-	struct msn_handler_data ns[1];
 	msn_flags_t flags;
 
@@ -126,7 +164,4 @@
 
 	GSList *msgq, *grpq, *soapq;
-	GSList *switchboards;
-	int sb_failures;
-	time_t first_sb_failure;
 
 	const struct msn_away_state *away_state;
@@ -139,24 +174,7 @@
 	GTree *domaintree;
 	int adl_todo;
-};
-
-struct msn_switchboard {
-	struct im_connection *ic;
-
-	/* The following two are also in the handler. TODO: Clean up. */
-	int fd;
-	gint inp;
-	struct msn_handler_data *handler;
-	gint keepalive;
-
-	int trId;
-	int ready;
-
-	int session;
-	char *key;
-
-	GSList *msgq;
-	char *who;
-	struct groupchat *chat;
+
+	gboolean is_http;
+	struct msn_gw *gw;
 };
 
@@ -205,6 +223,4 @@
 #define STATUS_FATAL            1
 #define STATUS_SB_FATAL         2
-#define STATUS_SB_IM_SPARE      4       /* Make one-to-one conversation switchboard available again, invite failed. */
-#define STATUS_SB_CHAT_SPARE    8       /* Same, but also for groupchats (not used yet). */
 
 extern int msn_chat_id;
@@ -218,15 +234,15 @@
    before doing *anything* else. */
 extern GSList *msn_connections;
-extern GSList *msn_switchboards;
 
 /* ns.c */
 int msn_ns_write(struct im_connection *ic, int fd, const char *fmt, ...) G_GNUC_PRINTF(3, 4);
-gboolean msn_ns_connect(struct im_connection *ic, struct msn_handler_data *handler, const char *host, int port);
-void msn_ns_close(struct msn_handler_data *handler);
+gboolean msn_ns_connect(struct im_connection *ic, const char *host, int port);
+void msn_ns_close(struct msn_data *handler);
 void msn_auth_got_passport_token(struct im_connection *ic, const char *token, const char *error);
 void msn_auth_got_contact_list(struct im_connection *ic);
 int msn_ns_finish_login(struct im_connection *ic);
 int msn_ns_sendmessage(struct im_connection *ic, struct bee_user *bu, const char *text);
-void msn_ns_oim_send_queue(struct im_connection *ic, GSList **msgq);
+int msn_ns_command(struct msn_data *md, char **cmd, int num_parts);
+int msn_ns_message(struct msn_data *md, char *msg, int msglen, char **cmd, int num_parts);
 
 /* msn_util.c */
@@ -235,7 +251,6 @@
 int msn_buddy_list_remove(struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *group);
 void msn_buddy_ask(bee_user_t *bu);
-char **msn_linesplit(char *line);
-int msn_handler(struct msn_handler_data *h);
-void msn_msgq_purge(struct im_connection *ic, GSList **list);
+void msn_queue_feed(struct msn_data *h, char *bytes, int st);
+int msn_handler(struct msn_data *h);
 char *msn_p11_challenge(char *challenge);
 gint msn_domaintree_cmp(gconstpointer a_, gconstpointer b_);
@@ -251,17 +266,10 @@
 const struct msn_status_code *msn_status_by_number(int number);
 
-/* sb.c */
-int msn_sb_write(struct msn_switchboard *sb, const char *fmt, ...) G_GNUC_PRINTF(2, 3);;
-struct msn_switchboard *msn_sb_create(struct im_connection *ic, char *host, int port, char *key, int session);
-struct msn_switchboard *msn_sb_by_handle(struct im_connection *ic, const char *handle);
-struct msn_switchboard *msn_sb_by_chat(struct groupchat *c);
-struct msn_switchboard *msn_sb_spare(struct im_connection *ic);
-int msn_sb_sendmessage(struct msn_switchboard *sb, char *text);
-struct groupchat *msn_sb_to_chat(struct msn_switchboard *sb);
-void msn_sb_destroy(struct msn_switchboard *sb);
-gboolean msn_sb_connected(gpointer data, gint source, b_input_condition cond);
-int msn_sb_write_msg(struct im_connection *ic, struct msn_message *m);
-void msn_sb_start_keepalives(struct msn_switchboard *sb, gboolean initial);
-void msn_sb_stop_keepalives(struct msn_switchboard *sb);
+/* gw.c */
+struct msn_gw *msn_gw_new(struct im_connection *ic);
+void msn_gw_free(struct msn_gw *gw);
+void msn_gw_open(struct msn_gw *gw);
+ssize_t msn_gw_read(struct msn_gw *gw, char **buf);
+void msn_gw_write(struct msn_gw *gw, char *buf, size_t len);
 
 #endif //_MSN_H
Index: protocols/msn/msn_util.c
===================================================================
--- protocols/msn/msn_util.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/msn/msn_util.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -55,46 +55,4 @@
 
 	*groupid = '\0';
-#if 0
-	if (group) {
-		int i;
-		for (i = 0; i < md->groupcount; i++) {
-			if (g_strcasecmp(md->grouplist[i], group) == 0) {
-				g_snprintf(groupid, sizeof(groupid), " %d", i);
-				break;
-			}
-		}
-
-		if (*groupid == '\0') {
-			/* Have to create this group, it doesn't exist yet. */
-			struct msn_groupadd *ga;
-			GSList *l;
-
-			for (l = md->grpq; l; l = l->next) {
-				ga = l->data;
-				if (g_strcasecmp(ga->group, group) == 0) {
-					break;
-				}
-			}
-
-			ga = g_new0(struct msn_groupadd, 1);
-			ga->who = g_strdup(who);
-			ga->group = g_strdup(group);
-			md->grpq = g_slist_prepend(md->grpq, ga);
-
-			if (l == NULL) {
-				char groupname[strlen(group) + 1];
-				strcpy(groupname, group);
-				http_encode(groupname);
-				g_snprintf(buf, sizeof(buf), "ADG %d %s %d\r\n", ++md->trId, groupname, 0);
-				return msn_write(ic, buf, strlen(buf));
-			} else {
-				/* This can happen if the user's doing lots of adds to a
-				   new group at once; we're still waiting for the server
-				   to confirm group creation. */
-				return 1;
-			}
-		}
-	}
-#endif
 
 	if (!((bu = bee_user_by_handle(ic->bee, ic, who)) ||
@@ -132,15 +90,4 @@
 
 	*groupid = '\0';
-#if 0
-	if (group) {
-		int i;
-		for (i = 0; i < md->groupcount; i++) {
-			if (g_strcasecmp(md->grouplist[i], group) == 0) {
-				g_snprintf(groupid, sizeof(groupid), " %d", i);
-				break;
-			}
-		}
-	}
-#endif
 
 	if (!(bu = bee_user_by_handle(ic->bee, ic, who)) ||
@@ -174,11 +121,7 @@
 };
 
-static void msn_buddy_ask_yes(void *data)
+static void msn_buddy_ask_free(void *data)
 {
 	struct msn_buddy_ask_data *bla = data;
-
-	msn_buddy_list_add(bla->ic, MSN_BUDDY_AL, bla->handle, bla->realname, NULL);
-
-	imcb_ask_add(bla->ic, bla->handle, NULL);
 
 	g_free(bla->handle);
@@ -187,4 +130,15 @@
 }
 
+static void msn_buddy_ask_yes(void *data)
+{
+	struct msn_buddy_ask_data *bla = data;
+
+	msn_buddy_list_add(bla->ic, MSN_BUDDY_AL, bla->handle, bla->realname, NULL);
+
+	imcb_ask_add(bla->ic, bla->handle, NULL);
+
+	msn_buddy_ask_free(bla);
+}
+
 static void msn_buddy_ask_no(void *data)
 {
@@ -193,7 +147,5 @@
 	msn_buddy_list_add(bla->ic, MSN_BUDDY_BL, bla->handle, bla->realname, NULL);
 
-	g_free(bla->handle);
-	g_free(bla->realname);
-	g_free(bla);
+	msn_buddy_ask_free(bla);
 }
 
@@ -216,38 +168,19 @@
 	           "The user %s (%s) wants to add you to his/her buddy list.",
 	           bu->handle, bu->fullname);
-	imcb_ask(bu->ic, buf, bla, msn_buddy_ask_yes, msn_buddy_ask_no);
-}
-
-/* *NOT* thread-safe, but that's not a problem for now... */
-char **msn_linesplit(char *line)
-{
-	static char **ret = NULL;
-	static int size = 3;
-	int i, n = 0;
-
-	if (ret == NULL) {
-		ret = g_new0(char*, size);
-	}
-
-	for (i = 0; line[i] && line[i] == ' '; i++) {
-		;
-	}
-	if (line[i]) {
-		ret[n++] = line + i;
-		for (i++; line[i]; i++) {
-			if (line[i] == ' ') {
-				line[i] = 0;
-			} else if (line[i] != ' ' && !line[i - 1]) {
-				ret[n++] = line + i;
-			}
-
-			if (n >= size) {
-				ret = g_renew(char*, ret, size += 2);
-			}
-		}
-	}
-	ret[n] = NULL;
-
-	return(ret);
+
+	imcb_ask_with_free(bu->ic, buf, bla, msn_buddy_ask_yes, msn_buddy_ask_no, msn_buddy_ask_free);
+}
+
+void msn_queue_feed(struct msn_data *h, char *bytes, int st)
+{
+	h->rxq = g_renew(char, h->rxq, h->rxlen + st);
+	memcpy(h->rxq + h->rxlen, bytes, st);
+	h->rxlen += st;
+
+	if (getenv("BITLBEE_DEBUG")) {
+		fprintf(stderr, "\n\x1b[92m<<< ");
+		write(2, bytes , st);
+		fprintf(stderr, "\x1b[97m");
+	}
 }
 
@@ -260,20 +193,7 @@
                    1: OK */
 
-int msn_handler(struct msn_handler_data *h)
-{
-	int st;
-
-	h->rxq = g_renew(char, h->rxq, h->rxlen + 1024);
-	st = read(h->fd, h->rxq + h->rxlen, 1024);
-	h->rxlen += st;
-
-	if (st <= 0) {
-		return(-1);
-	}
-
-	if (getenv("BITLBEE_DEBUG")) {
-		write(2, "->C:", 4);
-		write(2, h->rxq + h->rxlen - st, st);
-	}
+int msn_handler(struct msn_data *h)
+{
+	int st = 1;
 
 	while (st) {
@@ -287,9 +207,10 @@
 
 					cmd_text = g_strndup(h->rxq, i);
-					cmd = msn_linesplit(cmd_text);
-					for (count = 0; cmd[count]; count++) {
-						;
-					}
-					st = h->exec_command(h, cmd, count);
+					cmd = g_strsplit_set(cmd_text, " ", -1);
+					count = g_strv_length(cmd);
+
+					st = msn_ns_command(h, cmd, count);
+
+					g_strfreev(cmd);
 					g_free(cmd_text);
 
@@ -314,5 +235,5 @@
 			/* If we reached the end of the buffer, there's still an incomplete command there.
 			   Return and wait for more data. */
-			if (i == h->rxlen && h->rxq[i - 1] != '\r' && h->rxq[i - 1] != '\n') {
+			if (i && i == h->rxlen && h->rxq[i - 1] != '\r' && h->rxq[i - 1] != '\n') {
 				break;
 			}
@@ -327,10 +248,11 @@
 
 			msg = g_strndup(h->rxq, h->msglen);
-			cmd = msn_linesplit(h->cmd_text);
-			for (count = 0; cmd[count]; count++) {
-				;
-			}
-
-			st = h->exec_message(h, msg, h->msglen, cmd, count);
+
+			cmd = g_strsplit_set(h->cmd_text, " ", -1);
+			count = g_strv_length(cmd);
+
+			st = msn_ns_message(h, msg, h->msglen, cmd, count);
+
+			g_strfreev(cmd);
 			g_free(msg);
 			g_free(h->cmd_text);
@@ -366,44 +288,4 @@
 }
 
-void msn_msgq_purge(struct im_connection *ic, GSList **list)
-{
-	struct msn_message *m;
-	GString *ret;
-	GSList *l;
-	int n = 0;
-
-	l = *list;
-	if (l == NULL) {
-		return;
-	}
-
-	m = l->data;
-	ret = g_string_sized_new(1024);
-	g_string_printf(ret, "Warning: Cleaning up MSN (switchboard) connection with unsent "
-	                "messages to %s:", m->who ? m->who : "unknown recipient");
-
-	while (l) {
-		m = l->data;
-
-		if (strncmp(m->text, "\r\r\r", 3) != 0) {
-			g_string_append_printf(ret, "\n%s", m->text);
-			n++;
-		}
-
-		g_free(m->who);
-		g_free(m->text);
-		g_free(m);
-
-		l = l->next;
-	}
-	g_slist_free(*list);
-	*list = NULL;
-
-	if (n > 0) {
-		imcb_log(ic, "%s", ret->str);
-	}
-	g_string_free(ret, TRUE);
-}
-
 /* Copied and heavily modified from http://tmsnc.sourceforge.net/chl.c */
 char *msn_p11_challenge(char *challenge)
@@ -529,13 +411,6 @@
 int msn_ns_set_display_name(struct im_connection *ic, const char *value)
 {
-	struct msn_data *md = ic->proto_data;
-	char fn[strlen(value) * 3 + 1];
-
-	strcpy(fn, value);
-	http_encode(fn);
-
-	/* Note: We don't actually know if the server accepted the new name,
-	   and won't give proper feedback yet if it doesn't. */
-	return msn_ns_write(ic, -1, "PRP %d MFN %s\r\n", ++md->trId, fn);
+	// TODO, implement this through msn_set_away's method
+	return 1;
 }
 
Index: protocols/msn/ns.c
===================================================================
--- protocols/msn/ns.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/msn/ns.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -35,9 +35,10 @@
 static gboolean msn_ns_connected(gpointer data, gint source, b_input_condition cond);
 static gboolean msn_ns_callback(gpointer data, gint source, b_input_condition cond);
-static int msn_ns_command(struct msn_handler_data *handler, char **cmd, int num_parts);
-static int msn_ns_message(struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts);
 
 static void msn_ns_send_adl_start(struct im_connection *ic);
 static void msn_ns_send_adl(struct im_connection *ic);
+static void msn_ns_structured_message(struct msn_data *md, char *msg, int msglen, char **cmd);
+static void msn_ns_sdg(struct msn_data *md, char *who, char **parts, char *action);
+static void msn_ns_nfy(struct msn_data *md, char *who, char **parts, char *action, gboolean is_put);
 
 int msn_ns_write(struct im_connection *ic, int fd, const char *fmt, ...)
@@ -54,13 +55,20 @@
 
 	if (fd < 0) {
-		fd = md->ns->fd;
+		fd = md->fd;
 	}
 
 	if (getenv("BITLBEE_DEBUG")) {
-		fprintf(stderr, "->NS%d:%s\n", fd, out);
+		fprintf(stderr, "\x1b[91m>>>[NS%d] %s\n\x1b[97m", fd, out);
 	}
 
 	len = strlen(out);
-	st = write(fd, out, len);
+
+	if (md->is_http) {
+		st = len;
+		msn_gw_write(md->gw, out, len);
+	} else {
+		st = write(fd, out, len);
+	}
+
 	g_free(out);
 	if (st != len) {
@@ -73,15 +81,34 @@
 }
 
-gboolean msn_ns_connect(struct im_connection *ic, struct msn_handler_data *handler, const char *host, int port)
-{
-	if (handler->fd >= 0) {
-		closesocket(handler->fd);
-	}
-
-	handler->exec_command = msn_ns_command;
-	handler->exec_message = msn_ns_message;
-	handler->data = ic;
-	handler->fd = proxy_connect(host, port, msn_ns_connected, handler);
-	if (handler->fd < 0) {
+gboolean msn_ns_connect(struct im_connection *ic, const char *host, int port)
+{
+	struct msn_data *md = ic->proto_data;
+
+	if (md->fd >= 0) {
+		closesocket(md->fd);
+	}
+
+	if (md->is_http) {
+		md->gw = msn_gw_new(ic);
+		md->gw->callback = msn_ns_callback;
+		msn_ns_connected(md, -1, B_EV_IO_READ);
+	} else {
+		md->fd = proxy_connect(host, port, msn_ns_connected, md);
+		if (md->fd < 0) {
+			imcb_error(ic, "Could not connect to server");
+			imc_logout(ic, TRUE);
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean msn_ns_connected(gpointer data, gint source, b_input_condition cond)
+{
+	struct msn_data *md = data;
+	struct im_connection *ic = md->ic;
+
+	if (source == -1 && !md->is_http) {
 		imcb_error(ic, "Could not connect to server");
 		imc_logout(ic, TRUE);
@@ -89,28 +116,7 @@
 	}
 
-	return TRUE;
-}
-
-static gboolean msn_ns_connected(gpointer data, gint source, b_input_condition cond)
-{
-	struct msn_handler_data *handler = data;
-	struct im_connection *ic = handler->data;
-	struct msn_data *md;
-
-	if (!g_slist_find(msn_connections, ic)) {
-		return FALSE;
-	}
-
-	md = ic->proto_data;
-
-	if (source == -1) {
-		imcb_error(ic, "Could not connect to server");
-		imc_logout(ic, TRUE);
-		return FALSE;
-	}
-
-	g_free(handler->rxq);
-	handler->rxlen = 0;
-	handler->rxq = g_new0(char, 1);
+	g_free(md->rxq);
+	md->rxlen = 0;
+	md->rxq = g_new0(char, 1);
 
 	if (md->uuid == NULL) {
@@ -130,5 +136,7 @@
 
 	if (msn_ns_write(ic, source, "VER %d %s CVR0\r\n", ++md->trId, MSNP_VER)) {
-		handler->inpa = b_input_add(handler->fd, B_EV_IO_READ, msn_ns_callback, handler);
+		if (!md->is_http) {
+			md->inpa = b_input_add(md->fd, B_EV_IO_READ, msn_ns_callback, md);
+		}
 		imcb_log(ic, "Connected to server, waiting for reply");
 	}
@@ -137,39 +145,57 @@
 }
 
-void msn_ns_close(struct msn_handler_data *handler)
-{
-	if (handler->fd >= 0) {
-		closesocket(handler->fd);
-		b_event_remove(handler->inpa);
-	}
-
-	handler->fd = handler->inpa = -1;
-	g_free(handler->rxq);
-	g_free(handler->cmd_text);
-
-	handler->rxlen = 0;
-	handler->rxq = NULL;
-	handler->cmd_text = NULL;
+void msn_ns_close(struct msn_data *md)
+{
+	if (md->gw) {
+		msn_gw_free(md->gw);
+	}
+	if (md->fd >= 0) {
+		closesocket(md->fd);
+		b_event_remove(md->inpa);
+	}
+
+	md->fd = md->inpa = -1;
+	g_free(md->rxq);
+	g_free(md->cmd_text);
+
+	md->rxlen = 0;
+	md->rxq = NULL;
+	md->cmd_text = NULL;
 }
 
 static gboolean msn_ns_callback(gpointer data, gint source, b_input_condition cond)
 {
-	struct msn_handler_data *handler = data;
-	struct im_connection *ic = handler->data;
-
-	if (msn_handler(handler) == -1) {  /* Don't do this on ret == 0, it's already done then. */
+	struct msn_data *md = data;
+	struct im_connection *ic = md->ic;
+	char *bytes;
+	int st;
+
+	if (md->is_http) {
+		st = msn_gw_read(md->gw, &bytes);
+	} else {
+		bytes = g_malloc(1024);
+		st = read(md->fd, bytes, 1024);
+	}
+
+	if (st <= 0) {
 		imcb_error(ic, "Error while reading from server");
 		imc_logout(ic, TRUE);
-
 		return FALSE;
-	} else {
-		return TRUE;
-	}
-}
-
-static int msn_ns_command(struct msn_handler_data *handler, char **cmd, int num_parts)
-{
-	struct im_connection *ic = handler->data;
-	struct msn_data *md = ic->proto_data;
+	}
+
+	msn_queue_feed(md, bytes, st);
+
+	g_free(bytes);
+
+	/* Ignore ret == 0, it's already disconnected then. */
+	msn_handler(md);
+
+	return TRUE;
+	
+}
+
+int msn_ns_command(struct msn_data *md, char **cmd, int num_parts)
+{
+	struct im_connection *ic = md->ic;
 
 	if (num_parts == 0) {
@@ -185,9 +211,9 @@
 		}
 
-		return(msn_ns_write(ic, handler->fd, "CVR %d 0x0409 mac 10.2.0 ppc macmsgs 3.5.1 macmsgs %s\r\n",
+		return(msn_ns_write(ic, md->fd, "CVR %d 0x0409 mac 10.2.0 ppc macmsgs 3.5.1 macmsgs %s VmVyc2lvbjogMQ0KWGZyQ291bnQ6IDINClhmclNlbnRVVENUaW1lOiA2MzU2MTQ3OTU5NzgzOTAwMDANCklzR2VvWGZyOiB0cnVlDQo=\r\n",
 		                    ++md->trId, ic->acc->user));
 	} else if (strcmp(cmd[0], "CVR") == 0) {
 		/* We don't give a damn about the information we just received */
-		return msn_ns_write(ic, handler->fd, "USR %d SSO I %s\r\n", ++md->trId, ic->acc->user);
+		return msn_ns_write(ic, md->fd, "USR %d SSO I %s\r\n", ++md->trId, ic->acc->user);
 	} else if (strcmp(cmd[0], "XFR") == 0) {
 		char *server;
@@ -195,6 +221,6 @@
 
 		if (num_parts >= 6 && strcmp(cmd[2], "NS") == 0) {
-			b_event_remove(handler->inpa);
-			handler->inpa = -1;
+			b_event_remove(md->inpa);
+			md->inpa = -1;
 
 			server = strchr(cmd[3], ':');
@@ -209,52 +235,5 @@
 
 			imcb_log(ic, "Transferring to other server");
-			return msn_ns_connect(ic, handler, server, port);
-		} else if (num_parts >= 6 && strcmp(cmd[2], "SB") == 0) {
-			struct msn_switchboard *sb;
-
-			server = strchr(cmd[3], ':');
-			if (!server) {
-				imcb_error(ic, "Syntax error");
-				imc_logout(ic, TRUE);
-				return(0);
-			}
-			*server = 0;
-			port = atoi(server + 1);
-			server = cmd[3];
-
-			if (strcmp(cmd[4], "CKI") != 0) {
-				imcb_error(ic, "Unknown authentication method for switchboard");
-				imc_logout(ic, TRUE);
-				return(0);
-			}
-
-			debug("Connecting to a new switchboard with key %s", cmd[5]);
-
-			if ((sb = msn_sb_create(ic, server, port, cmd[5], MSN_SB_NEW)) == NULL) {
-				/* Although this isn't strictly fatal for the NS connection, it's
-				   definitely something serious (we ran out of file descriptors?). */
-				imcb_error(ic, "Could not create new switchboard");
-				imc_logout(ic, TRUE);
-				return(0);
-			}
-
-			if (md->msgq) {
-				struct msn_message *m = md->msgq->data;
-				GSList *l;
-
-				sb->who = g_strdup(m->who);
-
-				/* Move all the messages to the first user in the message
-				   queue to the switchboard message queue. */
-				l = md->msgq;
-				while (l) {
-					m = l->data;
-					l = l->next;
-					if (strcmp(m->who, sb->who) == 0) {
-						sb->msgq = g_slist_append(sb->msgq, m);
-						md->msgq = g_slist_remove(md->msgq, m);
-					}
-				}
-			}
+			return msn_ns_connect(ic, server, port);
 		} else {
 			imcb_error(ic, "Syntax error");
@@ -290,14 +269,11 @@
 		}
 
-		handler->msglen = atoi(cmd[3]);
-
-		if (handler->msglen <= 0) {
+		md->msglen = atoi(cmd[3]);
+
+		if (md->msglen <= 0) {
 			imcb_error(ic, "Syntax error");
 			imc_logout(ic, TRUE);
 			return(0);
 		}
-	} else if (strcmp(cmd[0], "BLP") == 0) {
-		msn_ns_send_adl_start(ic);
-		return msn_ns_finish_login(ic);
 	} else if (strcmp(cmd[0], "ADL") == 0) {
 		if (num_parts >= 3 && strcmp(cmd[2], "OK") == 0) {
@@ -305,8 +281,6 @@
 			return msn_ns_finish_login(ic);
 		} else if (num_parts >= 3) {
-			handler->msglen = atoi(cmd[2]);
-		}
-	} else if (strcmp(cmd[0], "PRP") == 0) {
-		imcb_connected(ic);
+			md->msglen = atoi(cmd[2]);
+		}
 	} else if (strcmp(cmd[0], "CHL") == 0) {
 		char *resp;
@@ -326,178 +300,25 @@
 		g_free(resp);
 		return st;
-	} else if (strcmp(cmd[0], "ILN") == 0 || strcmp(cmd[0], "NLN") == 0) {
-		const struct msn_away_state *st;
-		const char *handle;
-		int cap = 0;
-
-		if (num_parts < 6) {
-			imcb_error(ic, "Syntax error");
-			imc_logout(ic, TRUE);
-			return(0);
-		}
-		/* ILN and NLN are more or less the same, except ILN has a trId
-		   at the start, and NLN has a capability field at the end.
-		   Does ILN still exist BTW? */
-		if (cmd[0][1] == 'I') {
-			cmd++;
-		} else {
-			cap = atoi(cmd[4]);
-		}
-
-		handle = msn_normalize_handle(cmd[2]);
-		if (strcmp(handle, ic->acc->user) == 0) {
-			return 1; /* That's me! */
-
-		}
-		http_decode(cmd[3]);
-		imcb_rename_buddy(ic, handle, cmd[3]);
-
-		st = msn_away_state_by_code(cmd[1]);
-		if (!st) {
-			/* FIXME: Warn/Bomb about unknown away state? */
-			st = msn_away_state_list + 1;
-		}
-
-		imcb_buddy_status(ic, handle, OPT_LOGGED_IN |
-		                  (st != msn_away_state_list ? OPT_AWAY : 0) |
-		                  (cap & 1 ? OPT_MOBILE : 0),
-		                  st->name, NULL);
-
-		msn_sb_stop_keepalives(msn_sb_by_handle(ic, handle));
-	} else if (strcmp(cmd[0], "FLN") == 0) {
-		const char *handle;
-
-		if (cmd[1] == NULL) {
-			return 1;
-		}
-
-		handle = msn_normalize_handle(cmd[1]);
-		imcb_buddy_status(ic, handle, 0, NULL, NULL);
-		msn_sb_start_keepalives(msn_sb_by_handle(ic, handle), TRUE);
-	} else if (strcmp(cmd[0], "RNG") == 0) {
-		struct msn_switchboard *sb;
-		char *server;
-		int session, port;
-
-		if (num_parts < 7) {
-			imcb_error(ic, "Syntax error");
-			imc_logout(ic, TRUE);
-			return(0);
-		}
-
-		session = atoi(cmd[1]);
-
-		server = strchr(cmd[2], ':');
-		if (!server) {
-			imcb_error(ic, "Syntax error");
-			imc_logout(ic, TRUE);
-			return(0);
-		}
-		*server = 0;
-		port = atoi(server + 1);
-		server = cmd[2];
-
-		if (strcmp(cmd[3], "CKI") != 0) {
-			imcb_error(ic, "Unknown authentication method for switchboard");
-			imc_logout(ic, TRUE);
-			return(0);
-		}
-
-		debug("Got a call from %s (session %d). Key = %s", cmd[5], session, cmd[4]);
-
-		if ((sb = msn_sb_create(ic, server, port, cmd[4], session)) == NULL) {
-			/* Although this isn't strictly fatal for the NS connection, it's
-			   definitely something serious (we ran out of file descriptors?). */
-			imcb_error(ic, "Could not create new switchboard");
-			imc_logout(ic, TRUE);
-			return(0);
-		} else {
-			sb->who = g_strdup(msn_normalize_handle(cmd[5]));
-		}
+	} else if (strcmp(cmd[0], "QRY") == 0) {
+		/* CONGRATULATIONS */
 	} else if (strcmp(cmd[0], "OUT") == 0) {
-		int allow_reconnect = TRUE;
-
-		if (cmd[1] && strcmp(cmd[1], "OTH") == 0) {
-			imcb_error(ic, "Someone else logged in with your account");
-			allow_reconnect = FALSE;
-		} else if (cmd[1] && strcmp(cmd[1], "SSD") == 0) {
-			imcb_error(ic, "Terminating session because of server shutdown");
-		} else {
-			imcb_error(ic, "Session terminated by remote server (%s)",
-			           cmd[1] ? cmd[1] : "reason unknown)");
-		}
-
-		imc_logout(ic, allow_reconnect);
+		imcb_error(ic, "Session terminated by remote server (%s)", cmd[1] ? cmd[1] : "reason unknown");
+		imc_logout(ic, TRUE);
 		return(0);
-	} else if (strcmp(cmd[0], "IPG") == 0) {
-		imcb_error(ic, "Received IPG command, we don't handle them yet.");
-
-		handler->msglen = atoi(cmd[1]);
-
-		if (handler->msglen <= 0) {
-			imcb_error(ic, "Syntax error");
-			imc_logout(ic, TRUE);
-			return(0);
-		}
-	}
-#if 0
-	else if (strcmp(cmd[0], "ADG") == 0) {
-		char *group = g_strdup(cmd[3]);
-		int groupnum, i;
-		GSList *l, *next;
-
-		http_decode(group);
-		if (sscanf(cmd[4], "%d", &groupnum) == 1) {
-			if (groupnum >= md->groupcount) {
-				md->grouplist = g_renew(char *, md->grouplist, groupnum + 1);
-				for (i = md->groupcount; i <= groupnum; i++) {
-					md->grouplist[i] = NULL;
-				}
-				md->groupcount = groupnum + 1;
-			}
-			g_free(md->grouplist[groupnum]);
-			md->grouplist[groupnum] = group;
-		} else {
-			/* Shouldn't happen, but if it does, give up on the group. */
-			g_free(group);
-			imcb_error(ic, "Syntax error");
-			imc_logout(ic, TRUE);
-			return 0;
-		}
-
-		for (l = md->grpq; l; l = next) {
-			struct msn_groupadd *ga = l->data;
-			next = l->next;
-			if (g_strcasecmp(ga->group, group) == 0) {
-				if (!msn_buddy_list_add(ic, "FL", ga->who, ga->who, group)) {
-					return 0;
-				}
-
-				g_free(ga->group);
-				g_free(ga->who);
-				g_free(ga);
-				md->grpq = g_slist_remove(md->grpq, ga);
-			}
-		}
-	}
-#endif
-	else if (strcmp(cmd[0], "GCF") == 0) {
+	} else if (strcmp(cmd[0], "GCF") == 0) {
 		/* Coming up is cmd[2] bytes of stuff we're supposed to
 		   censore. Meh. */
-		handler->msglen = atoi(cmd[2]);
-	} else if (strcmp(cmd[0], "UBX") == 0) {
-		/* Status message. */
+		md->msglen = atoi(cmd[2]);
+	} else if ((strcmp(cmd[0], "NFY") == 0) || (strcmp(cmd[0], "SDG") == 0)) {
 		if (num_parts >= 3) {
-			handler->msglen = atoi(cmd[2]);
+			md->msglen = atoi(cmd[2]);
+		}
+	} else if (strcmp(cmd[0], "PUT") == 0) {
+		if (num_parts >= 4) {
+			md->msglen = atoi(cmd[3]);
 		}
 	} else if (strcmp(cmd[0], "NOT") == 0) {
-		/* Some kind of notification, poorly documented but
-		   apparently used to announce address book changes. */
 		if (num_parts >= 2) {
-			handler->msglen = atoi(cmd[1]);
-		}
-	} else if (strcmp(cmd[0], "UBM") == 0) {
-		if (num_parts >= 7) {
-			handler->msglen = atoi(cmd[6]);
+			md->msglen = atoi(cmd[1]);
 		}
 	} else if (strcmp(cmd[0], "QNG") == 0) {
@@ -516,8 +337,8 @@
 		/* Oh yes, errors can have payloads too now. Discard them for now. */
 		if (num_parts >= 3) {
-			handler->msglen = atoi(cmd[2]);
+			md->msglen = atoi(cmd[2]);
 		}
 	} else {
-		/* debug( "Received unknown command from main server: %s", cmd[0] ); */
+		imcb_error(ic, "Received unknown command from main server: %s", cmd[0]);
 	}
 
@@ -525,7 +346,7 @@
 }
 
-static int msn_ns_message(struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts)
-{
-	struct im_connection *ic = handler->data;
+int msn_ns_message(struct msn_data *md, char *msg, int msglen, char **cmd, int num_parts)
+{
+	struct im_connection *ic = md->ic;
 	char *body;
 	int blen = 0;
@@ -598,56 +419,5 @@
 				}
 			} else if (g_strncasecmp(ct, "text/x-msmsgsactivemailnotification", 35) == 0) {
-			} else if (g_strncasecmp(ct, "text/x-msmsgsinitialmdatanotification", 37) == 0 ||
-			           g_strncasecmp(ct, "text/x-msmsgsoimnotification", 28) == 0) {
-				/* We received an offline message. Or at least notification
-				   that there is one waiting for us. Fetching the message(s)
-				   and purging them from the server is a lot of SOAPy work
-				   not worth doing IMHO. Also I thought it was possible to
-				   have the notification server send them directly, I was
-				   pretty sure I saw Pidgin do it..
-
-				   At least give a notification for now, seems like a
-				   reasonable thing to do. Only problem is, they'll keep
-				   coming back at login time until you read them using a
-				   different client. :-( */
-
-				char *xml = get_rfc822_header(body, "Mail-Data:", blen);
-				struct xt_node *md, *m;
-
-				if (!xml) {
-					return 1;
-				}
-				md = xt_from_string(xml, 0);
-				if (!md) {
-					return 1;
-				}
-
-				for (m = md->children; (m = xt_find_node(m, "M")); m = m->next) {
-					struct xt_node *e = xt_find_node(m->children, "E");
-					struct xt_node *rt = xt_find_node(m->children, "RT");
-					struct tm tp;
-					time_t msgtime = 0;
-
-					if (!e || !e->text) {
-						continue;
-					}
-
-					memset(&tp, 0, sizeof(tp));
-					if (rt && rt->text &&
-					    sscanf(rt->text, "%4d-%2d-%2dT%2d:%2d:%2d.",
-					           &tp.tm_year, &tp.tm_mon, &tp.tm_mday,
-					           &tp.tm_hour, &tp.tm_min, &tp.tm_sec) == 6) {
-						tp.tm_year -= 1900;
-						tp.tm_mon--;
-						msgtime = mktime_utc(&tp);
-
-					}
-					imcb_buddy_msg(ic, e->text,
-					               "<< \002BitlBee\002 - Received offline message. BitlBee can't show these. >>", 0,
-					               msgtime);
-				}
-
-				g_free(xml);
-				xt_free_node(md);
+				/* Notification that a message has been read... Ignore it */
 			} else {
 				debug("Can't handle %s packet from notification server", ct);
@@ -656,16 +426,4 @@
 			g_free(ct);
 		}
-	} else if (strcmp(cmd[0], "UBX") == 0) {
-		struct xt_node *ubx, *psm;
-		char *psm_text = NULL;
-
-		ubx = xt_from_string(msg, msglen);
-		if (ubx && strcmp(ubx->name, "Data") == 0 &&
-		    (psm = xt_find_node(ubx->children, "PSM"))) {
-			psm_text = psm->text;
-		}
-
-		imcb_buddy_status_msg(ic, msn_normalize_handle(cmd[1]), psm_text);
-		xt_free_node(ubx);
 	} else if (strcmp(cmd[0], "ADL") == 0) {
 		struct xt_node *adl, *d, *c;
@@ -716,33 +474,114 @@
 			}
 		}
-	} else if (strcmp(cmd[0], "UBM") == 0) {
-		/* This one will give us msgs from federated networks. Technically
-		   it should also get us offline messages, but I don't know how
-		   I can signal MSN servers to use it. */
-		char *ct, *handle;
-
-		if (strcmp(cmd[1], ic->acc->user) == 0) {
-			/* With MPOP, you'll get copies of your own msgs from other
-			   sessions. Discard those at least for now. */
-			return 1;
-		}
-
-		ct = get_rfc822_header(msg, "Content-Type", msglen);
-		if (strncmp(ct, "text/plain", 10) != 0) {
-			/* Typing notification or something? */
-			g_free(ct);
-			return 1;
-		}
-		if (strcmp(cmd[2], "1") != 0) {
-			handle = g_strdup_printf("%s:%s", cmd[2], cmd[1]);
-		} else {
-			handle = g_strdup(cmd[1]);
-		}
-
-		imcb_buddy_msg(ic, handle, body, 0, 0);
-		g_free(handle);
+	} else if ((strcmp(cmd[0], "SDG") == 0) || (strcmp(cmd[0], "NFY") == 0)) {
+		msn_ns_structured_message(md, msg, msglen, cmd);
 	}
 
 	return 1;
+}
+
+static void msn_ns_structured_message(struct msn_data *md, char *msg, int msglen, char **cmd)
+{
+	char **parts = NULL;
+	char *semicolon = NULL;
+	char *action = NULL;
+	char *from = NULL;
+	char *who = NULL;
+
+	parts = g_strsplit(msg, "\r\n\r\n", 4);
+
+	if (!(from = get_rfc822_header(parts[0], "From", 0))) {
+		goto cleanup;
+	}
+
+	/* either the semicolon or the end of the string */
+	semicolon = strchr(from, ';') ? : (from + strlen(from));
+
+	who = g_strndup(from + 2, semicolon - from - 2);
+
+	if ((strcmp(cmd[0], "SDG") == 0) && (action = get_rfc822_header(parts[2], "Message-Type", 0))) {
+		msn_ns_sdg(md, who, parts, action);
+
+	} else if ((strcmp(cmd[0], "NFY") == 0) && (action = get_rfc822_header(parts[2], "Uri", 0))) {
+		gboolean is_put = (strcmp(cmd[1], "PUT") == 0);
+		msn_ns_nfy(md, who, parts, action, is_put);
+	}
+
+cleanup:
+	g_strfreev(parts);
+	g_free(action);
+	g_free(from);
+	g_free(who);
+}
+
+static void msn_ns_sdg(struct msn_data *md, char *who, char **parts, char *action)
+{
+	struct im_connection *ic = md->ic;
+
+	if (strcmp(action, "Control/Typing") == 0) {
+		imcb_buddy_typing(ic, who, OPT_TYPING);
+	} else if (strcmp(action, "Text") == 0) {
+		imcb_buddy_msg(ic, who, parts[3], 0, 0);
+	}
+}
+
+static void msn_ns_nfy(struct msn_data *md, char *who, char **parts, char *action, gboolean is_put)
+{
+	struct im_connection *ic = md->ic;
+	struct xt_node *body = NULL;
+	struct xt_node *s = NULL;
+	const char *state = NULL;
+	char *nick = NULL;
+	char *psm = NULL;
+	int flags = OPT_LOGGED_IN;
+
+	if (strcmp(action, "/user") != 0) {
+		return;
+	}
+
+	if (!(body = xt_from_string(parts[3], 0))) {
+		goto cleanup;
+	}
+
+	s = body->children;
+	while ((s = xt_find_node(s, "s"))) {
+		struct xt_node *s2;
+		char *n = xt_find_attr(s, "n");  /* service name: IM, PE, etc */
+
+		if (strcmp(n, "IM") == 0) {
+			/* IM has basic presence information */
+			if (!is_put) {
+				/* NFY DEL with a <s> usually means log out from the last endpoint */
+				flags &= ~OPT_LOGGED_IN;
+				break;
+			}
+
+			s2 = xt_find_node(s->children, "Status");
+			if (s2 && s2->text_len) {
+				const struct msn_away_state *msn_state = msn_away_state_by_code(s2->text);
+				state = msn_state->name;
+				if (msn_state != msn_away_state_list) {
+					flags |= OPT_AWAY;
+				}
+			}
+		} else if (strcmp(n, "PE") == 0) {
+			if ((s2 = xt_find_node(s->children, "PSM")) && s2->text_len) {
+				psm = s2->text;
+			}
+			if ((s2 = xt_find_node(s->children, "FriendlyName")) && s2->text_len) {
+				nick = s2->text;
+			}
+		}
+		s = s->next;
+	}
+
+	imcb_buddy_status(ic, who, flags, state, psm);
+
+	if (nick) {
+		imcb_rename_buddy(ic, who, nick);
+	}
+
+cleanup:
+	xt_free_node(body);
 }
 
@@ -768,6 +607,4 @@
 void msn_auth_got_contact_list(struct im_connection *ic)
 {
-	struct msn_data *md;
-
 	/* Dead connection? */
 	if (g_slist_find(msn_connections, ic) == NULL) {
@@ -775,11 +612,11 @@
 	}
 
-	md = ic->proto_data;
-	msn_ns_write(ic, -1, "BLP %d %s\r\n", ++md->trId, "BL");
+	msn_ns_send_adl_start(ic);
+	msn_ns_finish_login(ic);
 }
 
 static gboolean msn_ns_send_adl_1(gpointer key, gpointer value, gpointer data)
 {
-	struct xt_node *adl = data, *d, *c;
+	struct xt_node *adl = data, *d, *c, *s;
 	struct bee_user *bu = value;
 	struct msn_buddy_data *bd = bu->data;
@@ -810,6 +647,9 @@
 	c = xt_new_node("c", NULL, NULL);
 	xt_add_attr(c, "n", handle);
-	xt_add_attr(c, "l", l);
 	xt_add_attr(c, "t", "1");   /* FIXME: Network type, i.e. 32 for Y!MSG */
+	s = xt_new_node("s", NULL, NULL);
+	xt_add_attr(s, "n", "IM");
+	xt_add_attr(s, "l", l);
+	xt_insert_child(c, s);
 	xt_insert_child(d, c);
 
@@ -881,9 +721,5 @@
 
 	if ((md->flags & MSN_DONE_ADL) && (md->flags & MSN_GOT_PROFILE)) {
-		if (md->flags & MSN_EMAIL_UNVERIFIED) {
-			imcb_connected(ic);
-		} else {
-			return msn_ns_set_display_name(ic, set_getstr(&ic->acc->set, "display_name"));
-		}
+		imcb_connected(ic);
 	}
 
@@ -891,9 +727,10 @@
 }
 
+// TODO: typing notifications, nudges lol, etc
 int msn_ns_sendmessage(struct im_connection *ic, bee_user_t *bu, const char *text)
 {
 	struct msn_data *md = ic->proto_data;
-	int type = 0;
-	char *buf, *handle;
+	int retval = 0;
+	char *buf;
 
 	if (strncmp(text, "\r\r\r", 3) == 0) {
@@ -903,50 +740,7 @@
 	}
 
-	/* This might be a federated contact. Get its network number,
-	   prefixed to bu->handle with a colon. Default is 1. */
-	for (handle = bu->handle; g_ascii_isdigit(*handle); handle++) {
-		type = type * 10 + *handle - '0';
-	}
-	if (*handle == ':') {
-		handle++;
-	} else {
-		type = 1;
-	}
-
-	buf = g_strdup_printf("%s%s", MSN_MESSAGE_HEADERS, text);
-
-	if (msn_ns_write(ic, -1, "UUM %d %s %d %d %zd\r\n%s",
-	                 ++md->trId, handle, type,
-	                 1,          /* type == IM (not nudge/typing) */
-	                 strlen(buf), buf)) {
-		return 1;
-	} else {
-		return 0;
-	}
-}
-
-void msn_ns_oim_send_queue(struct im_connection *ic, GSList **msgq)
-{
-	GSList *l;
-
-	for (l = *msgq; l; l = l->next) {
-		struct msn_message *m = l->data;
-		bee_user_t *bu = bee_user_by_handle(ic->bee, ic, m->who);
-
-		if (bu) {
-			if (!msn_ns_sendmessage(ic, bu, m->text)) {
-				return;
-			}
-		}
-	}
-
-	while (*msgq != NULL) {
-		struct msn_message *m = (*msgq)->data;
-
-		g_free(m->who);
-		g_free(m->text);
-		g_free(m);
-
-		*msgq = g_slist_remove(*msgq, m);
-	}
-}
+	buf = g_strdup_printf(MSN_MESSAGE_HEADERS, bu->handle, ic->acc->user, md->uuid, strlen(text), text);
+	retval = msn_ns_write(ic, -1, "SDG %d %zd\r\n%s", ++md->trId, strlen(buf), buf);
+	g_free(buf);
+	return retval;
+}
Index: protocols/msn/sb.c
===================================================================
--- protocols/msn/sb.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ 	(revision )
@@ -1,751 +1,0 @@
-/********************************************************************\
-  * BitlBee -- An IRC to other IM-networks gateway                     *
-  *                                                                    *
-  * Copyright 2002-2012 Wilmer van der Gaast and others                *
-  \********************************************************************/
-
-/* MSN module - Switchboard server callbacks and utilities              */
-
-/*
-  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 with
-  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
-  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
-  Fifth Floor, Boston, MA  02110-1301  USA
-*/
-
-#include <ctype.h>
-#include "nogaim.h"
-#include "msn.h"
-#include "md5.h"
-#include "soap.h"
-#include "invitation.h"
-
-static gboolean msn_sb_callback(gpointer data, gint source, b_input_condition cond);
-static int msn_sb_command(struct msn_handler_data *handler, char **cmd, int num_parts);
-static int msn_sb_message(struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts);
-
-int msn_sb_write(struct msn_switchboard *sb, const char *fmt, ...)
-{
-	va_list params;
-	char *out;
-	size_t len;
-	int st;
-
-	va_start(params, fmt);
-	out = g_strdup_vprintf(fmt, params);
-	va_end(params);
-
-	if (getenv("BITLBEE_DEBUG")) {
-		fprintf(stderr, "->SB%d:%s\n", sb->fd, out);
-	}
-
-	len = strlen(out);
-	st = write(sb->fd, out, len);
-	g_free(out);
-	if (st != len) {
-		msn_sb_destroy(sb);
-		return 0;
-	}
-
-	return 1;
-}
-
-int msn_sb_write_msg(struct im_connection *ic, struct msn_message *m)
-{
-	struct msn_data *md = ic->proto_data;
-	struct msn_switchboard *sb;
-
-	/* FIXME: *CHECK* the reliability of using spare sb's! */
-	if ((sb = msn_sb_spare(ic))) {
-		debug("Trying to use a spare switchboard to message %s", m->who);
-
-		sb->who = g_strdup(m->who);
-		if (msn_sb_write(sb, "CAL %d %s\r\n", ++sb->trId, m->who)) {
-			/* He/She should join the switchboard soon, let's queue the message. */
-			sb->msgq = g_slist_append(sb->msgq, m);
-			return(1);
-		}
-	}
-
-	debug("Creating a new switchboard to message %s", m->who);
-
-	/* If we reach this line, there was no spare switchboard, so let's make one. */
-	if (!msn_ns_write(ic, -1, "XFR %d SB\r\n", ++md->trId)) {
-		g_free(m->who);
-		g_free(m->text);
-		g_free(m);
-
-		return(0);
-	}
-
-	/* And queue the message to md. We'll pick it up when the switchboard comes up. */
-	md->msgq = g_slist_append(md->msgq, m);
-
-	/* FIXME: If the switchboard creation fails, the message will not be sent. */
-
-	return(1);
-}
-
-struct msn_switchboard *msn_sb_create(struct im_connection *ic, char *host, int port, char *key, int session)
-{
-	struct msn_data *md = ic->proto_data;
-	struct msn_switchboard *sb = g_new0(struct msn_switchboard, 1);
-
-	sb->fd = proxy_connect(host, port, msn_sb_connected, sb);
-	if (sb->fd < 0) {
-		g_free(sb);
-		return(NULL);
-	}
-
-	sb->ic = ic;
-	sb->key = g_strdup(key);
-	sb->session = session;
-
-	msn_switchboards = g_slist_append(msn_switchboards, sb);
-	md->switchboards = g_slist_append(md->switchboards, sb);
-
-	return(sb);
-}
-
-struct msn_switchboard *msn_sb_by_handle(struct im_connection *ic, const char *handle)
-{
-	struct msn_data *md = ic->proto_data;
-	struct msn_switchboard *sb;
-	GSList *l;
-
-	for (l = md->switchboards; l; l = l->next) {
-		sb = l->data;
-		if (sb->who && strcmp(sb->who, handle) == 0) {
-			return(sb);
-		}
-	}
-
-	return(NULL);
-}
-
-struct msn_switchboard *msn_sb_by_chat(struct groupchat *c)
-{
-	struct msn_data *md = c->ic->proto_data;
-	struct msn_switchboard *sb;
-	GSList *l;
-
-	for (l = md->switchboards; l; l = l->next) {
-		sb = l->data;
-		if (sb->chat == c) {
-			return(sb);
-		}
-	}
-
-	return(NULL);
-}
-
-struct msn_switchboard *msn_sb_spare(struct im_connection *ic)
-{
-	struct msn_data *md = ic->proto_data;
-	struct msn_switchboard *sb;
-	GSList *l;
-
-	for (l = md->switchboards; l; l = l->next) {
-		sb = l->data;
-		if (!sb->who && !sb->chat) {
-			return(sb);
-		}
-	}
-
-	return(NULL);
-}
-
-int msn_sb_sendmessage(struct msn_switchboard *sb, char *text)
-{
-	if (sb->ready) {
-		char *buf;
-		int i, j;
-
-		/* Build the message. Convert LF to CR-LF for normal messages. */
-		if (strcmp(text, TYPING_NOTIFICATION_MESSAGE) == 0) {
-			i = strlen(MSN_TYPING_HEADERS) + strlen(sb->ic->acc->user);
-			buf = g_new0(char, i);
-			i = g_snprintf(buf, i, MSN_TYPING_HEADERS, sb->ic->acc->user);
-		} else if (strcmp(text, NUDGE_MESSAGE) == 0) {
-			buf = g_strdup(MSN_NUDGE_HEADERS);
-			i = strlen(buf);
-		} else if (strcmp(text, SB_KEEPALIVE_MESSAGE) == 0) {
-			buf = g_strdup(MSN_SB_KEEPALIVE_HEADERS);
-			i = strlen(buf);
-		} else if (strncmp(text, MSN_INVITE_HEADERS, sizeof(MSN_INVITE_HEADERS) - 1) == 0) {
-			buf = g_strdup(text);
-			i = strlen(buf);
-		} else {
-			buf = g_new0(char, sizeof(MSN_MESSAGE_HEADERS) + strlen(text) * 2 + 1);
-			i = strlen(MSN_MESSAGE_HEADERS);
-
-			strcpy(buf, MSN_MESSAGE_HEADERS);
-			for (j = 0; text[j]; j++) {
-				if (text[j] == '\n') {
-					buf[i++] = '\r';
-				}
-
-				buf[i++] = text[j];
-			}
-		}
-
-		/* Build the final packet (MSG command + the message). */
-		if (msn_sb_write(sb, "MSG %d N %d\r\n%s", ++sb->trId, i, buf)) {
-			g_free(buf);
-			return 1;
-		} else {
-			g_free(buf);
-			return 0;
-		}
-	} else if (sb->who) {
-		struct msn_message *m = g_new0(struct msn_message, 1);
-
-		m->who = g_strdup("");
-		m->text = g_strdup(text);
-		sb->msgq = g_slist_append(sb->msgq, m);
-
-		return(1);
-	} else {
-		return(0);
-	}
-}
-
-struct groupchat *msn_sb_to_chat(struct msn_switchboard *sb)
-{
-	struct im_connection *ic = sb->ic;
-	struct groupchat *c = NULL;
-	char buf[1024];
-
-	/* Create the groupchat structure. */
-	g_snprintf(buf, sizeof(buf), "MSN groupchat session %d", sb->session);
-	if (sb->who) {
-		c = bee_chat_by_title(ic->bee, ic, sb->who);
-	}
-	if (c && !msn_sb_by_chat(c)) {
-		sb->chat = c;
-	} else {
-		sb->chat = imcb_chat_new(ic, buf);
-	}
-
-	/* Populate the channel. */
-	if (sb->who) {
-		imcb_chat_add_buddy(sb->chat, sb->who);
-	}
-	imcb_chat_add_buddy(sb->chat, ic->acc->user);
-
-	/* And make sure the switchboard doesn't look like a regular chat anymore. */
-	if (sb->who) {
-		g_free(sb->who);
-		sb->who = NULL;
-	}
-
-	return sb->chat;
-}
-
-void msn_sb_destroy(struct msn_switchboard *sb)
-{
-	struct im_connection *ic = sb->ic;
-	struct msn_data *md = ic->proto_data;
-
-	debug("Destroying switchboard: %s", sb->who ? sb->who : sb->key ? sb->key : "");
-
-	msn_msgq_purge(ic, &sb->msgq);
-	msn_sb_stop_keepalives(sb);
-
-	if (sb->key) {
-		g_free(sb->key);
-	}
-	if (sb->who) {
-		g_free(sb->who);
-	}
-
-	if (sb->chat) {
-		imcb_chat_free(sb->chat);
-	}
-
-	if (sb->handler) {
-		if (sb->handler->rxq) {
-			g_free(sb->handler->rxq);
-		}
-		if (sb->handler->cmd_text) {
-			g_free(sb->handler->cmd_text);
-		}
-		g_free(sb->handler);
-	}
-
-	if (sb->inp) {
-		b_event_remove(sb->inp);
-	}
-	closesocket(sb->fd);
-
-	msn_switchboards = g_slist_remove(msn_switchboards, sb);
-	md->switchboards = g_slist_remove(md->switchboards, sb);
-	g_free(sb);
-}
-
-gboolean msn_sb_connected(gpointer data, gint source, b_input_condition cond)
-{
-	struct msn_switchboard *sb = data;
-	struct im_connection *ic;
-	struct msn_data *md;
-	char buf[1024];
-
-	/* Are we still alive? */
-	if (!g_slist_find(msn_switchboards, sb)) {
-		return FALSE;
-	}
-
-	ic = sb->ic;
-	md = ic->proto_data;
-
-	if (source != sb->fd) {
-		debug("Error %d while connecting to switchboard server", 1);
-		msn_sb_destroy(sb);
-		return FALSE;
-	}
-
-	/* Prepare the callback */
-	sb->handler = g_new0(struct msn_handler_data, 1);
-	sb->handler->fd = sb->fd;
-	sb->handler->rxq = g_new0(char, 1);
-	sb->handler->data = sb;
-	sb->handler->exec_command = msn_sb_command;
-	sb->handler->exec_message = msn_sb_message;
-
-	if (sb->session == MSN_SB_NEW) {
-		g_snprintf(buf, sizeof(buf), "USR %d %s;{%s} %s\r\n", ++sb->trId, ic->acc->user, md->uuid, sb->key);
-	} else {
-		g_snprintf(buf, sizeof(buf), "ANS %d %s;{%s} %s %d\r\n", ++sb->trId, ic->acc->user, md->uuid, sb->key,
-		           sb->session);
-	}
-
-	if (msn_sb_write(sb, "%s", buf)) {
-		sb->inp = b_input_add(sb->fd, B_EV_IO_READ, msn_sb_callback, sb);
-	} else {
-		debug("Error %d while connecting to switchboard server", 2);
-	}
-
-	return FALSE;
-}
-
-static gboolean msn_sb_callback(gpointer data, gint source, b_input_condition cond)
-{
-	struct msn_switchboard *sb = data;
-	struct im_connection *ic = sb->ic;
-	struct msn_data *md = ic->proto_data;
-
-	if (msn_handler(sb->handler) != -1) {
-		return TRUE;
-	}
-
-	if (sb->msgq != NULL) {
-		time_t now = time(NULL);
-
-		if (now - md->first_sb_failure > 600) {
-			/* It's not really the first one, but the start of this "series".
-			   With this, the warning below will be shown only if this happens
-			   at least three times in ten minutes. This algorithm isn't
-			   perfect, but for this purpose it will do. */
-			md->first_sb_failure = now;
-			md->sb_failures = 0;
-		}
-
-		debug("Error: Switchboard died");
-		if (++md->sb_failures >= 3) {
-			imcb_log(ic, "Warning: Many switchboard failures on MSN connection. "
-			         "There might be problems delivering your messages.");
-		}
-
-		if (md->msgq == NULL) {
-			md->msgq = sb->msgq;
-		} else {
-			GSList *l;
-
-			for (l = md->msgq; l->next; l = l->next) {
-				;
-			}
-			l->next = sb->msgq;
-		}
-		sb->msgq = NULL;
-
-		debug("Moved queued messages back to the main queue, "
-		      "creating a new switchboard to retry.");
-		if (!msn_ns_write(ic, -1, "XFR %d SB\r\n", ++md->trId)) {
-			return FALSE;
-		}
-	}
-
-	msn_sb_destroy(sb);
-	return FALSE;
-}
-
-static int msn_sb_command(struct msn_handler_data *handler, char **cmd, int num_parts)
-{
-	struct msn_switchboard *sb = handler->data;
-	struct im_connection *ic = sb->ic;
-
-	if (!num_parts) {
-		/* Hrrm... Empty command...? Ignore? */
-		return(1);
-	}
-
-	if (strcmp(cmd[0], "XFR") == 0) {
-		imcb_error(ic,
-		           "Received an XFR from a switchboard server, unable to comply! This is likely to be a bug, please report it!");
-		imc_logout(ic, TRUE);
-		return(0);
-	} else if (strcmp(cmd[0], "USR") == 0) {
-		if (num_parts < 5) {
-			msn_sb_destroy(sb);
-			return(0);
-		}
-
-		if (strcmp(cmd[2], "OK") != 0) {
-			msn_sb_destroy(sb);
-			return(0);
-		}
-
-		if (sb->who) {
-			return msn_sb_write(sb, "CAL %d %s\r\n", ++sb->trId, sb->who);
-		} else {
-			debug("Just created a switchboard, but I don't know what to do with it.");
-		}
-	} else if (strcmp(cmd[0], "IRO") == 0) {
-		int num, tot;
-
-		if (num_parts < 6) {
-			msn_sb_destroy(sb);
-			return(0);
-		}
-
-		num = atoi(cmd[2]);
-		tot = atoi(cmd[3]);
-
-		if (tot <= 0) {
-			msn_sb_destroy(sb);
-			return(0);
-		} else if (tot > 1) {
-			char buf[1024];
-
-			/* For as much as I understand this MPOP stuff now, a
-			   switchboard has two (or more) roster entries per
-			   participant. One "bare JID" and one JID;UUID. Ignore
-			   the latter. */
-			if (!strchr(cmd[4], ';')) {
-				/* HACK: Since even 1:1 chats now have >2 participants
-				   (ourselves included) it gets hard to tell them apart
-				   from rooms. Let's hope this is enough: */
-				if (sb->chat == NULL && num != tot) {
-					g_snprintf(buf, sizeof(buf), "MSN groupchat session %d", sb->session);
-					sb->chat = imcb_chat_new(ic, buf);
-
-					g_free(sb->who);
-					sb->who = NULL;
-				}
-
-				if (sb->chat) {
-					imcb_chat_add_buddy(sb->chat, cmd[4]);
-				}
-			}
-
-			/* We have the full roster, start showing the channel to
-			   the user. */
-			if (num == tot && sb->chat) {
-				imcb_chat_add_buddy(sb->chat, ic->acc->user);
-			}
-		}
-	} else if (strcmp(cmd[0], "ANS") == 0) {
-		if (num_parts < 3) {
-			msn_sb_destroy(sb);
-			return(0);
-		}
-
-		if (strcmp(cmd[2], "OK") != 0) {
-			debug("Switchboard server sent a negative ANS reply");
-			msn_sb_destroy(sb);
-			return(0);
-		}
-
-		sb->ready = 1;
-
-		msn_sb_start_keepalives(sb, FALSE);
-	} else if (strcmp(cmd[0], "CAL") == 0) {
-		if (num_parts < 4 || !g_ascii_isdigit(cmd[3][0])) {
-			msn_sb_destroy(sb);
-			return(0);
-		}
-
-		sb->session = atoi(cmd[3]);
-	} else if (strcmp(cmd[0], "JOI") == 0) {
-		if (num_parts < 3) {
-			msn_sb_destroy(sb);
-			return(0);
-		}
-
-		/* See IRO above. Handle "bare JIDs" only. */
-		if (strchr(cmd[1], ';')) {
-			return 1;
-		}
-
-		if (sb->who && g_strcasecmp(cmd[1], sb->who) == 0) {
-			/* The user we wanted to talk to is finally there, let's send the queued messages then. */
-			struct msn_message *m;
-			GSList *l;
-			int st = 1;
-
-			debug("%s arrived in the switchboard session, now sending queued message(s)", cmd[1]);
-
-			/* Without this, sendmessage() will put everything back on the queue... */
-			sb->ready = 1;
-
-			while ((l = sb->msgq)) {
-				m = l->data;
-				if (st) {
-					/* This hack is meant to convert a regular new chat into a groupchat */
-					if (strcmp(m->text, GROUPCHAT_SWITCHBOARD_MESSAGE) == 0) {
-						msn_sb_to_chat(sb);
-					} else {
-						st = msn_sb_sendmessage(sb, m->text);
-					}
-				}
-				g_free(m->text);
-				g_free(m->who);
-				g_free(m);
-
-				sb->msgq = g_slist_remove(sb->msgq, m);
-			}
-
-			msn_sb_start_keepalives(sb, FALSE);
-
-			return(st);
-		} else if (strcmp(cmd[1], ic->acc->user) == 0) {
-			/* Well, gee thanks. Thanks for letting me know I've arrived.. */
-		} else if (sb->who) {
-			debug("Converting chat with %s to a groupchat because %s joined the session.", sb->who, cmd[1]);
-
-			/* This SB is a one-to-one chat right now, but someone else is joining. */
-			msn_sb_to_chat(sb);
-
-			imcb_chat_add_buddy(sb->chat, cmd[1]);
-		} else if (sb->chat) {
-			imcb_chat_add_buddy(sb->chat, cmd[1]);
-			sb->ready = 1;
-		} else {
-			/* PANIC! */
-		}
-	} else if (strcmp(cmd[0], "MSG") == 0) {
-		if (num_parts < 4) {
-			msn_sb_destroy(sb);
-			return(0);
-		}
-
-		sb->handler->msglen = atoi(cmd[3]);
-
-		if (sb->handler->msglen <= 0) {
-			debug("Received a corrupted message on the switchboard, the switchboard will be closed");
-			msn_sb_destroy(sb);
-			return(0);
-		}
-	} else if (strcmp(cmd[0], "NAK") == 0) {
-		if (sb->who) {
-			imcb_log(ic, "The MSN servers could not deliver one of your messages to %s.", sb->who);
-		} else {
-			imcb_log(ic,
-			         "The MSN servers could not deliver one of your groupchat messages to all participants.");
-		}
-	} else if (strcmp(cmd[0], "BYE") == 0) {
-		if (num_parts < 2) {
-			msn_sb_destroy(sb);
-			return(0);
-		}
-
-		/* if( cmd[2] && *cmd[2] == '1' ) -=> Chat is being cleaned up because of idleness */
-
-		if (sb->who) {
-			msn_sb_stop_keepalives(sb);
-
-			/* This is a single-person chat, and the other person is leaving. */
-			g_free(sb->who);
-			sb->who = NULL;
-			sb->ready = 0;
-
-			debug("Person %s left the one-to-one switchboard connection. Keeping it around as a spare...",
-			      cmd[1]);
-
-			/* We could clean up the switchboard now, but keeping it around
-			   as a spare for a next conversation sounds more sane to me.
-			   The server will clean it up when it's idle for too long. */
-		} else if (sb->chat && !strchr(cmd[1], ';')) {
-			imcb_chat_remove_buddy(sb->chat, cmd[1], "");
-		} else {
-			/* PANIC! */
-		}
-	} else if (g_ascii_isdigit(cmd[0][0])) {
-		int num = atoi(cmd[0]);
-		const struct msn_status_code *err = msn_status_by_number(num);
-
-		/* If the person is offline, send an offline message instead,
-		   and don't report an error. */
-		if (num == 217) {
-			msn_ns_oim_send_queue(ic, &sb->msgq);
-		} else {
-			imcb_error(ic, "Error reported by switchboard server: %s", err->text);
-		}
-
-		if (err->flags & STATUS_SB_FATAL) {
-			msn_sb_destroy(sb);
-			return 0;
-		} else if (err->flags & STATUS_FATAL) {
-			imc_logout(ic, TRUE);
-			return 0;
-		} else if (err->flags & STATUS_SB_IM_SPARE) {
-			if (sb->who) {
-				/* Apparently some invitation failed. We might want to use this
-				   board later, so keep it as a spare. */
-				g_free(sb->who);
-				sb->who = NULL;
-
-				/* Also clear the msgq, otherwise someone else might get them. */
-				msn_msgq_purge(ic, &sb->msgq);
-			}
-
-			/* Do NOT return 0 here, we want to keep this sb. */
-		}
-	} else {
-		/* debug( "Received unknown command from switchboard server: %s", cmd[0] ); */
-	}
-
-	return(1);
-}
-
-static int msn_sb_message(struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts)
-{
-	struct msn_switchboard *sb = handler->data;
-	struct im_connection *ic = sb->ic;
-	char *body;
-
-	if (!num_parts) {
-		return(1);
-	}
-
-	if ((body = strstr(msg, "\r\n\r\n"))) {
-		body += 4;
-	}
-
-	if (strcmp(cmd[0], "MSG") == 0) {
-		char *ct = get_rfc822_header(msg, "Content-Type:", msglen);
-
-		if (!ct) {
-			return(1);
-		}
-
-		if (g_strncasecmp(ct, "text/plain", 10) == 0) {
-			g_free(ct);
-
-			if (!body) {
-				return(1);
-			}
-
-			if (sb->who) {
-				imcb_buddy_msg(ic, cmd[1], body, 0, 0);
-			} else if (sb->chat) {
-				imcb_chat_msg(sb->chat, cmd[1], body, 0, 0);
-			} else {
-				/* PANIC! */
-			}
-		}
-#if 0
-		// Disable MSN ft support for now.
-		else if (g_strncasecmp(ct, "text/x-msmsgsinvite", 19) == 0) {
-			char *command = get_rfc822_header(body, "Invitation-Command:", blen);
-			char *cookie = get_rfc822_header(body, "Invitation-Cookie:", blen);
-			unsigned int icookie;
-
-			g_free(ct);
-
-			/* Every invite should have both a Command and Cookie header */
-			if (!command || !cookie) {
-				g_free(command);
-				g_free(cookie);
-				imcb_log(ic, "Warning: No command or cookie from %s", sb->who);
-				return 1;
-			}
-
-			icookie = strtoul(cookie, NULL, 10);
-			g_free(cookie);
-
-			if (g_strncasecmp(command, "INVITE", 6) == 0) {
-				msn_invitation_invite(sb, cmd[1], icookie, body, blen);
-			} else if (g_strncasecmp(command, "ACCEPT", 6) == 0) {
-				msn_invitation_accept(sb, cmd[1], icookie, body, blen);
-			} else if (g_strncasecmp(command, "CANCEL", 6) == 0) {
-				msn_invitation_cancel(sb, cmd[1], icookie, body, blen);
-			} else {
-				imcb_log(ic, "Warning: Received invalid invitation with "
-				         "command %s from %s", command, sb->who);
-			}
-
-			g_free(command);
-		}
-#endif
-		else if (g_strncasecmp(ct, "application/x-msnmsgrp2p", 24) == 0) {
-			/* Not currently implemented. Don't warn about it since
-			   this seems to be used for avatars now. */
-			g_free(ct);
-		} else if (g_strncasecmp(ct, "text/x-msmsgscontrol", 20) == 0) {
-			char *who = get_rfc822_header(msg, "TypingUser:", msglen);
-
-			if (who) {
-				imcb_buddy_typing(ic, who, OPT_TYPING);
-				g_free(who);
-			}
-
-			g_free(ct);
-		} else {
-			g_free(ct);
-		}
-	}
-
-	return(1);
-}
-
-static gboolean msn_sb_keepalive(gpointer data, gint source, b_input_condition cond)
-{
-	struct msn_switchboard *sb = data;
-
-	return sb->ready && msn_sb_sendmessage(sb, SB_KEEPALIVE_MESSAGE);
-}
-
-void msn_sb_start_keepalives(struct msn_switchboard *sb, gboolean initial)
-{
-	bee_user_t *bu;
-
-	if (sb && sb->who && sb->keepalive == 0 &&
-	    (bu = bee_user_by_handle(sb->ic->bee, sb->ic, sb->who)) &&
-	    !(bu->flags & BEE_USER_ONLINE) &&
-	    set_getbool(&sb->ic->acc->set, "switchboard_keepalives")) {
-		if (initial) {
-			msn_sb_keepalive(sb, 0, 0);
-		}
-
-		sb->keepalive = b_timeout_add(20000, msn_sb_keepalive, sb);
-	}
-}
-
-void msn_sb_stop_keepalives(struct msn_switchboard *sb)
-{
-	if (sb && sb->keepalive > 0) {
-		b_event_remove(sb->keepalive);
-		sb->keepalive = 0;
-	}
-}
Index: protocols/msn/soap.c
===================================================================
--- protocols/msn/soap.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/msn/soap.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -209,4 +209,5 @@
 		return;
 	}
+	fprintf(stderr, "\n\x1b[90mSOAP:\n");
 
 	if (headers) {
@@ -225,4 +226,5 @@
 		xt_free_node(xt);
 	}
+	fprintf(stderr, "\n\x1b[97m\n");
 }
 
Index: protocols/msn/soap.h
===================================================================
--- protocols/msn/soap.h	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/msn/soap.h	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -167,5 +167,5 @@
 
 #define SOAP_MEMLIST_PAYLOAD \
-	"<FindMembership xmlns=\"http://www.msn.com/webservices/AddressBook\"><serviceFilter xmlns=\"http://www.msn.com/webservices/AddressBook\"><Types xmlns=\"http://www.msn.com/webservices/AddressBook\"><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">Messenger</ServiceType><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">Invitation</ServiceType><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">SocialNetwork</ServiceType><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">Space</ServiceType><ServiceType xmlns=\"http://www.msn.com/webservices/AddressBook\">Profile</ServiceType></Types></serviceFilter>" \
+	"<FindMembership xmlns=\"http://www.msn.com/webservices/AddressBook\"><serviceFilter><Types><ServiceType>Messenger</ServiceType><ServiceType>IMAvailability</ServiceType></Types></serviceFilter><expandMembership>true</expandMembership>" \
 	"</FindMembership>"
 
Index: protocols/msn/tables.c
===================================================================
--- protocols/msn/tables.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/msn/tables.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -73,10 +73,10 @@
 	{ 206, "Domain name missing",                                   0 },
 	{ 207, "Already logged in",                                     0 },
-	{ 208, "Invalid handle",                                        STATUS_SB_IM_SPARE },
+	{ 208, "Invalid handle",                                        0 },
 	{ 209, "Forbidden nickname",                                    0 },
 	{ 210, "Buddy list too long",                                   0 },
 	{ 215, "Handle is already in list",                             0 },
-	{ 216, "Handle is not in list",                                 STATUS_SB_IM_SPARE },
-	{ 217, "Person is off-line or non-existent",                    STATUS_SB_IM_SPARE },
+	{ 216, "Handle is not in list",                                 0 },
+	{ 217, "Person is off-line or non-existent",                    0 },
 	{ 218, "Already in that mode",                                  0 },
 	{ 219, "Handle is already in opposite list",                    0 },
@@ -113,5 +113,5 @@
 	{ 711, "Write is blocking",                                     STATUS_FATAL },
 	{ 712, "Session is overloaded",                                 STATUS_FATAL },
-	{ 713, "Calling too rapidly",                                   STATUS_SB_IM_SPARE },
+	{ 713, "Calling too rapidly",                                   0 },
 	{ 714, "Too many sessions",                                     STATUS_FATAL },
 	{ 715, "Not expected/Invalid argument/action",                  0 },
Index: protocols/nogaim.c
===================================================================
--- protocols/nogaim.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/nogaim.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -503,9 +503,7 @@
 };
 
-static void imcb_ask_auth_cb_no(void *data)
+static void imcb_ask_cb_free(void *data)
 {
 	struct imcb_ask_cb_data *cbd = data;
-
-	cbd->ic->acc->prpl->auth_deny(cbd->ic, cbd->handle);
 
 	g_free(cbd->handle);
@@ -513,4 +511,13 @@
 }
 
+static void imcb_ask_auth_cb_no(void *data)
+{
+	struct imcb_ask_cb_data *cbd = data;
+
+	cbd->ic->acc->prpl->auth_deny(cbd->ic, cbd->handle);
+
+	imcb_ask_cb_free(cbd);
+}
+
 static void imcb_ask_auth_cb_yes(void *data)
 {
@@ -519,6 +526,5 @@
 	cbd->ic->acc->prpl->auth_allow(cbd->ic, cbd->handle);
 
-	g_free(cbd->handle);
-	g_free(cbd);
+	imcb_ask_cb_free(cbd);
 }
 
@@ -540,12 +546,7 @@
 	data->handle = g_strdup(handle);
 	query_add((irc_t *) ic->bee->ui_data, ic, s,
-	          imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, g_free, data);
-}
-
-
-static void imcb_ask_add_cb_no(void *data)
-{
-	g_free(((struct imcb_ask_cb_data*) data)->handle);
-	g_free(data);
+	          imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, imcb_ask_cb_free, data);
+
+	g_free(s);
 }
 
@@ -556,10 +557,10 @@
 	cbd->ic->acc->prpl->add_buddy(cbd->ic, cbd->handle, NULL);
 
-	imcb_ask_add_cb_no(data);
+	imcb_ask_cb_free(data);
 }
 
 void imcb_ask_add(struct im_connection *ic, const char *handle, const char *realname)
 {
-	struct imcb_ask_cb_data *data = g_new0(struct imcb_ask_cb_data, 1);
+	struct imcb_ask_cb_data *data;
 	char *s;
 
@@ -569,4 +570,6 @@
 	}
 
+	data = g_new0(struct imcb_ask_cb_data, 1);
+
 	s = g_strdup_printf("The user %s is not in your buddy list yet. Do you want to add him/her now?", handle);
 
@@ -574,5 +577,7 @@
 	data->handle = g_strdup(handle);
 	query_add((irc_t *) ic->bee->ui_data, ic, s,
-	          imcb_ask_add_cb_yes, imcb_ask_add_cb_no, g_free, data);
+	          imcb_ask_add_cb_yes, imcb_ask_cb_free, imcb_ask_cb_free, data);
+
+	g_free(s);
 }
 
Index: protocols/purple/bpurple.h
===================================================================
--- protocols/purple/bpurple.h	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
+++ protocols/purple/bpurple.h	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -0,0 +1,17 @@
+#ifndef BPURPLE_H
+# define BPURPLE_H
+
+#include <purple.h>
+#include <glib.h>
+
+#define PURPLE_REQUEST_HANDLE "purple_request"
+
+struct purple_data
+{
+    PurpleAccount *account;
+
+    GHashTable *input_requests;
+    guint next_request_id;
+};
+
+#endif /* !BPURPLE_H */
Index: protocols/purple/ft-direct.c
===================================================================
--- protocols/purple/ft-direct.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/purple/ft-direct.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -28,4 +28,5 @@
 
 #include "bitlbee.h"
+#include "bpurple.h"
 
 #include <stdarg.h>
@@ -207,5 +208,5 @@
 void purple_transfer_request(struct im_connection *ic, file_transfer_t *ft, char *handle)
 {
-	PurpleAccount *pa = ic->proto_data;
+	struct purple_data *pd = ic->proto_data;
 	struct prpl_xfer_data *px;
 
@@ -213,5 +214,6 @@
 	   multi-threaded anyway. */
 	next_ft = ft;
-	serv_send_file(purple_account_get_connection(pa), handle, ft->file_name);
+	serv_send_file(purple_account_get_connection(pd->account), handle,
+	               ft->file_name);
 
 	ft->write = prpl_xfer_write;
Index: protocols/purple/ft.c
===================================================================
--- protocols/purple/ft.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/purple/ft.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -28,4 +28,5 @@
 
 #include "bitlbee.h"
+#include "bpurple.h"
 
 #include <stdarg.h>
@@ -285,10 +286,11 @@
 {
 	struct prpl_xfer_data *px = ft->data;
-	PurpleAccount *pa = px->ic->proto_data;
+	struct purple_data *pd = px->ic->proto_data;
 
 	/* xfer_new() will pick up this variable. It's a hack but we're not
 	   multi-threaded anyway. */
 	next_ft = ft;
-	serv_send_file(purple_account_get_connection(pa), px->handle, px->fn);
+	serv_send_file(purple_account_get_connection(pd->account),
+                   px->handle, px->fn);
 }
 
Index: protocols/purple/purple.c
===================================================================
--- protocols/purple/purple.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/purple/purple.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -23,4 +23,5 @@
 
 #include "bitlbee.h"
+#include "bpurple.h"
 #include "help.h"
 
@@ -39,10 +40,26 @@
 static char *set_eval_display_name(set_t *set, char *value);
 
+void purple_request_input_callback(guint id, struct im_connection *ic,
+                                   const char *message, const char *who);
+
+/* purple_request_input specific stuff */
+typedef void (*ri_callback_t)(gpointer, const gchar *);
+
+struct request_input_data {
+	ri_callback_t data_callback;
+	void *user_data;
+	struct im_connection *ic;
+	char *buddy;
+	guint id;
+};
+
 struct im_connection *purple_ic_by_pa(PurpleAccount *pa)
 {
 	GSList *i;
+	struct purple_data *pd;
 
 	for (i = purple_connections; i; i = i->next) {
-		if (((struct im_connection *) i->data)->proto_data == pa) {
+		pd = ((struct im_connection *) i->data)->proto_data;
+		if (pd->account == pa) {
 			return i->data;
 		}
@@ -290,5 +307,5 @@
 {
 	struct im_connection *ic = imcb_new(acc);
-	PurpleAccount *pa;
+	struct purple_data *pd;
 
 	if ((local_bee != NULL && local_bee != acc->bee) ||
@@ -306,18 +323,28 @@
 	purple_connections = g_slist_prepend(purple_connections, ic);
 
-	ic->proto_data = pa = purple_account_new(acc->user, (char *) acc->prpl->data);
-	purple_account_set_password(pa, acc->pass);
-	purple_sync_settings(acc, pa);
-
-	purple_account_set_enabled(pa, "BitlBee", TRUE);
+	ic->proto_data = pd = g_new0(struct purple_data, 1);
+	pd->account = purple_account_new(acc->user, (char *) acc->prpl->data);
+	pd->input_requests = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+	                                           NULL, g_free);
+	pd->next_request_id = 0;
+	purple_account_set_password(pd->account, acc->pass);
+	purple_sync_settings(acc, pd->account);
+
+	purple_account_set_enabled(pd->account, "BitlBee", TRUE);
 }
 
 static void purple_logout(struct im_connection *ic)
 {
-	PurpleAccount *pa = ic->proto_data;
-
-	purple_account_set_enabled(pa, "BitlBee", FALSE);
+	struct purple_data *pd = ic->proto_data;
+
+	if (!pd) {
+		return;
+	}
+
+	purple_account_set_enabled(pd->account, "BitlBee", FALSE);
 	purple_connections = g_slist_remove(purple_connections, ic);
-	purple_accounts_remove(pa);
+	purple_accounts_remove(pd->account);
+	g_hash_table_destroy(pd->input_requests);
+	g_free(pd);
 }
 
@@ -325,9 +352,16 @@
 {
 	PurpleConversation *conv;
+	struct purple_data *pd = ic->proto_data;
+
+	if (!strncmp(who, PURPLE_REQUEST_HANDLE, sizeof(PURPLE_REQUEST_HANDLE) - 1)) {
+		guint request_id = atoi(who + sizeof(PURPLE_REQUEST_HANDLE));
+		purple_request_input_callback(request_id, ic, message, who);
+		return 1;
+	}
 
 	if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
-	                                                  who, ic->proto_data)) == NULL) {
+	                                                  who, pd->account)) == NULL) {
 		conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
-		                               ic->proto_data, who);
+		                               pd->account, who);
 	}
 
@@ -339,8 +373,8 @@
 static GList *purple_away_states(struct im_connection *ic)
 {
-	PurpleAccount *pa = ic->proto_data;
+	struct purple_data *pd = ic->proto_data;
 	GList *st, *ret = NULL;
 
-	for (st = purple_account_get_status_types(pa); st; st = st->next) {
+	for (st = purple_account_get_status_types(pd->account); st; st = st->next) {
 		PurpleStatusPrimitive prim = purple_status_type_get_primitive(st->data);
 		if (prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE) {
@@ -354,6 +388,6 @@
 static void purple_set_away(struct im_connection *ic, char *state_txt, char *message)
 {
-	PurpleAccount *pa = ic->proto_data;
-	GList *status_types = purple_account_get_status_types(pa), *st;
+	struct purple_data *pd = ic->proto_data;
+	GList *status_types = purple_account_get_status_types(pd->account), *st;
 	PurpleStatusType *pst = NULL;
 	GList *args = NULL;
@@ -378,5 +412,6 @@
 	}
 
-	purple_account_set_status_list(pa, st ? purple_status_type_get_id(pst) : "away",
+	purple_account_set_status_list(pd->account,
+	                               st ? purple_status_type_get_id(pst) : "away",
 	                               TRUE, args);
 
@@ -447,4 +482,5 @@
 	PurpleBuddy *pb;
 	PurpleGroup *pg = NULL;
+	struct purple_data *pd = ic->proto_data;
 
 	if (group && !(pg = purple_find_group(group))) {
@@ -453,9 +489,9 @@
 	}
 
-	pb = purple_buddy_new((PurpleAccount *) ic->proto_data, who, NULL);
+	pb = purple_buddy_new(pd->account, who, NULL);
 	purple_blist_add_buddy(pb, NULL, pg, NULL);
-	purple_account_add_buddy((PurpleAccount *) ic->proto_data, pb);
-
-	purple_gg_buddylist_export(((PurpleAccount *) ic->proto_data)->gc);
+	purple_account_add_buddy(pd->account, pb);
+
+	purple_gg_buddylist_export(pd->account->gc);
 }
 
@@ -463,49 +499,52 @@
 {
 	PurpleBuddy *pb;
-
-	pb = purple_find_buddy((PurpleAccount *) ic->proto_data, who);
+	struct purple_data *pd = ic->proto_data;
+
+	pb = purple_find_buddy(pd->account, who);
 	if (pb != NULL) {
 		PurpleGroup *group;
 
 		group = purple_buddy_get_group(pb);
-		purple_account_remove_buddy((PurpleAccount *) ic->proto_data, pb, group);
+		purple_account_remove_buddy(pd->account, pb, group);
 
 		purple_blist_remove_buddy(pb);
 	}
 
-	purple_gg_buddylist_export(((PurpleAccount *) ic->proto_data)->gc);
+	purple_gg_buddylist_export(pd->account->gc);
 }
 
 static void purple_add_permit(struct im_connection *ic, char *who)
 {
-	PurpleAccount *pa = ic->proto_data;
-
-	purple_privacy_permit_add(pa, who, FALSE);
+	struct purple_data *pd = ic->proto_data;
+
+	purple_privacy_permit_add(pd->account, who, FALSE);
 }
 
 static void purple_add_deny(struct im_connection *ic, char *who)
 {
-	PurpleAccount *pa = ic->proto_data;
-
-	purple_privacy_deny_add(pa, who, FALSE);
+	struct purple_data *pd = ic->proto_data;
+
+	purple_privacy_deny_add(pd->account, who, FALSE);
 }
 
 static void purple_rem_permit(struct im_connection *ic, char *who)
 {
-	PurpleAccount *pa = ic->proto_data;
-
-	purple_privacy_permit_remove(pa, who, FALSE);
+	struct purple_data *pd = ic->proto_data;
+
+	purple_privacy_permit_remove(pd->account, who, FALSE);
 }
 
 static void purple_rem_deny(struct im_connection *ic, char *who)
 {
-	PurpleAccount *pa = ic->proto_data;
-
-	purple_privacy_deny_remove(pa, who, FALSE);
+	struct purple_data *pd = ic->proto_data;
+
+	purple_privacy_deny_remove(pd->account, who, FALSE);
 }
 
 static void purple_get_info(struct im_connection *ic, char *who)
 {
-	serv_get_info(purple_account_get_connection(ic->proto_data), who);
+	struct purple_data *pd = ic->proto_data;
+
+	serv_get_info(purple_account_get_connection(pd->account), who);
 }
 
@@ -517,5 +556,5 @@
 {
 	PurpleTypingState state = PURPLE_NOT_TYPING;
-	PurpleAccount *pa = ic->proto_data;
+	struct purple_data *pd = ic->proto_data;
 
 	if (flags & OPT_TYPING) {
@@ -525,5 +564,5 @@
 	}
 
-	serv_send_typing(purple_account_get_connection(pa), who, state);
+	serv_send_typing(purple_account_get_connection(pd->account), who, state);
 
 	return 1;
@@ -559,8 +598,8 @@
 	/* There went my nice afternoon. :-( */
 
-	PurpleAccount *pa = ic->proto_data;
-	PurplePlugin *prpl = purple_plugins_find_with_id(pa->protocol_id);
+	struct purple_data *pd = ic->proto_data;
+	PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id);
 	PurplePluginProtocolInfo *pi = prpl->info->extra_info;
-	PurpleBuddy *pb = purple_find_buddy((PurpleAccount *) ic->proto_data, who);
+	PurpleBuddy *pb = purple_find_buddy(pd->account, who);
 	PurpleMenuAction *mi;
 	GList *menu;
@@ -597,6 +636,7 @@
 	PurpleConversation *pc = gc->data;
 	PurpleConvChat *pcc = PURPLE_CONV_CHAT(pc);
-
-	serv_chat_invite(purple_account_get_connection(gc->ic->proto_data),
+	struct purple_data *pd = gc->ic->proto_data;
+
+	serv_chat_invite(purple_account_get_connection(pd->account),
 	                 purple_conv_chat_get_id(pcc),
 	                 message && *message ? message : "Please join my chat",
@@ -623,6 +663,6 @@
                                    set_t **sets)
 {
-	PurpleAccount *pa = ic->proto_data;
-	PurplePlugin *prpl = purple_plugins_find_with_id(pa->protocol_id);
+	struct purple_data *pd = ic->proto_data;
+	PurplePlugin *prpl = purple_plugins_find_with_id(pd->account->protocol_id);
 	PurplePluginProtocolInfo *pi = prpl->info->extra_info;
 	GHashTable *chat_hash;
@@ -631,14 +671,17 @@
 
 	if (!pi->chat_info || !pi->chat_info_defaults ||
-	    !(info = pi->chat_info(purple_account_get_connection(pa)))) {
+	    !(info = pi->chat_info(purple_account_get_connection(pd->account)))) {
 		imcb_error(ic, "Joining chatrooms not supported by this protocol");
 		return NULL;
 	}
 
-	if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, room, pa))) {
+	if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+	                                                  room, pd->account))) {
 		purple_conversation_destroy(conv);
 	}
 
-	chat_hash = pi->chat_info_defaults(purple_account_get_connection(pa), room);
+	chat_hash = pi->chat_info_defaults(
+	        purple_account_get_connection(pd->account), room
+	);
 
 	for (l = info; l; l = l->next) {
@@ -654,5 +697,5 @@
 	}
 
-	serv_join_chat(purple_account_get_connection(pa), chat_hash);
+	serv_join_chat(purple_account_get_connection(pd->account), chat_hash);
 
 	return NULL;
@@ -981,5 +1024,4 @@
 		pqad->yes(pqad->user_data, pqad->yes_i);
 	}
-	g_free(pqad);
 }
 
@@ -991,5 +1033,13 @@
 		pqad->no(pqad->user_data, pqad->no_i);
 	}
-	g_free(pqad);
+}
+
+/* q->free() callback from query_del()*/
+static void prplcb_request_action_free(void *data)
+{
+	struct prplcb_request_action_data *pqad = data;
+
+	pqad->bee_data = NULL;
+	purple_request_close(PURPLE_REQUEST_ACTION, pqad);
 }
 
@@ -1026,5 +1076,6 @@
 	q = g_strdup_printf("Request: %s\n\n%s\n\n%s", title, primary, secondary);
 	pqad->bee_data = query_add(local_bee->ui_data, purple_ic_by_pa(account), q,
-	                           prplcb_request_action_yes, prplcb_request_action_no, g_free, pqad);
+	                           prplcb_request_action_yes, prplcb_request_action_no,
+	                           prplcb_request_action_free, pqad);
 
 	g_free(q);
@@ -1033,19 +1084,89 @@
 }
 
-/*
-static void prplcb_request_test()
-{
-        fprintf( stderr, "bla\n" );
-}
-*/
+/* So it turns out some requests have no account context at all, because
+ * libpurple hates us. This means that query_del_by_conn() won't remove those
+ * on logout, and will segfault if the user replies. That's why this exists.
+ */
+static void prplcb_close_request(PurpleRequestType type, void *data)
+{
+	struct prplcb_request_action_data *pqad;
+	struct request_input_data *ri;
+	struct purple_data *pd;
+
+	if (!data) {
+		return;
+	}
+
+	switch (type) {
+	case PURPLE_REQUEST_ACTION:
+		pqad = data;
+		/* if this is null, it's because query_del was run already */
+		if (pqad->bee_data) {
+			query_del(local_bee->ui_data, pqad->bee_data);
+		}
+		g_free(pqad);
+		break;
+	case PURPLE_REQUEST_INPUT:
+		ri = data;
+		pd = ri->ic->proto_data;
+		imcb_remove_buddy(ri->ic, ri->buddy, NULL);
+		g_free(ri->buddy);
+		g_hash_table_remove(pd->input_requests, GUINT_TO_POINTER(ri->id));
+		break;
+	default:
+		g_free(data);
+		break;
+	}
+
+}
+
+void* prplcb_request_input(const char *title, const char *primary,
+        const char *secondary, const char *default_value, gboolean multiline,
+        gboolean masked, gchar *hint, const char *ok_text, GCallback ok_cb,
+        const char *cancel_text, GCallback cancel_cb, PurpleAccount *account,
+        const char *who, PurpleConversation *conv, void *user_data)
+{
+	struct im_connection *ic = purple_ic_by_pa(account);
+	struct purple_data *pd = ic->proto_data;
+	struct request_input_data *ri = g_new0(struct request_input_data, 1);
+	guint id = pd->next_request_id++;
+
+	ri->id = id;
+	ri->ic = ic;
+	ri->buddy = g_strdup_printf("%s_%u", PURPLE_REQUEST_HANDLE, id);
+	ri->data_callback = (ri_callback_t) ok_cb;
+	ri->user_data = user_data;
+	g_hash_table_insert(pd->input_requests, GUINT_TO_POINTER(id), ri);
+
+	imcb_add_buddy(ic, ri->buddy, NULL);
+	imcb_buddy_msg(ic, ri->buddy, secondary, 0, 0);
+
+	return ri;
+}
+
+void purple_request_input_callback(guint id, struct im_connection *ic,
+                                   const char *message, const char *who)
+{
+	struct purple_data *pd = ic->proto_data;
+	struct request_input_data *ri;
+
+	if (!(ri = g_hash_table_lookup(pd->input_requests, GUINT_TO_POINTER(id)))) {
+		return;
+	}
+
+	ri->data_callback(ri->user_data, message);
+
+	purple_request_close(PURPLE_REQUEST_INPUT, ri);
+}
+
 
 static PurpleRequestUiOps bee_request_uiops =
 {
-	NULL,
+	prplcb_request_input,
 	NULL,
 	prplcb_request_action,
 	NULL,
 	NULL,
-	NULL,
+	prplcb_close_request,
 	NULL,
 };
Index: protocols/skype/Makefile
===================================================================
--- protocols/skype/Makefile	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/skype/Makefile	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -6,7 +6,7 @@
 DATE := $(shell date +%Y-%m-%d)
 INSTALL = install
-ASCIIDOC = yes
 
-ifeq ($(ASCIIDOC),yes)
+
+ifdef ASCIIDOC
 MANPAGES = skyped.1
 else
@@ -29,5 +29,5 @@
 
 install-doc: doc
-ifeq ($(ASCIIDOC),yes)
+ifdef ASCIIDOC
 	$(INSTALL) -d $(DESTDIR)$(MANDIR)/man1
 	$(INSTALL) -m644 $(MANPAGES) $(DESTDIR)$(MANDIR)/man1
Index: protocols/twitter/twitter.c
===================================================================
--- protocols/twitter/twitter.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/twitter/twitter.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -852,4 +852,21 @@
 }
 
+/* Parses a decimal or hex tweet ID, returns TRUE on success */
+static gboolean twitter_parse_id(char *string, int base, guint64 *id)
+{
+	guint64 parsed;
+	char *endptr;
+
+	errno = 0;
+	parsed = g_ascii_strtoull(string, &endptr, base);
+	if (errno || endptr == string || *endptr != '\0') {
+		return FALSE;
+	}
+	*id = parsed;
+	return TRUE;
+}
+
+bee_user_t twitter_log_local_user;
+
 /** Convert the given bitlbee tweet ID, bitlbee username, or twitter tweet ID
  *  into a twitter tweet ID.
@@ -879,13 +896,8 @@
 			arg++;
 		}
-		if (sscanf(arg, "%" G_GINT64_MODIFIER "x", &id) == 1 &&
-		    id < TWITTER_LOG_LENGTH) {
+		if (twitter_parse_id(arg, 16, &id) && id < TWITTER_LOG_LENGTH) {
 			bu = td->log[id].bu;
 			id = td->log[id].id;
-			/* Beware of dangling pointers! */
-			if (!g_slist_find(ic->bee->users, bu)) {
-				bu = NULL;
-			}
-		} else if (sscanf(arg, "%" G_GINT64_MODIFIER "d", &id) == 1) {
+		} else if (twitter_parse_id(arg, 10, &id)) {
 			/* Allow normal tweet IDs as well; not a very useful
 			   feature but it's always been there. Just ignore
@@ -897,4 +909,14 @@
 	}
 	if (bu_) {
+		if (bu == &twitter_log_local_user) {
+			/* HACK alert. There's no bee_user object for the local
+			 * user so just fake one for the few cmds that need it. */
+			twitter_log_local_user.handle = td->user;
+		} else {
+			/* Beware of dangling pointers! */
+			if (!g_slist_find(ic->bee->users, bu)) {
+				bu = NULL;
+			}
+		}
 		*bu_ = bu;
 	}
@@ -989,4 +1011,17 @@
 		in_reply_to = id;
 		allow_post = TRUE;
+	} else if (g_strcasecmp(cmd[0], "url") == 0) {
+		id = twitter_message_id_from_command_arg(ic, cmd[1], &bu);
+		if (!id) {
+			twitter_log(ic, "Tweet `%s' does not exist", cmd[1]);
+		} else {
+			/* More common link is twitter.com/$UID/status/$ID (and that's
+			 * what this will 302 to) but can't generate that since for RTs,
+			 * bu here points at the retweeter while id contains the id of
+			 * the original message. */
+			twitter_log(ic, "https://twitter.com/statuses/%lld", id);
+		}
+		goto eof;
+
 	} else if (g_strcasecmp(cmd[0], "post") == 0) {
 		message += 5;
Index: protocols/twitter/twitter.h
===================================================================
--- protocols/twitter/twitter.h	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/twitter/twitter.h	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -101,5 +101,7 @@
 struct twitter_log_data {
 	guint64 id;
-	struct bee_user *bu; /* DANGER: can be a dead pointer. Check it first. */
+	/* DANGER: bu can be a dead pointer. Check it first.
+	 * twitter_message_id_from_command_arg() will do this. */
+	struct bee_user *bu;
 };
 
@@ -110,4 +112,12 @@
  */
 extern GSList *twitter_connections;
+
+/**
+ * Evil hack: Fake bee_user which will always point at the local user.
+ * Sometimes used as a return value by twitter_message_id_from_command_arg.
+ * NOT thread safe but don't you dare to even think of ever making BitlBee
+ * threaded. :-)
+ */
+extern bee_user_t twitter_log_local_user;
 
 void twitter_login_finish(struct im_connection *ic);
Index: protocols/twitter/twitter_lib.c
===================================================================
--- protocols/twitter/twitter_lib.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/twitter/twitter_lib.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -460,5 +460,5 @@
 #endif
 
-static char* expand_entities(char* text, const json_value *entities);
+static void expand_entities(char **text, const json_value *node);
 
 /**
@@ -473,5 +473,5 @@
 {
 	struct twitter_xml_status *txs;
-	const json_value *rt = NULL, *entities = NULL;
+	const json_value *rt = NULL;
 
 	if (node->type != json_object) {
@@ -501,6 +501,4 @@
 		} else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) {
 			txs->reply_to = v->u.integer;
-		} else if (strcmp("entities", k) == 0 && v->type == json_object) {
-			entities = v;
 		}
 	}
@@ -516,6 +514,6 @@
 			txs_free(rtxs);
 		}
-	} else if (entities) {
-		txs->text = expand_entities(txs->text, entities);
+	} else {
+		expand_entities(&txs->text, node);
 	}
 
@@ -534,5 +532,4 @@
 {
 	struct twitter_xml_status *txs;
-	const json_value *entities = NULL;
 
 	if (node->type != json_object) {
@@ -561,7 +558,5 @@
 	}
 
-	if (entities) {
-		txs->text = expand_entities(txs->text, entities);
-	}
+	expand_entities(&txs->text, node);
 
 	if (txs->text && txs->user && txs->id) {
@@ -573,6 +568,24 @@
 }
 
-static char* expand_entities(char* text, const json_value *entities)
-{
+static void expand_entities(char **text, const json_value *node)
+{
+	json_value *entities, *quoted;
+	char *quote_url = NULL, *quote_text = NULL;
+
+	if (!((entities = json_o_get(node, "entities")) && entities->type == json_object))
+		return;
+	if ((quoted = json_o_get(node, "quoted_status")) && quoted->type == json_object) {
+		/* New "retweets with comments" feature. Note that this info
+		 * seems to be included in the streaming API only! Grab the
+		 * full message and try to insert it when we run into the
+		 * Tweet entity. */
+		struct twitter_xml_status *txs = twitter_xt_get_status(quoted);
+		quote_text = g_strdup_printf("@%s: %s", txs->user->screen_name, txs->text);
+		quote_url = g_strdup_printf("%s/status/%" G_GUINT64_FORMAT, txs->user->screen_name, txs->id);
+		txs_free(txs);
+	} else {
+		quoted = NULL;
+	}
+
 	JSON_O_FOREACH(entities, k, v) {
 		int i;
@@ -586,4 +599,6 @@
 
 		for (i = 0; i < v->u.array.length; i++) {
+			const char *format = "%s%s <%s>%s";
+
 			if (v->u.array.values[i]->type != json_object) {
 				continue;
@@ -592,20 +607,25 @@
 			const char *kort = json_o_str(v->u.array.values[i], "url");
 			const char *disp = json_o_str(v->u.array.values[i], "display_url");
+			const char *full = json_o_str(v->u.array.values[i], "expanded_url");
 			char *pos, *new;
 
-			if (!kort || !disp || !(pos = strstr(text, kort))) {
+			if (!kort || !disp || !(pos = strstr(*text, kort))) {
 				continue;
 			}
+			if (quote_url && strstr(full, quote_url)) {
+				format = "%s<%s> [%s]%s";
+				disp = quote_text;
+			}
 
 			*pos = '\0';
-			new = g_strdup_printf("%s%s <%s>%s", text, kort,
+			new = g_strdup_printf(format, *text, kort,
 			                      disp, pos + strlen(kort));
 
-			g_free(text);
-			text = new;
-		}
-	}
-
-	return text;
+			g_free(*text);
+			*text = new;
+		}
+	}
+	g_free(quote_text);
+	g_free(quote_url);
 }
 
@@ -681,4 +701,6 @@
 	if (g_strcasecmp(txs->user->screen_name, td->user) == 0) {
 		td->log[td->log_id].id = txs->rt_id;
+		/* More useful than NULL. */
+		td->log[td->log_id].bu = &twitter_log_local_user;
 	}
 
@@ -832,5 +854,5 @@
 
 	last_id_str = g_strdup_printf("%" G_GUINT64_FORMAT, td->timeline_id);
-	set_setstr(&ic->acc->set, "last_tweet", last_id_str);
+	set_setstr(&ic->acc->set, "_last_tweet", last_id_str);
 	g_free(last_id_str);
 }
@@ -862,4 +884,7 @@
 
 		imcb_error(ic, "Stream closed (%s)", req->status_string);
+		if (req->status_code == 401) {
+			imcb_error(ic, "Check your system clock.");
+		}
 		imc_logout(ic, TRUE);
 		return;
Index: protocols/yahoo/yahoo.c
===================================================================
--- protocols/yahoo/yahoo.c	(revision 531eabdee2ee9d64e034bbb82caa08a69dd8dc9c)
+++ protocols/yahoo/yahoo.c	(revision 8563fe7ae580368212233d53e9b81b67dfa7b619)
@@ -733,7 +733,7 @@
 		inp = l->data;
 		if (inp->h == tag) {
+			byahoo_inputs = g_slist_remove(byahoo_inputs, inp);
 			g_free(inp->d);
 			g_free(inp);
-			byahoo_inputs = g_slist_remove(byahoo_inputs, inp);
 			break;
 		}
