aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/msn
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/msn')
-rw-r--r--protocols/msn/Makefile46
-rw-r--r--protocols/msn/invitation.c622
-rw-r--r--protocols/msn/invitation.h82
-rw-r--r--protocols/msn/msn.c415
-rw-r--r--protocols/msn/msn.h266
-rw-r--r--protocols/msn/msn_util.c590
-rw-r--r--protocols/msn/ns.c886
-rw-r--r--protocols/msn/sb.c806
-rw-r--r--protocols/msn/soap.c1162
-rw-r--r--protocols/msn/soap.h378
-rw-r--r--protocols/msn/tables.c157
11 files changed, 5410 insertions, 0 deletions
diff --git a/protocols/msn/Makefile b/protocols/msn/Makefile
new file mode 100644
index 00000000..8845d41b
--- /dev/null
+++ b/protocols/msn/Makefile
@@ -0,0 +1,46 @@
+###########################
+## Makefile for BitlBee ##
+## ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+ifdef SRCDIR
+SRCDIR := $(SRCDIR)protocols/msn/
+endif
+
+# [SH] Program variables
+objects = msn.o msn_util.o ns.o sb.o soap.o tables.o
+
+LFLAGS += -r
+
+# [SH] Phony targets
+all: msn_mod.o
+check: all
+lcov: check
+gcov:
+ gcov *.c
+
+.PHONY: all clean distclean
+
+clean:
+ rm -f *.o core
+
+distclean: clean
+ rm -rf .depend
+
+### MAIN PROGRAM
+
+$(objects): ../../Makefile.settings Makefile
+
+$(objects): %.o: $(SRCDIR)%.c
+ @echo '*' Compiling $<
+ @$(CC) -c $(CFLAGS) $< -o $@
+
+msn_mod.o: $(objects)
+ @echo '*' Linking msn_mod.o
+ @$(LD) $(LFLAGS) $(objects) -o msn_mod.o
+
+-include .depend/*.d
diff --git a/protocols/msn/invitation.c b/protocols/msn/invitation.c
new file mode 100644
index 00000000..9f8b9a6e
--- /dev/null
+++ b/protocols/msn/invitation.c
@@ -0,0 +1,622 @@
+/********************************************************************\
+* 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., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 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;
+}
diff --git a/protocols/msn/invitation.h b/protocols/msn/invitation.h
new file mode 100644
index 00000000..289efd7b
--- /dev/null
+++ b/protocols/msn/invitation.h
@@ -0,0 +1,82 @@
+/********************************************************************\
+* 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., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 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
diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c
new file mode 100644
index 00000000..03f28422
--- /dev/null
+++ b/protocols/msn/msn.c
@@ -0,0 +1,415 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module - Main file; functions to be called from BitlBee */
+
+/*
+ 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., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "nogaim.h"
+#include "soap.h"
+#include "msn.h"
+
+int msn_chat_id;
+GSList *msn_connections;
+GSList *msn_switchboards;
+
+static char *set_eval_display_name( set_t *set, char *value );
+
+static void msn_init( account_t *acc )
+{
+ set_t *s;
+
+ s = set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc );
+ s->flags |= ACC_SET_NOSAVE | ACC_SET_ONLINE_ONLY;
+
+ 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;
+}
+
+static void msn_login( account_t *acc )
+{
+ struct im_connection *ic = imcb_new( acc );
+ struct msn_data *md = g_new0( struct msn_data, 1 );
+
+ ic->proto_data = md;
+
+ if( strchr( acc->user, '@' ) == NULL )
+ {
+ imcb_error( ic, "Invalid account name" );
+ imc_logout( ic, FALSE );
+ return;
+ }
+
+ md->ic = ic;
+ md->away_state = msn_away_state_list;
+ md->domaintree = g_tree_new( msn_domaintree_cmp );
+ md->ns->fd = -1;
+
+ msn_connections = g_slist_prepend( msn_connections, ic );
+
+ imcb_log( ic, "Connecting" );
+ msn_ns_connect( ic, md->ns, MSN_NS_HOST, MSN_NS_PORT );
+}
+
+static void msn_logout( struct im_connection *ic )
+{
+ struct msn_data *md = ic->proto_data;
+ GSList *l;
+ int i;
+
+ 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_soapq_flush( ic, FALSE );
+
+ for( i = 0; i < sizeof( md->tokens ) / sizeof( md->tokens[0] ); i ++ )
+ g_free( md->tokens[i] );
+ g_free( md->lock_key );
+ g_free( md->pp_policy );
+
+ while( md->groups )
+ {
+ struct msn_group *mg = md->groups->data;
+ g_free( mg->id );
+ g_free( mg->name );
+ g_free( mg );
+ md->groups = g_slist_remove( md->groups, mg );
+ }
+
+ g_free( md->profile_rid );
+
+ if( md->domaintree )
+ g_tree_destroy( md->domaintree );
+ md->domaintree = NULL;
+
+ while( md->grpq )
+ {
+ struct msn_groupadd *ga = md->grpq->data;
+ g_free( ga->group );
+ g_free( ga->who );
+ g_free( ga );
+ md->grpq = g_slist_remove( md->grpq, ga );
+ }
+
+ g_free( md );
+ }
+
+ for( l = ic->permit; l; l = l->next )
+ g_free( l->data );
+ g_slist_free( ic->permit );
+
+ for( l = ic->deny; l; l = l->next )
+ g_free( l->data );
+ g_slist_free( ic->deny );
+
+ msn_connections = g_slist_remove( msn_connections, ic );
+}
+
+static int msn_buddy_msg( struct im_connection *ic, char *who, char *message, int away )
+{
+ struct msn_switchboard *sb;
+
+#ifdef DEBUG
+ if( strcmp( who, "raw" ) == 0 )
+ {
+ msn_ns_write( ic, -1, "%s\r\n", message );
+ }
+ else
+#endif
+ 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 );
+ }
+
+ return( 0 );
+}
+
+static GList *msn_away_states( struct im_connection *ic )
+{
+ static GList *l = NULL;
+ int i;
+
+ if( l == NULL )
+ for( i = 0; *msn_away_state_list[i].code; i ++ )
+ if( *msn_away_state_list[i].name )
+ l = g_list_append( l, (void*) msn_away_state_list[i].name );
+
+ return l;
+}
+
+static void msn_set_away( struct im_connection *ic, char *state, char *message )
+{
+ char *uux;
+ struct msn_data *md = ic->proto_data;
+
+ if( state == NULL )
+ md->away_state = msn_away_state_list;
+ else if( ( md->away_state = msn_away_state_by_name( state ) ) == NULL )
+ md->away_state = msn_away_state_list + 1;
+
+ if( !msn_ns_write( ic, -1, "CHG %d %s\r\n", ++md->trId, md->away_state->code ) )
+ return;
+
+ uux = g_markup_printf_escaped( "<Data><PSM>%s</PSM><CurrentMedia></CurrentMedia>"
+ "</Data>", message ? message : "" );
+ msn_ns_write( ic, -1, "UUX %d %zd\r\n%s", ++md->trId, strlen( uux ), uux );
+ g_free( uux );
+}
+
+static void msn_get_info(struct im_connection *ic, char *who)
+{
+ /* Just make an URL and let the user fetch the info */
+ imcb_log( ic, "%s\n%s: %s%s", _("User Info"), _("For now, fetch yourself"), PROFILE_URL, who );
+}
+
+static void msn_add_buddy( struct im_connection *ic, char *who, char *group )
+{
+ struct bee_user *bu = bee_user_by_handle( ic->bee, ic, who );
+
+ msn_buddy_list_add( ic, MSN_BUDDY_FL, who, who, group );
+ if( bu && bu->group )
+ msn_buddy_list_remove( ic, MSN_BUDDY_FL, who, bu->group->name );
+}
+
+static void msn_remove_buddy( struct im_connection *ic, char *who, char *group )
+{
+ msn_buddy_list_remove( ic, MSN_BUDDY_FL, who, NULL );
+}
+
+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! */
+}
+
+static void msn_chat_invite( struct groupchat *c, char *who, char *message )
+{
+ struct msn_switchboard *sb = msn_sb_by_chat( c );
+ char buf[1024];
+
+ if( sb )
+ {
+ g_snprintf( buf, sizeof( buf ), "CAL %d %s\r\n", ++sb->trId, who );
+ msn_sb_write( sb, buf, strlen( buf ) );
+ }
+}
+
+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", 5 );
+}
+
+static struct groupchat *msn_chat_with( struct im_connection *ic, char *who )
+{
+ struct msn_switchboard *sb;
+ 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;
+ }
+}
+
+static void msn_keepalive( struct im_connection *ic )
+{
+ msn_ns_write( ic, -1, "PNG\r\n" );
+}
+
+static void msn_add_permit( struct im_connection *ic, char *who )
+{
+ msn_buddy_list_add( ic, MSN_BUDDY_AL, who, who, NULL );
+}
+
+static void msn_rem_permit( struct im_connection *ic, char *who )
+{
+ msn_buddy_list_remove( ic, MSN_BUDDY_AL, who, NULL );
+}
+
+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 );
+ }
+}
+
+static void msn_rem_deny( struct im_connection *ic, char *who )
+{
+ msn_buddy_list_remove( ic, MSN_BUDDY_BL, who, NULL );
+}
+
+static int msn_send_typing( struct im_connection *ic, char *who, int typing )
+{
+ struct bee_user *bu = bee_user_by_handle( ic->bee, ic, who );
+
+ if( !( bu->flags & BEE_USER_ONLINE ) )
+ return 0;
+ else if( typing & OPT_TYPING )
+ return( msn_buddy_msg( ic, who, TYPING_NOTIFICATION_MESSAGE, 0 ) );
+ else
+ return 1;
+}
+
+static char *set_eval_display_name( set_t *set, char *value )
+{
+ account_t *acc = set->data;
+ struct im_connection *ic = acc->ic;
+ struct msn_data *md = ic->proto_data;
+
+ if( md->flags & MSN_EMAIL_UNVERIFIED )
+ imcb_log( ic, "Warning: Your e-mail address is unverified. MSN doesn't allow "
+ "changing your display name until your e-mail address is verified." );
+
+ if( md->flags & MSN_GOT_PROFILE_DN )
+ msn_soap_profile_set_dn( ic, value );
+ else
+ msn_soap_addressbook_set_display_name( ic, value );
+
+ return msn_ns_set_display_name( ic, value ) ? value : NULL;
+}
+
+static void msn_buddy_data_add( bee_user_t *bu )
+{
+ struct msn_data *md = bu->ic->proto_data;
+ bu->data = g_new0( struct msn_buddy_data, 1 );
+ g_tree_insert( md->domaintree, bu->handle, bu );
+}
+
+static void msn_buddy_data_free( bee_user_t *bu )
+{
+ struct msn_data *md = bu->ic->proto_data;
+ struct msn_buddy_data *bd = bu->data;
+
+ g_free( bd->cid );
+ g_free( bd );
+
+ g_tree_remove( md->domaintree, bu->handle );
+}
+
+GList *msn_buddy_action_list( bee_user_t *bu )
+{
+ static GList *ret = NULL;
+
+ if( ret == NULL )
+ {
+ static const struct buddy_action ba[2] = {
+ { "NUDGE", "Draw attention" },
+ };
+
+ ret = g_list_prepend( ret, (void*) ba + 0 );
+ }
+
+ return ret;
+}
+
+void *msn_buddy_action( struct bee_user *bu, const char *action, char * const args[], void *data )
+{
+ if( g_strcasecmp( action, "NUDGE" ) == 0 )
+ msn_buddy_msg( bu->ic, bu->handle, NUDGE_MESSAGE, 0 );
+
+ return NULL;
+}
+
+void msn_initmodule()
+{
+ struct prpl *ret = g_new0(struct prpl, 1);
+
+ ret->name = "msn";
+ ret->mms = 1409; /* this guess taken from libotr UPGRADING file */
+ ret->login = msn_login;
+ ret->init = msn_init;
+ ret->logout = msn_logout;
+ ret->buddy_msg = msn_buddy_msg;
+ ret->away_states = msn_away_states;
+ ret->set_away = msn_set_away;
+ ret->get_info = msn_get_info;
+ ret->add_buddy = msn_add_buddy;
+ ret->remove_buddy = msn_remove_buddy;
+ ret->chat_msg = msn_chat_msg;
+ ret->chat_invite = msn_chat_invite;
+ ret->chat_leave = msn_chat_leave;
+ ret->chat_with = msn_chat_with;
+ ret->keepalive = msn_keepalive;
+ ret->add_permit = msn_add_permit;
+ ret->rem_permit = msn_rem_permit;
+ ret->add_deny = msn_add_deny;
+ ret->rem_deny = msn_rem_deny;
+ ret->send_typing = msn_send_typing;
+ ret->handle_cmp = g_strcasecmp;
+ ret->buddy_data_add = msn_buddy_data_add;
+ ret->buddy_data_free = msn_buddy_data_free;
+ ret->buddy_action_list = msn_buddy_action_list;
+ ret->buddy_action = msn_buddy_action;
+
+ //ret->transfer_request = msn_ftp_transfer_request;
+
+ register_protocol(ret);
+}
diff --git a/protocols/msn/msn.h b/protocols/msn/msn.h
new file mode 100644
index 00000000..da9430ab
--- /dev/null
+++ b/protocols/msn/msn.h
@@ -0,0 +1,266 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module */
+
+/*
+ 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., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef _MSN_H
+#define _MSN_H
+
+/* Some hackish magicstrings to make special-purpose messages/switchboards.
+ */
+#define TYPING_NOTIFICATION_MESSAGE "\r\r\rBEWARE, ME R TYPINK MESSAGE!!!!\r\r\r"
+#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
+#define debug( text... ) imcb_log( ic, text );
+#else
+#define debug( text... )
+#endif
+
+/* This should be MSN Messenger 7.0.0813
+#define MSNP11_PROD_KEY "CFHUR$52U_{VIX5T"
+#define MSNP11_PROD_ID "PROD0101{0RM?UBW"
+*/
+
+#define MSN_NS_HOST "messenger.hotmail.com"
+#define MSN_NS_PORT 1863
+
+/* Some other version.
+#define MSNP11_PROD_KEY "O4BG@C7BWLYQX?5G"
+#define MSNP11_PROD_ID "PROD01065C%ZFN6F"
+*/
+
+#define MSNP11_PROD_KEY "ILTXC!4IXB5FB*PX"
+#define MSNP11_PROD_ID "PROD0119GSJUC$18"
+#define MSNP_VER "MSNP15"
+#define MSNP_BUILD "8.5.1288"
+
+#define MSN_SB_NEW -24062002
+
+#define MSN_MESSAGE_HEADERS "MIME-Version: 1.0\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"
+
+#define MSN_TYPING_HEADERS "MIME-Version: 1.0\r\n" \
+ "Content-Type: text/x-msmsgscontrol\r\n" \
+ "TypingUser: %s\r\n" \
+ "\r\n\r\n"
+
+#define MSN_NUDGE_HEADERS "MIME-Version: 1.0\r\n" \
+ "Content-Type: text/x-msnmsgr-datacast\r\n" \
+ "\r\n" \
+ "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/"
+
+typedef enum
+{
+ MSN_GOT_PROFILE = 1,
+ MSN_GOT_PROFILE_DN = 2,
+ MSN_DONE_ADL = 4,
+ MSN_REAUTHING = 8,
+ MSN_EMAIL_UNVERIFIED = 16,
+} msn_flags_t;
+
+struct msn_handler_data
+{
+ int fd, inpa;
+ int rxlen;
+ char *rxq;
+
+ int msglen;
+ 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;
+
+ int trId;
+ char *tokens[4];
+ char *lock_key, *pp_policy;
+
+ GSList *msgq, *grpq, *soapq;
+ GSList *switchboards;
+ int sb_failures;
+ time_t first_sb_failure;
+
+ const struct msn_away_state *away_state;
+ GSList *groups;
+ char *profile_rid;
+
+ /* Mostly used for sending the ADL command; since MSNP13 the client
+ is responsible for downloading the contact list and then sending
+ it to the MSNP server. */
+ 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;
+};
+
+struct msn_away_state
+{
+ char code[4];
+ char name[16];
+};
+
+struct msn_status_code
+{
+ int number;
+ char *text;
+ int flags;
+};
+
+struct msn_message
+{
+ char *who;
+ char *text;
+};
+
+struct msn_groupadd
+{
+ char *who;
+ char *group;
+};
+
+typedef enum
+{
+ MSN_BUDDY_FL = 1, /* Warning: FL,AL,BL *must* be 1,2,4. */
+ MSN_BUDDY_AL = 2,
+ MSN_BUDDY_BL = 4,
+ MSN_BUDDY_RL = 8,
+ MSN_BUDDY_PL = 16,
+ MSN_BUDDY_ADL_SYNCED = 256,
+} msn_buddy_flags_t;
+
+struct msn_buddy_data
+{
+ char *cid;
+ msn_buddy_flags_t flags;
+};
+
+struct msn_group
+{
+ char *name;
+ char *id;
+};
+
+/* Bitfield values for msn_status_code.flags */
+#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;
+extern const struct msn_away_state msn_away_state_list[];
+extern const struct msn_status_code msn_status_code_list[];
+
+/* Keep a list of all the active connections. We need these lists because
+ "connected" callbacks might be called when the connection they belong too
+ is down already (for example, when an impatient user disabled the
+ connection), the callback should check whether it's still listed here
+ 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, ... );
+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 );
+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 );
+
+/* msn_util.c */
+int msn_logged_in( struct im_connection *ic );
+int msn_buddy_list_add( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *realname_, const char *group );
+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_findheader( char *text, char *header, int len );
+char **msn_linesplit( char *line );
+int msn_handler( struct msn_handler_data *h );
+void msn_msgq_purge( struct im_connection *ic, GSList **list );
+char *msn_p11_challenge( char *challenge );
+gint msn_domaintree_cmp( gconstpointer a_, gconstpointer b_ );
+struct msn_group *msn_group_by_name( struct im_connection *ic, const char *name );
+struct msn_group *msn_group_by_id( struct im_connection *ic, const char *id );
+int msn_ns_set_display_name( struct im_connection *ic, const char *value );
+
+/* tables.c */
+const struct msn_away_state *msn_away_state_by_number( int number );
+const struct msn_away_state *msn_away_state_by_code( char *code );
+const struct msn_away_state *msn_away_state_by_name( char *name );
+const struct msn_status_code *msn_status_by_number( int number );
+
+/* sb.c */
+int msn_sb_write( struct msn_switchboard *sb, const char *fmt, ... );
+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, 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 );
+
+#endif //_MSN_H
diff --git a/protocols/msn/msn_util.c b/protocols/msn/msn_util.c
new file mode 100644
index 00000000..7fa68915
--- /dev/null
+++ b/protocols/msn/msn_util.c
@@ -0,0 +1,590 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module - Miscellaneous 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., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "nogaim.h"
+#include "msn.h"
+#include "md5.h"
+#include "soap.h"
+#include <ctype.h>
+
+int msn_logged_in( struct im_connection *ic )
+{
+ imcb_connected( ic );
+
+ return( 0 );
+}
+
+static char *adlrml_entry( const char *handle_, msn_buddy_flags_t list )
+{
+ char *domain, handle[strlen(handle_)+1];
+
+ strcpy( handle, handle_ );
+ if( ( domain = strchr( handle, '@' ) ) )
+ *(domain++) = '\0';
+ else
+ return NULL;
+
+ return g_markup_printf_escaped( "<ml><d n=\"%s\"><c n=\"%s\" l=\"%d\" t=\"1\"/></d></ml>",
+ domain, handle, list );
+}
+
+int msn_buddy_list_add( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *realname, const char *group )
+{
+ struct msn_data *md = ic->proto_data;
+ char groupid[8];
+ bee_user_t *bu;
+ struct msn_buddy_data *bd;
+ char *adl;
+
+ *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 ) ) ||
+ ( bu = bee_user_new( ic->bee, ic, who, 0 ) ) ) ||
+ !( bd = bu->data ) || bd->flags & list )
+ return 1;
+
+ bd->flags |= list;
+
+ if( list == MSN_BUDDY_FL )
+ msn_soap_ab_contact_add( ic, bu );
+ else
+ msn_soap_memlist_edit( ic, who, TRUE, list );
+
+ if( ( adl = adlrml_entry( who, list ) ) )
+ {
+ int st = msn_ns_write( ic, -1, "ADL %d %zd\r\n%s",
+ ++md->trId, strlen( adl ), adl );
+ g_free( adl );
+
+ return st;
+ }
+
+ return 1;
+}
+
+int msn_buddy_list_remove( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *group )
+{
+ struct msn_data *md = ic->proto_data;
+ char groupid[8];
+ bee_user_t *bu;
+ struct msn_buddy_data *bd;
+ char *adl;
+
+ *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 ) ) ||
+ !( bd = bu->data ) || !( bd->flags & list ) )
+ return 1;
+
+ bd->flags &= ~list;
+
+ if( list == MSN_BUDDY_FL )
+ msn_soap_ab_contact_del( ic, bu );
+ else
+ msn_soap_memlist_edit( ic, who, FALSE, list );
+
+ if( ( adl = adlrml_entry( who, list ) ) )
+ {
+ int st = msn_ns_write( ic, -1, "RML %d %zd\r\n%s",
+ ++md->trId, strlen( adl ), adl );
+ g_free( adl );
+
+ return st;
+ }
+
+ return 1;
+}
+
+struct msn_buddy_ask_data
+{
+ struct im_connection *ic;
+ char *handle;
+ char *realname;
+};
+
+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 );
+
+ g_free( bla->handle );
+ g_free( bla->realname );
+ g_free( bla );
+}
+
+static void msn_buddy_ask_no( void *data )
+{
+ struct msn_buddy_ask_data *bla = data;
+
+ 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 );
+}
+
+void msn_buddy_ask( bee_user_t *bu )
+{
+ struct msn_buddy_ask_data *bla;
+ struct msn_buddy_data *bd = bu->data;
+ char buf[1024];
+
+ if( ( bd->flags & 30 ) != 8 && ( bd->flags & 30 ) != 16 )
+ return;
+
+ bla = g_new0( struct msn_buddy_ask_data, 1 );
+ bla->ic = bu->ic;
+ bla->handle = g_strdup( bu->handle );
+ bla->realname = g_strdup( bu->fullname );
+
+ g_snprintf( buf, sizeof( buf ),
+ "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 );
+}
+
+char *msn_findheader( char *text, char *header, int len )
+{
+ int hlen = strlen( header ), i;
+ char *ret;
+
+ if( len == 0 )
+ len = strlen( text );
+
+ i = 0;
+ while( ( i + hlen ) < len )
+ {
+ /* Maybe this is a bit over-commented, but I just hate this part... */
+ if( g_strncasecmp( text + i, header, hlen ) == 0 )
+ {
+ /* Skip to the (probable) end of the header */
+ i += hlen;
+
+ /* Find the first non-[: \t] character */
+ while( i < len && ( text[i] == ':' || text[i] == ' ' || text[i] == '\t' ) ) i ++;
+
+ /* Make sure we're still inside the string */
+ if( i >= len ) return( NULL );
+
+ /* Save the position */
+ ret = text + i;
+
+ /* Search for the end of this line */
+ while( i < len && text[i] != '\r' && text[i] != '\n' ) i ++;
+
+ /* Make sure we're still inside the string */
+ if( i >= len ) return( NULL );
+
+ /* Copy the found data */
+ return( g_strndup( ret, text + i - ret ) );
+ }
+
+ /* This wasn't the header we were looking for, skip to the next line. */
+ while( i < len && ( text[i] != '\r' && text[i] != '\n' ) ) i ++;
+ while( i < len && ( text[i] == '\r' || text[i] == '\n' ) ) i ++;
+
+ /* End of headers? */
+ if( ( i >= 4 && strncmp( text + i - 4, "\r\n\r\n", 4 ) == 0 ) ||
+ ( i >= 2 && ( strncmp( text + i - 2, "\n\n", 2 ) == 0 ||
+ strncmp( text + i - 2, "\r\r", 2 ) == 0 ) ) )
+ {
+ break;
+ }
+ }
+
+ return( NULL );
+}
+
+/* *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 );
+}
+
+/* This one handles input from a MSN Messenger server. Both the NS and SB servers usually give
+ commands, but sometimes they give additional data (payload). This function tries to handle
+ this all in a nice way and send all data to the right places. */
+
+/* Return values: -1: Read error, abort connection.
+ 0: Command reported error; Abort *immediately*. (The connection does not exist anymore)
+ 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 );
+ }
+
+ while( st )
+ {
+ int i;
+
+ if( h->msglen == 0 )
+ {
+ for( i = 0; i < h->rxlen; i ++ )
+ {
+ if( h->rxq[i] == '\r' || h->rxq[i] == '\n' )
+ {
+ char *cmd_text, **cmd;
+ int count;
+
+ 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 );
+ g_free( cmd_text );
+
+ /* If the connection broke, don't continue. We don't even exist anymore. */
+ if( !st )
+ return( 0 );
+
+ if( h->msglen )
+ h->cmd_text = g_strndup( h->rxq, i );
+
+ /* Skip to the next non-emptyline */
+ while( i < h->rxlen && ( h->rxq[i] == '\r' || h->rxq[i] == '\n' ) ) i ++;
+
+ break;
+ }
+ }
+
+ /* 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' )
+ break;
+ }
+ else
+ {
+ char *msg, **cmd;
+ int count;
+
+ /* Do we have the complete message already? */
+ if( h->msglen > h->rxlen )
+ break;
+
+ 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 );
+ g_free( msg );
+ g_free( h->cmd_text );
+ h->cmd_text = NULL;
+
+ if( !st )
+ return( 0 );
+
+ i = h->msglen;
+ h->msglen = 0;
+ }
+
+ /* More data after this block? */
+ if( i < h->rxlen )
+ {
+ char *tmp;
+
+ tmp = g_memdup( h->rxq + i, h->rxlen - i );
+ g_free( h->rxq );
+ h->rxq = tmp;
+ h->rxlen -= i;
+ i = 0;
+ }
+ else
+ /* If not, reset the rx queue and get lost. */
+ {
+ g_free( h->rxq );
+ h->rxq = g_new0( char, 1 );
+ h->rxlen = 0;
+ return( 1 );
+ }
+ }
+
+ return( 1 );
+}
+
+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 )
+{
+ char *output, buf[256];
+ md5_state_t md5c;
+ unsigned char md5Hash[16], *newHash;
+ unsigned int *md5Parts, *chlStringParts, newHashParts[5];
+ long long nHigh = 0, nLow = 0;
+ int i, n;
+
+ /* Create the MD5 hash */
+ md5_init(&md5c);
+ md5_append(&md5c, (unsigned char*) challenge, strlen(challenge));
+ md5_append(&md5c, (unsigned char*) MSNP11_PROD_KEY, strlen(MSNP11_PROD_KEY));
+ md5_finish(&md5c, md5Hash);
+
+ /* Split it into four integers */
+ md5Parts = (unsigned int *)md5Hash;
+ for (i = 0; i < 4; i ++)
+ {
+ md5Parts[i] = GUINT32_TO_LE(md5Parts[i]);
+
+ /* & each integer with 0x7FFFFFFF */
+ /* and save one unmodified array for later */
+ newHashParts[i] = md5Parts[i];
+ md5Parts[i] &= 0x7FFFFFFF;
+ }
+
+ /* make a new string and pad with '0' */
+ n = g_snprintf(buf, sizeof(buf)-5, "%s%s00000000", challenge, MSNP11_PROD_ID);
+ /* truncate at an 8-byte boundary */
+ buf[n&=~7] = '\0';
+
+ /* split into integers */
+ chlStringParts = (unsigned int *)buf;
+
+ /* this is magic */
+ for (i = 0; i < (n / 4) - 1; i += 2)
+ {
+ long long temp;
+
+ chlStringParts[i] = GUINT32_TO_LE(chlStringParts[i]);
+ chlStringParts[i+1] = GUINT32_TO_LE(chlStringParts[i+1]);
+
+ temp = (md5Parts[0] * (((0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF)+nHigh) + md5Parts[1])%0x7FFFFFFF;
+ nHigh = (md5Parts[2] * (((long long)chlStringParts[i+1]+temp) % 0x7FFFFFFF) + md5Parts[3]) % 0x7FFFFFFF;
+ nLow = nLow + nHigh + temp;
+ }
+ nHigh = (nHigh+md5Parts[1]) % 0x7FFFFFFF;
+ nLow = (nLow+md5Parts[3]) % 0x7FFFFFFF;
+
+ newHashParts[0] ^= nHigh;
+ newHashParts[1] ^= nLow;
+ newHashParts[2] ^= nHigh;
+ newHashParts[3] ^= nLow;
+
+ /* swap more bytes if big endian */
+ for (i = 0; i < 4; i ++)
+ newHashParts[i] = GUINT32_TO_LE(newHashParts[i]);
+
+ /* make a string of the parts */
+ newHash = (unsigned char *)newHashParts;
+
+ /* convert to hexadecimal */
+ output = g_new(char, 33);
+ for (i = 0; i < 16; i ++)
+ sprintf(output + i * 2, "%02x", newHash[i]);
+
+ return output;
+}
+
+gint msn_domaintree_cmp( gconstpointer a_, gconstpointer b_ )
+{
+ const char *a = a_, *b = b_;
+ gint ret;
+
+ if( !( a = strchr( a, '@' ) ) || !( b = strchr( b, '@' ) ) ||
+ ( ret = strcmp( a, b ) ) == 0 )
+ ret = strcmp( a_, b_ );
+
+ return ret;
+}
+
+struct msn_group *msn_group_by_name( struct im_connection *ic, const char *name )
+{
+ struct msn_data *md = ic->proto_data;
+ GSList *l;
+
+ for( l = md->groups; l; l = l->next )
+ {
+ struct msn_group *mg = l->data;
+
+ if( g_strcasecmp( mg->name, name ) == 0 )
+ return mg;
+ }
+
+ return NULL;
+}
+
+struct msn_group *msn_group_by_id( struct im_connection *ic, const char *id )
+{
+ struct msn_data *md = ic->proto_data;
+ GSList *l;
+
+ for( l = md->groups; l; l = l->next )
+ {
+ struct msn_group *mg = l->data;
+
+ if( g_strcasecmp( mg->id, id ) == 0 )
+ return mg;
+ }
+
+ return NULL;
+}
+
+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 );
+}
diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c
new file mode 100644
index 00000000..604e2f4e
--- /dev/null
+++ b/protocols/msn/ns.c
@@ -0,0 +1,886 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module - Notification server callbacks */
+
+/*
+ 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., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <ctype.h>
+#include "nogaim.h"
+#include "msn.h"
+#include "md5.h"
+#include "soap.h"
+#include "xmltree.h"
+
+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 );
+
+int msn_ns_write( struct im_connection *ic, int fd, const char *fmt, ... )
+{
+ struct msn_data *md = ic->proto_data;
+ va_list params;
+ char *out;
+ size_t len;
+ int st;
+
+ va_start( params, fmt );
+ out = g_strdup_vprintf( fmt, params );
+ va_end( params );
+
+ if( fd < 0 )
+ fd = md->ns->fd;
+
+ if( getenv( "BITLBEE_DEBUG" ) )
+ fprintf( stderr, "->NS%d:%s", fd, out );
+
+ len = strlen( out );
+ st = write( fd, out, len );
+ g_free( out );
+ if( st != len )
+ {
+ imcb_error( ic, "Short write() to main server" );
+ imc_logout( ic, TRUE );
+ return 0;
+ }
+
+ return 1;
+}
+
+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 )
+ {
+ 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_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 );
+
+ 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 );
+ imcb_log( ic, "Connected to server, waiting for reply" );
+ }
+
+ return FALSE;
+}
+
+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;
+}
+
+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. */
+ {
+ 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;
+
+ if( num_parts == 0 )
+ {
+ /* Hrrm... Empty command...? Ignore? */
+ return( 1 );
+ }
+
+ if( strcmp( cmd[0], "VER" ) == 0 )
+ {
+ if( cmd[2] && strncmp( cmd[2], MSNP_VER, 5 ) != 0 )
+ {
+ imcb_error( ic, "Unsupported protocol" );
+ imc_logout( ic, FALSE );
+ return( 0 );
+ }
+
+ return( msn_ns_write( ic, handler->fd, "CVR %d 0x0409 mac 10.2.0 ppc macmsgs 3.5.1 macmsgs %s\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 );
+ }
+ else if( strcmp( cmd[0], "XFR" ) == 0 )
+ {
+ char *server;
+ int port;
+
+ if( num_parts >= 6 && strcmp( cmd[2], "NS" ) == 0 )
+ {
+ b_event_remove( handler->inpa );
+ handler->inpa = -1;
+
+ 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];
+
+ 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 );
+ }
+ }
+ }
+ }
+ else
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+ }
+ else if( strcmp( cmd[0], "USR" ) == 0 )
+ {
+ if( num_parts >= 6 && strcmp( cmd[2], "SSO" ) == 0 &&
+ strcmp( cmd[3], "S" ) == 0 )
+ {
+ g_free( md->pp_policy );
+ md->pp_policy = g_strdup( cmd[4] );
+ msn_soap_passport_sso_request( ic, cmd[5] );
+ }
+ else if( strcmp( cmd[2], "OK" ) == 0 )
+ {
+ /* If the number after the handle is 0, the e-mail
+ address is unverified, which means we can't change
+ the display name. */
+ if( cmd[4][0] == '0' )
+ md->flags |= MSN_EMAIL_UNVERIFIED;
+
+ imcb_log( ic, "Authenticated, getting buddy list" );
+ msn_soap_memlist_request( ic );
+ }
+ else
+ {
+ imcb_error( ic, "Unknown authentication type" );
+ imc_logout( ic, FALSE );
+ return( 0 );
+ }
+ }
+ else if( strcmp( cmd[0], "MSG" ) == 0 )
+ {
+ if( num_parts < 4 )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ handler->msglen = atoi( cmd[3] );
+
+ if( handler->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 )
+ {
+ msn_ns_send_adl( ic );
+ 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 );
+ }
+ else if( strcmp( cmd[0], "CHL" ) == 0 )
+ {
+ char *resp;
+ int st;
+
+ if( num_parts < 3 )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ resp = msn_p11_challenge( cmd[2] );
+
+ st = msn_ns_write( ic, -1, "QRY %d %s %zd\r\n%s",
+ ++md->trId, MSNP11_PROD_ID,
+ strlen( resp ), resp );
+ g_free( resp );
+ return st;
+ }
+ else if( strcmp( cmd[0], "ILN" ) == 0 )
+ {
+ const struct msn_away_state *st;
+
+ if( num_parts < 6 )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ http_decode( cmd[5] );
+ imcb_rename_buddy( ic, cmd[3], cmd[5] );
+
+ st = msn_away_state_by_code( cmd[2] );
+ if( !st )
+ {
+ /* FIXME: Warn/Bomb about unknown away state? */
+ st = msn_away_state_list + 1;
+ }
+
+ imcb_buddy_status( ic, cmd[3], OPT_LOGGED_IN |
+ ( st != msn_away_state_list ? OPT_AWAY : 0 ),
+ st->name, NULL );
+ }
+ else if( strcmp( cmd[0], "FLN" ) == 0 )
+ {
+ if( cmd[1] == NULL )
+ return 1;
+
+ imcb_buddy_status( ic, cmd[1], 0, NULL, NULL );
+
+ msn_sb_start_keepalives( msn_sb_by_handle( ic, cmd[1] ), TRUE );
+ }
+ else if( strcmp( cmd[0], "NLN" ) == 0 )
+ {
+ const struct msn_away_state *st;
+ int cap;
+
+ if( num_parts < 6 )
+ {
+ imcb_error( ic, "Syntax error" );
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ http_decode( cmd[4] );
+ cap = atoi( cmd[5] );
+ imcb_rename_buddy( ic, cmd[2], cmd[4] );
+
+ 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, cmd[2], 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, cmd[2] ) );
+ }
+ 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( cmd[5] );
+ }
+ }
+ 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 (reason unknown)" );
+ }
+
+ imc_logout( ic, allow_reconnect );
+ 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 )
+ {
+ /* 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. */
+ if( num_parts >= 4 )
+ handler->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( isdigit( cmd[0][0] ) )
+ {
+ int num = atoi( cmd[0] );
+ const struct msn_status_code *err = msn_status_by_number( num );
+
+ imcb_error( ic, "Error reported by MSN server: %s", err->text );
+
+ if( err->flags & STATUS_FATAL )
+ {
+ imc_logout( ic, TRUE );
+ return( 0 );
+ }
+
+ /* Oh yes, errors can have payloads too now. Discard them for now. */
+ if( num_parts >= 3 )
+ handler->msglen = atoi( cmd[2] );
+ }
+ else
+ {
+ /* debug( "Received unknown command from main server: %s", cmd[0] ); */
+ }
+
+ return( 1 );
+}
+
+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;
+ char *body;
+ int blen = 0;
+
+ if( !num_parts )
+ return( 1 );
+
+ if( ( body = strstr( msg, "\r\n\r\n" ) ) )
+ {
+ body += 4;
+ blen = msglen - ( body - msg );
+ }
+
+ if( strcmp( cmd[0], "MSG" ) == 0 )
+ {
+ if( g_strcasecmp( cmd[1], "Hotmail" ) == 0 )
+ {
+ char *ct = msn_findheader( msg, "Content-Type:", msglen );
+
+ if( !ct )
+ return( 1 );
+
+ if( g_strncasecmp( ct, "application/x-msmsgssystemmessage", 33 ) == 0 )
+ {
+ char *mtype;
+ char *arg1;
+
+ if( !body )
+ return( 1 );
+
+ mtype = msn_findheader( body, "Type:", blen );
+ arg1 = msn_findheader( body, "Arg1:", blen );
+
+ if( mtype && strcmp( mtype, "1" ) == 0 )
+ {
+ if( arg1 )
+ imcb_log( ic, "The server is going down for maintenance in %s minutes.", arg1 );
+ }
+
+ g_free( arg1 );
+ g_free( mtype );
+ }
+ else if( g_strncasecmp( ct, "text/x-msmsgsprofile", 20 ) == 0 )
+ {
+ /* We don't care about this profile for now... */
+ }
+ else if( g_strncasecmp( ct, "text/x-msmsgsinitialemailnotification", 37 ) == 0 )
+ {
+ if( set_getbool( &ic->acc->set, "mail_notifications" ) )
+ {
+ char *inbox = msn_findheader( body, "Inbox-Unread:", blen );
+ char *folders = msn_findheader( body, "Folders-Unread:", blen );
+
+ if( inbox && folders )
+ imcb_log( ic, "INBOX contains %s new messages, plus %s messages in other folders.", inbox, folders );
+
+ g_free( inbox );
+ g_free( folders );
+ }
+ }
+ else if( g_strncasecmp( ct, "text/x-msmsgsemailnotification", 30 ) == 0 )
+ {
+ if( set_getbool( &ic->acc->set, "mail_notifications" ) )
+ {
+ char *from = msn_findheader( body, "From-Addr:", blen );
+ char *fromname = msn_findheader( body, "From:", blen );
+
+ if( from && fromname )
+ imcb_log( ic, "Received an e-mail message from %s <%s>.", fromname, from );
+
+ g_free( from );
+ g_free( fromname );
+ }
+ }
+ else if( g_strncasecmp( ct, "text/x-msmsgsactivemailnotification", 35 ) == 0 )
+ {
+ /* Sorry, but this one really is *USELESS* */
+ }
+ else
+ {
+ debug( "Can't handle %s packet from notification server", ct );
+ }
+
+ g_free( ct );
+ }
+ }
+ else if( strcmp( cmd[0], "UBX" ) == 0 )
+ {
+ struct xt_node *ubx, *psm;
+ char *psm_text = NULL;
+
+ ubx = xt_from_string( msg );
+ if( ubx && strcmp( ubx->name, "Data" ) == 0 &&
+ ( psm = xt_find_node( ubx->children, "PSM" ) ) )
+ psm_text = psm->text;
+
+ imcb_buddy_status_msg( ic, cmd[1], psm_text );
+ xt_free_node( ubx );
+ }
+ else if( strcmp( cmd[0], "ADL" ) == 0 )
+ {
+ struct xt_node *adl, *d, *c;
+
+ if( !( adl = xt_from_string( msg ) ) )
+ return 1;
+
+ for( d = adl->children; d; d = d->next )
+ {
+ char *dn;
+ if( strcmp( d->name, "d" ) != 0 ||
+ ( dn = xt_find_attr( d, "n" ) ) == NULL )
+ continue;
+ for( c = d->children; c; c = c->next )
+ {
+ bee_user_t *bu;
+ struct msn_buddy_data *bd;
+ char *cn, *handle, *f, *l;
+ int flags;
+
+ if( strcmp( c->name, "c" ) != 0 ||
+ ( l = xt_find_attr( c, "l" ) ) == NULL ||
+ ( cn = xt_find_attr( c, "n" ) ) == NULL )
+ continue;
+
+ handle = g_strdup_printf( "%s@%s", cn, dn );
+ if( !( ( bu = bee_user_by_handle( ic->bee, ic, handle ) ) ||
+ ( bu = bee_user_new( ic->bee, ic, handle, 0 ) ) ) )
+ {
+ g_free( handle );
+ continue;
+ }
+ g_free( handle );
+ bd = bu->data;
+
+ if( ( f = xt_find_attr( c, "f" ) ) )
+ {
+ http_decode( f );
+ imcb_rename_buddy( ic, bu->handle, f );
+ }
+
+ flags = atoi( l ) & 15;
+ if( bd->flags != flags )
+ {
+ bd->flags = flags;
+ msn_buddy_ask( bu );
+ }
+ }
+ }
+ }
+
+ return( 1 );
+}
+
+void msn_auth_got_passport_token( struct im_connection *ic, const char *token, const char *error )
+{
+ struct msn_data *md;
+
+ /* Dead connection? */
+ if( g_slist_find( msn_connections, ic ) == NULL )
+ return;
+
+ md = ic->proto_data;
+
+ if( token )
+ {
+ msn_ns_write( ic, -1, "USR %d SSO S %s %s\r\n", ++md->trId, md->tokens[0], token );
+ }
+ else
+ {
+ imcb_error( ic, "Error during Passport authentication: %s", error );
+ imc_logout( ic, TRUE );
+ }
+}
+
+void msn_auth_got_contact_list( struct im_connection *ic )
+{
+ struct msn_data *md;
+
+ /* Dead connection? */
+ if( g_slist_find( msn_connections, ic ) == NULL )
+ return;
+
+ md = ic->proto_data;
+ msn_ns_write( ic, -1, "BLP %d %s\r\n", ++md->trId, "BL" );
+}
+
+static gboolean msn_ns_send_adl_1( gpointer key, gpointer value, gpointer data )
+{
+ struct xt_node *adl = data, *d, *c;
+ struct bee_user *bu = value;
+ struct msn_buddy_data *bd = bu->data;
+ struct msn_data *md = bu->ic->proto_data;
+ char handle[strlen(bu->handle)];
+ char *domain;
+ char l[4];
+
+ if( ( bd->flags & 7 ) == 0 || ( bd->flags & MSN_BUDDY_ADL_SYNCED ) )
+ return FALSE;
+
+ strcpy( handle, bu->handle );
+ if( ( domain = strchr( handle, '@' ) ) == NULL ) /* WTF */
+ return FALSE;
+ *domain = '\0';
+ domain ++;
+
+ if( ( d = adl->children ) == NULL ||
+ g_strcasecmp( xt_find_attr( d, "n" ), domain ) != 0 )
+ {
+ d = xt_new_node( "d", NULL, NULL );
+ xt_add_attr( d, "n", domain );
+ xt_insert_child( adl, d );
+ }
+
+ g_snprintf( l, sizeof( l ), "%d", bd->flags & 7 );
+ 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" ); /* 1 means normal, 4 means mobile? */
+ xt_insert_child( d, c );
+
+ /* Do this in batches of 100. */
+ bd->flags |= MSN_BUDDY_ADL_SYNCED;
+ return (--md->adl_todo % 140) == 0;
+}
+
+static void msn_ns_send_adl( struct im_connection *ic )
+{
+ struct xt_node *adl;
+ struct msn_data *md = ic->proto_data;
+ char *adls;
+
+ adl = xt_new_node( "ml", NULL, NULL );
+ xt_add_attr( adl, "l", "1" );
+ g_tree_foreach( md->domaintree, msn_ns_send_adl_1, adl );
+ if( adl->children == NULL )
+ {
+ /* This tells the caller that we're done now. */
+ md->adl_todo = -1;
+ xt_free_node( adl );
+ return;
+ }
+
+ adls = xt_to_string( adl );
+ xt_free_node( adl );
+ msn_ns_write( ic, -1, "ADL %d %zd\r\n%s", ++md->trId, strlen( adls ), adls );
+ g_free( adls );
+}
+
+static void msn_ns_send_adl_start( struct im_connection *ic )
+{
+ struct msn_data *md;
+ GSList *l;
+
+ /* Dead connection? */
+ if( g_slist_find( msn_connections, ic ) == NULL )
+ return;
+
+ md = ic->proto_data;
+ md->adl_todo = 0;
+ for( l = ic->bee->users; l; l = l->next )
+ {
+ bee_user_t *bu = l->data;
+ struct msn_buddy_data *bd = bu->data;
+
+ if( bu->ic != ic || ( bd->flags & 7 ) == 0 )
+ continue;
+
+ bd->flags &= ~MSN_BUDDY_ADL_SYNCED;
+ md->adl_todo++;
+ }
+
+ msn_ns_send_adl( ic );
+}
+
+int msn_ns_finish_login( struct im_connection *ic )
+{
+ struct msn_data *md = ic->proto_data;
+
+ if( ic->flags & OPT_LOGGED_IN )
+ return 1;
+
+ if( md->adl_todo < 0 )
+ md->flags |= MSN_DONE_ADL;
+
+ 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" ) );
+ }
+
+ return 1;
+}
diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c
new file mode 100644
index 00000000..37ac2889
--- /dev/null
+++ b/protocols/msn/sb.c
@@ -0,0 +1,806 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 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., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 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", 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, 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\r\n", ++sb->trId, ic->acc->user, sb->key );
+ else
+ g_snprintf( buf, sizeof( buf ), "ANS %d %s %s %d\r\n", ++sb->trId, ic->acc->user, 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];
+
+ if( num == 1 )
+ {
+ 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;
+ }
+
+ imcb_chat_add_buddy( sb->chat, cmd[4] );
+
+ if( num == tot )
+ {
+ 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 || !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 );
+ }
+
+ 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( 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 )
+ {
+ imcb_chat_remove_buddy( sb->chat, cmd[1], "" );
+ }
+ else
+ {
+ /* PANIC! */
+ }
+ }
+ else if( 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_soap_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;
+ int blen = 0;
+
+ if( !num_parts )
+ return( 1 );
+
+ if( ( body = strstr( msg, "\r\n\r\n" ) ) )
+ {
+ body += 4;
+ blen = msglen - ( body - msg );
+ }
+
+ if( strcmp( cmd[0], "MSG" ) == 0 )
+ {
+ char *ct = msn_findheader( 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 = msn_findheader( body, "Invitation-Command:", blen );
+ char *cookie = msn_findheader( 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 = msn_findheader( 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;
+ }
+}
diff --git a/protocols/msn/soap.c b/protocols/msn/soap.c
new file mode 100644
index 00000000..dac46a75
--- /dev/null
+++ b/protocols/msn/soap.c
@@ -0,0 +1,1162 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module - All the SOAPy XML stuff.
+ Some manager at Microsoft apparently thought MSNP wasn't XMLy enough so
+ someone stepped up and changed that. This is the result. Kilobytes and
+ more kilobytes of XML vomit to transfer tiny bits of informaiton. */
+
+/*
+ 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., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "http_client.h"
+#include "soap.h"
+#include "msn.h"
+#include "bitlbee.h"
+#include "url.h"
+#include "misc.h"
+#include "sha1.h"
+#include "base64.h"
+#include "xmltree.h"
+#include <ctype.h>
+#include <errno.h>
+
+/* This file tries to make SOAP stuff pretty simple to do by letting you just
+ provide a function to build a request, a few functions to parse various
+ parts of the response, and a function to run when the full response was
+ received and parsed. See the various examples below. */
+
+typedef enum
+{
+ MSN_SOAP_OK,
+ MSN_SOAP_RETRY,
+ MSN_SOAP_REAUTH,
+ MSN_SOAP_ABORT,
+} msn_soap_result_t;
+
+struct msn_soap_req_data;
+typedef int (*msn_soap_func) ( struct msn_soap_req_data * );
+
+struct msn_soap_req_data
+{
+ void *data;
+ struct im_connection *ic;
+ int ttl;
+
+ char *url, *action, *payload;
+ struct http_request *http_req;
+
+ const struct xt_handler_entry *xml_parser;
+ msn_soap_func build_request, handle_response, free_data;
+};
+
+static int msn_soap_send_request( struct msn_soap_req_data *req );
+static void msn_soap_free( struct msn_soap_req_data *soap_req );
+static void msn_soap_debug_print( const char *headers, const char *payload );
+
+static int msn_soap_start( struct im_connection *ic,
+ void *data,
+ msn_soap_func build_request,
+ const struct xt_handler_entry *xml_parser,
+ msn_soap_func handle_response,
+ msn_soap_func free_data )
+{
+ struct msn_soap_req_data *req = g_new0( struct msn_soap_req_data, 1 );
+
+ req->ic = ic;
+ req->data = data;
+ req->xml_parser = xml_parser;
+ req->build_request = build_request;
+ req->handle_response = handle_response;
+ req->free_data = free_data;
+ req->ttl = 3;
+
+ return msn_soap_send_request( req );
+}
+
+static void msn_soap_handle_response( struct http_request *http_req );
+
+static int msn_soap_send_request( struct msn_soap_req_data *soap_req )
+{
+ char *http_req;
+ char *soap_action = NULL;
+ url_t url;
+
+ soap_req->build_request( soap_req );
+
+ if( soap_req->action )
+ soap_action = g_strdup_printf( "SOAPAction: \"%s\"\r\n", soap_req->action );
+
+ url_set( &url, soap_req->url );
+ http_req = g_strdup_printf( SOAP_HTTP_REQUEST, url.file, url.host,
+ soap_action ? soap_action : "",
+ strlen( soap_req->payload ), soap_req->payload );
+
+ msn_soap_debug_print( http_req, soap_req->payload );
+
+ soap_req->http_req = http_dorequest( url.host, url.port, url.proto == PROTO_HTTPS,
+ http_req, msn_soap_handle_response, soap_req );
+
+ g_free( http_req );
+ g_free( soap_action );
+
+ return soap_req->http_req != NULL;
+}
+
+static void msn_soap_handle_response( struct http_request *http_req )
+{
+ struct msn_soap_req_data *soap_req = http_req->data;
+ int st;
+
+ if( g_slist_find( msn_connections, soap_req->ic ) == NULL )
+ {
+ msn_soap_free( soap_req );
+ return;
+ }
+
+ msn_soap_debug_print( http_req->reply_headers, http_req->reply_body );
+
+ if( http_req->body_size > 0 )
+ {
+ struct xt_parser *parser;
+ struct xt_node *err;
+
+ parser = xt_new( soap_req->xml_parser, soap_req );
+ xt_feed( parser, http_req->reply_body, http_req->body_size );
+ if( http_req->status_code == 500 &&
+ ( err = xt_find_path( parser->root, "soap:Body/soap:Fault/detail/errorcode" ) ) &&
+ err->text_len > 0 )
+ {
+ if( strcmp( err->text, "PassportAuthFail" ) == 0 )
+ {
+ xt_free( parser );
+ st = MSN_SOAP_REAUTH;
+ goto fail;
+ }
+ /* TODO: Handle/report other errors. */
+ }
+
+ xt_handle( parser, NULL, -1 );
+ xt_free( parser );
+ }
+
+ st = soap_req->handle_response( soap_req );
+
+fail:
+ g_free( soap_req->url );
+ g_free( soap_req->action );
+ g_free( soap_req->payload );
+ soap_req->url = soap_req->action = soap_req->payload = NULL;
+
+ if( st == MSN_SOAP_RETRY && --soap_req->ttl )
+ {
+ msn_soap_send_request( soap_req );
+ }
+ else if( st == MSN_SOAP_REAUTH )
+ {
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ if( !( md->flags & MSN_REAUTHING ) )
+ {
+ /* Nonce shouldn't actually be touched for re-auths. */
+ msn_soap_passport_sso_request( soap_req->ic, "blaataap" );
+ md->flags |= MSN_REAUTHING;
+ }
+ md->soapq = g_slist_append( md->soapq, soap_req );
+ }
+ else
+ {
+ soap_req->free_data( soap_req );
+ g_free( soap_req );
+ }
+}
+
+static char *msn_soap_abservice_build( const char *body_fmt, const char *scenario, const char *ticket, ... )
+{
+ va_list params;
+ char *ret, *format, *body;
+
+ format = g_markup_printf_escaped( SOAP_ABSERVICE_PAYLOAD, scenario, ticket );
+
+ va_start( params, ticket );
+ body = g_strdup_vprintf( body_fmt, params );
+ va_end( params );
+
+ ret = g_strdup_printf( format, body );
+ g_free( body );
+ g_free( format );
+
+ return ret;
+}
+
+static void msn_soap_debug_print( const char *headers, const char *payload )
+{
+ char *s;
+ int st;
+
+ if( !getenv( "BITLBEE_DEBUG" ) )
+ return;
+
+ if( ( s = strstr( headers, "\r\n\r\n" ) ) )
+ st = write( 1, s, s - headers + 4 );
+ else
+ st = write( 1, headers, strlen( headers ) );
+
+#ifdef DEBUG
+ {
+ struct xt_node *xt = xt_from_string( payload );
+ if( xt )
+ xt_print( xt );
+ xt_free_node( xt );
+ }
+#endif
+}
+
+int msn_soapq_flush( struct im_connection *ic, gboolean resend )
+{
+ struct msn_data *md = ic->proto_data;
+
+ while( md->soapq )
+ {
+ if( resend )
+ msn_soap_send_request( (struct msn_soap_req_data*) md->soapq->data );
+ else
+ msn_soap_free( (struct msn_soap_req_data*) md->soapq->data );
+ md->soapq = g_slist_remove( md->soapq, md->soapq->data );
+ }
+
+ return MSN_SOAP_OK;
+}
+
+static void msn_soap_free( struct msn_soap_req_data *soap_req )
+{
+ soap_req->free_data( soap_req );
+ g_free( soap_req->url );
+ g_free( soap_req->action );
+ g_free( soap_req->payload );
+ g_free( soap_req );
+}
+
+
+/* passport_sso: Authentication MSNP15+ */
+
+struct msn_soap_passport_sso_data
+{
+ char *nonce;
+ char *secret;
+ char *error;
+ char *redirect;
+};
+
+static int msn_soap_passport_sso_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_passport_sso_data *sd = soap_req->data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_data *md = ic->proto_data;
+ char pass[MAX_PASSPORT_PWLEN+1];
+
+ if( sd->redirect )
+ {
+ soap_req->url = sd->redirect;
+ sd->redirect = NULL;
+ }
+ /* MS changed this URL and broke the old MSN-specific one. The generic
+ one works, forwarding us to a msn.com URL that works. Takes an extra
+ second, but that's better than not being able to log in at all. :-/
+ else if( g_str_has_suffix( ic->acc->user, "@msn.com" ) )
+ soap_req->url = g_strdup( SOAP_PASSPORT_SSO_URL_MSN );
+ */
+ else
+ soap_req->url = g_strdup( SOAP_PASSPORT_SSO_URL );
+
+ strncpy( pass, ic->acc->pass, MAX_PASSPORT_PWLEN );
+ pass[MAX_PASSPORT_PWLEN] = '\0';
+ soap_req->payload = g_markup_printf_escaped( SOAP_PASSPORT_SSO_PAYLOAD,
+ ic->acc->user, pass, md->pp_policy );
+
+ return MSN_SOAP_OK;
+}
+
+static xt_status msn_soap_passport_sso_token( struct xt_node *node, gpointer data )
+{
+ struct msn_soap_req_data *soap_req = data;
+ struct msn_soap_passport_sso_data *sd = soap_req->data;
+ struct msn_data *md = soap_req->ic->proto_data;
+ struct xt_node *p;
+ char *id;
+
+ if( ( id = xt_find_attr( node, "Id" ) ) == NULL )
+ return XT_HANDLED;
+ id += strlen( id ) - 1;
+ if( *id == '1' &&
+ ( p = xt_find_path( node, "../../wst:RequestedProofToken/wst:BinarySecret" ) ) &&
+ p->text )
+ sd->secret = g_strdup( p->text );
+
+ *id -= '1';
+ if( *id >= 0 && *id < sizeof( md->tokens ) / sizeof( md->tokens[0] ) )
+ {
+ g_free( md->tokens[(int)*id] );
+ md->tokens[(int)*id] = g_strdup( node->text );
+ }
+
+ return XT_HANDLED;
+}
+
+static xt_status msn_soap_passport_failure( struct xt_node *node, gpointer data )
+{
+ struct msn_soap_req_data *soap_req = data;
+ struct msn_soap_passport_sso_data *sd = soap_req->data;
+ struct xt_node *code = xt_find_node( node->children, "faultcode" );
+ struct xt_node *string = xt_find_node( node->children, "faultstring" );
+ struct xt_node *url;
+
+ if( code == NULL || code->text_len == 0 )
+ sd->error = g_strdup( "Unknown error" );
+ else if( strcmp( code->text, "psf:Redirect" ) == 0 &&
+ ( url = xt_find_node( node->children, "psf:redirectUrl" ) ) &&
+ url->text_len > 0 )
+ sd->redirect = g_strdup( url->text );
+ else
+ sd->error = g_strdup_printf( "%s (%s)", code->text, string && string->text_len ?
+ string->text : "no description available" );
+
+ return XT_HANDLED;
+}
+
+static const struct xt_handler_entry msn_soap_passport_sso_parser[] = {
+ { "wsse:BinarySecurityToken", "wst:RequestedSecurityToken", msn_soap_passport_sso_token },
+ { "S:Fault", "S:Envelope", msn_soap_passport_failure },
+ { NULL, NULL, NULL }
+};
+
+static char *msn_key_fuckery( char *key, int key_len, char *type )
+{
+ unsigned char hash1[20+strlen(type)+1];
+ unsigned char hash2[20];
+ char *ret;
+
+ sha1_hmac( key, key_len, type, 0, hash1 );
+ strcpy( (char*) hash1 + 20, type );
+ sha1_hmac( key, key_len, (char*) hash1, sizeof( hash1 ) - 1, hash2 );
+
+ /* This is okay as hash1 is read completely before it's overwritten. */
+ sha1_hmac( key, key_len, (char*) hash1, 20, hash1 );
+ sha1_hmac( key, key_len, (char*) hash1, sizeof( hash1 ) - 1, hash1 );
+
+ ret = g_malloc( 24 );
+ memcpy( ret, hash2, 20 );
+ memcpy( ret + 20, hash1, 4 );
+ return ret;
+}
+
+static int msn_soap_passport_sso_handle_response( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_passport_sso_data *sd = soap_req->data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_data *md = ic->proto_data;
+ char *key1, *key2, *key3, *blurb64;
+ int key1_len;
+ unsigned char *padnonce, *des3res;
+ struct
+ {
+ unsigned int uStructHeaderSize; // 28. Does not count data
+ unsigned int uCryptMode; // CRYPT_MODE_CBC (1)
+ unsigned int uCipherType; // TripleDES (0x6603)
+ unsigned int uHashType; // SHA1 (0x8004)
+ unsigned int uIVLen; // 8
+ unsigned int uHashLen; // 20
+ unsigned int uCipherLen; // 72
+ unsigned char iv[8];
+ unsigned char hash[20];
+ unsigned char cipherbytes[72];
+ } blurb = {
+ GUINT32_TO_LE( 28 ),
+ GUINT32_TO_LE( 1 ),
+ GUINT32_TO_LE( 0x6603 ),
+ GUINT32_TO_LE( 0x8004 ),
+ GUINT32_TO_LE( 8 ),
+ GUINT32_TO_LE( 20 ),
+ GUINT32_TO_LE( 72 ),
+ };
+
+ if( sd->redirect )
+ return MSN_SOAP_RETRY;
+
+ if( md->soapq )
+ {
+ md->flags &= ~MSN_REAUTHING;
+ return msn_soapq_flush( ic, TRUE );
+ }
+
+ if( sd->secret == NULL )
+ {
+ msn_auth_got_passport_token( ic, NULL, sd->error );
+ return MSN_SOAP_OK;
+ }
+
+ key1_len = base64_decode( sd->secret, (unsigned char**) &key1 );
+
+ key2 = msn_key_fuckery( key1, key1_len, "WS-SecureConversationSESSION KEY HASH" );
+ key3 = msn_key_fuckery( key1, key1_len, "WS-SecureConversationSESSION KEY ENCRYPTION" );
+
+ sha1_hmac( key2, 24, sd->nonce, 0, blurb.hash );
+ padnonce = g_malloc( strlen( sd->nonce ) + 8 );
+ strcpy( (char*) padnonce, sd->nonce );
+ memset( padnonce + strlen( sd->nonce ), 8, 8 );
+
+ random_bytes( blurb.iv, 8 );
+
+ ssl_des3_encrypt( (unsigned char*) key3, 24, padnonce, strlen( sd->nonce ) + 8, blurb.iv, &des3res );
+ memcpy( blurb.cipherbytes, des3res, 72 );
+
+ blurb64 = base64_encode( (unsigned char*) &blurb, sizeof( blurb ) );
+ msn_auth_got_passport_token( ic, blurb64, NULL );
+
+ g_free( padnonce );
+ g_free( blurb64 );
+ g_free( des3res );
+ g_free( key1 );
+ g_free( key2 );
+ g_free( key3 );
+
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_passport_sso_free_data( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_passport_sso_data *sd = soap_req->data;
+
+ g_free( sd->nonce );
+ g_free( sd->secret );
+ g_free( sd->error );
+ g_free( sd->redirect );
+ g_free( sd );
+
+ return MSN_SOAP_OK;
+}
+
+int msn_soap_passport_sso_request( struct im_connection *ic, const char *nonce )
+{
+ struct msn_soap_passport_sso_data *sd = g_new0( struct msn_soap_passport_sso_data, 1 );
+
+ sd->nonce = g_strdup( nonce );
+
+ return msn_soap_start( ic, sd, msn_soap_passport_sso_build_request,
+ msn_soap_passport_sso_parser,
+ msn_soap_passport_sso_handle_response,
+ msn_soap_passport_sso_free_data );
+}
+
+
+/* oim_send: Sending offline messages */
+
+struct msn_soap_oim_send_data
+{
+ char *to;
+ char *msg;
+ int number;
+ msn_soap_result_t need_retry;
+};
+
+static int msn_soap_oim_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_oim_send_data *oim = soap_req->data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_data *md = ic->proto_data;
+ char *display_name_b64;
+
+ display_name_b64 = tobase64( set_getstr( &ic->acc->set, "display_name" ) );
+
+ soap_req->url = g_strdup( SOAP_OIM_SEND_URL );
+ soap_req->action = g_strdup( SOAP_OIM_SEND_ACTION );
+ soap_req->payload = g_markup_printf_escaped( SOAP_OIM_SEND_PAYLOAD,
+ ic->acc->user, display_name_b64, MSNP_VER, MSNP_BUILD,
+ oim->to, md->tokens[2],
+ MSNP11_PROD_ID, md->lock_key ? md->lock_key : "",
+ oim->number, oim->number, oim->msg );
+
+ g_free( display_name_b64 );
+ oim->need_retry = MSN_SOAP_OK;
+
+ return MSN_SOAP_OK;
+}
+
+static xt_status msn_soap_oim_reauth( struct xt_node *node, gpointer data )
+{
+ struct msn_soap_req_data *soap_req = data;
+ struct msn_soap_oim_send_data *oim = soap_req->data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_data *md = ic->proto_data;
+ struct xt_node *c;
+
+ if( ( c = xt_find_node( node->children, "LockKeyChallenge" ) ) && c->text_len > 0 )
+ {
+ g_free( md->lock_key );
+ md->lock_key = msn_p11_challenge( c->text );
+ oim->need_retry = MSN_SOAP_RETRY;
+ }
+ if( xt_find_node( node->children, "RequiredAuthPolicy" ) )
+ {
+ oim->need_retry = MSN_SOAP_REAUTH;
+ }
+
+ return XT_HANDLED;
+}
+
+static const struct xt_handler_entry msn_soap_oim_send_parser[] = {
+ { "detail", "soap:Fault", msn_soap_oim_reauth },
+ { NULL, NULL, NULL }
+};
+
+static int msn_soap_oim_handle_response( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_oim_send_data *oim = soap_req->data;
+
+ if( soap_req->http_req->status_code == 500 && oim->need_retry && soap_req->ttl > 0 )
+ {
+ return oim->need_retry;
+ }
+ else if( soap_req->http_req->status_code == 200 )
+ {
+ /* Noise..
+ imcb_log( soap_req->ic, "Offline message successfully delivered to %s", oim->to );
+ */
+ return MSN_SOAP_OK;
+ }
+ else
+ {
+ char *dec = frombase64( oim->msg );
+ imcb_log( soap_req->ic, "Failed to deliver offline message to %s:\n%s", oim->to, dec );
+ g_free( dec );
+ return MSN_SOAP_ABORT;
+ }
+}
+
+static int msn_soap_oim_free_data( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_oim_send_data *oim = soap_req->data;
+
+ g_free( oim->to );
+ g_free( oim->msg );
+ g_free( oim );
+
+ return MSN_SOAP_OK;
+}
+
+int msn_soap_oim_send( struct im_connection *ic, const char *to, const char *msg )
+{
+ struct msn_soap_oim_send_data *data;
+
+ /* Don't send any of the special messages since they creep people out. :-) */
+ if( strncmp( msg, "\r\r", 2 ) == 0 )
+ return 0;
+
+ data = g_new0( struct msn_soap_oim_send_data, 1 );
+ data->to = g_strdup( to );
+ data->msg = tobase64( msg );
+ data->number = 1;
+
+ return msn_soap_start( ic, data, msn_soap_oim_build_request,
+ msn_soap_oim_send_parser,
+ msn_soap_oim_handle_response,
+ msn_soap_oim_free_data );
+}
+
+int msn_soap_oim_send_queue( struct im_connection *ic, GSList **msgq )
+{
+ GSList *l;
+ char *n = NULL;
+
+ for( l = *msgq; l; l = l->next )
+ {
+ struct msn_message *m = l->data;
+
+ if( n == NULL )
+ n = m->who;
+ if( strcmp( n, m->who ) == 0 )
+ msn_soap_oim_send( ic, m->who, m->text );
+ }
+
+ 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 );
+ }
+
+ return 1;
+}
+
+
+/* memlist: Fetching the membership list (NOT address book) */
+
+static int msn_soap_memlist_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ soap_req->url = g_strdup( SOAP_MEMLIST_URL );
+ soap_req->action = g_strdup( SOAP_MEMLIST_ACTION );
+ soap_req->payload = msn_soap_abservice_build( SOAP_MEMLIST_PAYLOAD, "Initial", md->tokens[1] );
+
+ return 1;
+}
+
+static xt_status msn_soap_memlist_member( struct xt_node *node, gpointer data )
+{
+ bee_user_t *bu;
+ struct msn_buddy_data *bd;
+ struct xt_node *p;
+ char *role = NULL, *handle = NULL;
+ struct msn_soap_req_data *soap_req = data;
+ struct im_connection *ic = soap_req->ic;
+
+ if( ( p = xt_find_path( node, "../../MemberRole" ) ) )
+ role = p->text;
+
+ if( ( p = xt_find_node( node->children, "PassportName" ) ) )
+ handle = p->text;
+
+ if( !role || !handle ||
+ !( ( bu = bee_user_by_handle( ic->bee, ic, handle ) ) ||
+ ( bu = bee_user_new( ic->bee, ic, handle, 0 ) ) ) )
+ return XT_HANDLED;
+
+ bd = bu->data;
+ if( strcmp( role, "Allow" ) == 0 )
+ {
+ bd->flags |= MSN_BUDDY_AL;
+ ic->permit = g_slist_prepend( ic->permit, g_strdup( handle ) );
+ }
+ else if( strcmp( role, "Block" ) == 0 )
+ {
+ bd->flags |= MSN_BUDDY_BL;
+ ic->deny = g_slist_prepend( ic->deny, g_strdup( handle ) );
+ }
+ else if( strcmp( role, "Reverse" ) == 0 )
+ bd->flags |= MSN_BUDDY_RL;
+ else if( strcmp( role, "Pending" ) == 0 )
+ bd->flags |= MSN_BUDDY_PL;
+
+ if( getenv( "BITLBEE_DEBUG" ) )
+ printf( "%p %s %d\n", bu, handle, bd->flags );
+
+ return XT_HANDLED;
+}
+
+static const struct xt_handler_entry msn_soap_memlist_parser[] = {
+ { "Member", "Members", msn_soap_memlist_member },
+ { NULL, NULL, NULL }
+};
+
+static int msn_soap_memlist_handle_response( struct msn_soap_req_data *soap_req )
+{
+ msn_soap_addressbook_request( soap_req->ic );
+
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_memlist_free_data( struct msn_soap_req_data *soap_req )
+{
+ return 0;
+}
+
+int msn_soap_memlist_request( struct im_connection *ic )
+{
+ return msn_soap_start( ic, NULL, msn_soap_memlist_build_request,
+ msn_soap_memlist_parser,
+ msn_soap_memlist_handle_response,
+ msn_soap_memlist_free_data );
+}
+
+/* Variant: Adding/Removing people */
+struct msn_soap_memlist_edit_data
+{
+ char *handle;
+ gboolean add;
+ msn_buddy_flags_t list;
+};
+
+static int msn_soap_memlist_edit_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+ struct msn_soap_memlist_edit_data *med = soap_req->data;
+ char *add, *scenario, *list;
+
+ soap_req->url = g_strdup( SOAP_MEMLIST_URL );
+ if( med->add )
+ {
+ soap_req->action = g_strdup( SOAP_MEMLIST_ADD_ACTION );
+ add = "Add";
+ }
+ else
+ {
+ soap_req->action = g_strdup( SOAP_MEMLIST_DEL_ACTION );
+ add = "Delete";
+ }
+ switch( med->list )
+ {
+ case MSN_BUDDY_AL:
+ scenario = "BlockUnblock";
+ list = "Allow";
+ break;
+ case MSN_BUDDY_BL:
+ scenario = "BlockUnblock";
+ list = "Block";
+ break;
+ case MSN_BUDDY_RL:
+ scenario = "Timer";
+ list = "Reverse";
+ break;
+ case MSN_BUDDY_PL:
+ default:
+ scenario = "Timer";
+ list = "Pending";
+ break;
+ }
+ soap_req->payload = msn_soap_abservice_build( SOAP_MEMLIST_EDIT_PAYLOAD,
+ scenario, md->tokens[1], add, list, med->handle, add );
+
+ return 1;
+}
+
+static int msn_soap_memlist_edit_handle_response( struct msn_soap_req_data *soap_req )
+{
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_memlist_edit_free_data( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_memlist_edit_data *med = soap_req->data;
+
+ g_free( med->handle );
+ g_free( med );
+
+ return 0;
+}
+
+int msn_soap_memlist_edit( struct im_connection *ic, const char *handle, gboolean add, int list )
+{
+ struct msn_soap_memlist_edit_data *med;
+
+ med = g_new0( struct msn_soap_memlist_edit_data, 1 );
+ med->handle = g_strdup( handle );
+ med->add = add;
+ med->list = list;
+
+ return msn_soap_start( ic, med, msn_soap_memlist_edit_build_request,
+ NULL,
+ msn_soap_memlist_edit_handle_response,
+ msn_soap_memlist_edit_free_data );
+}
+
+
+/* addressbook: Fetching the membership list (NOT address book) */
+
+static int msn_soap_addressbook_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL );
+ soap_req->action = g_strdup( SOAP_ADDRESSBOOK_ACTION );
+ soap_req->payload = msn_soap_abservice_build( SOAP_ADDRESSBOOK_PAYLOAD, "Initial", md->tokens[1] );
+
+ return 1;
+}
+
+static xt_status msn_soap_addressbook_group( struct xt_node *node, gpointer data )
+{
+ struct xt_node *p;
+ char *id = NULL, *name = NULL;
+ struct msn_soap_req_data *soap_req = data;
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ if( ( p = xt_find_path( node, "../groupId" ) ) )
+ id = p->text;
+
+ if( ( p = xt_find_node( node->children, "name" ) ) )
+ name = p->text;
+
+ if( id && name )
+ {
+ struct msn_group *mg = g_new0( struct msn_group, 1 );
+ mg->id = g_strdup( id );
+ mg->name = g_strdup( name );
+ md->groups = g_slist_prepend( md->groups, mg );
+ }
+
+ if( getenv( "BITLBEE_DEBUG" ) )
+ printf( "%s %s\n", id, name );
+
+ return XT_HANDLED;
+}
+
+static xt_status msn_soap_addressbook_contact( struct xt_node *node, gpointer data )
+{
+ bee_user_t *bu;
+ struct msn_buddy_data *bd;
+ struct xt_node *p;
+ char *id = NULL, *type = NULL, *handle = NULL, *is_msgr = "false",
+ *display_name = NULL, *group_id = NULL;
+ struct msn_soap_req_data *soap_req = data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_group *group;
+
+ if( ( p = xt_find_path( node, "../contactId" ) ) )
+ id = p->text;
+ if( ( p = xt_find_node( node->children, "contactType" ) ) )
+ type = p->text;
+ if( ( p = xt_find_node( node->children, "passportName" ) ) )
+ handle = p->text;
+ if( ( p = xt_find_node( node->children, "displayName" ) ) )
+ display_name = p->text;
+ if( ( p = xt_find_node( node->children, "isMessengerUser" ) ) )
+ is_msgr = p->text;
+ if( ( p = xt_find_path( node, "groupIds/guid" ) ) )
+ group_id = p->text;
+
+ if( type && g_strcasecmp( type, "me" ) == 0 )
+ {
+ set_t *set = set_find( &ic->acc->set, "display_name" );
+ g_free( set->value );
+ set->value = g_strdup( display_name );
+
+ /* Try to fetch the profile; if the user has one, that's where
+ we can find the persistent display_name. */
+ if( ( p = xt_find_node( node->children, "CID" ) ) && p->text )
+ msn_soap_profile_get( ic, p->text );
+
+ return XT_HANDLED;
+ }
+
+ if( !bool2int( is_msgr ) || handle == NULL )
+ return XT_HANDLED;
+
+ if( !( bu = bee_user_by_handle( ic->bee, ic, handle ) ) &&
+ !( bu = bee_user_new( ic->bee, ic, handle, 0 ) ) )
+ return XT_HANDLED;
+
+ bd = bu->data;
+ bd->flags |= MSN_BUDDY_FL;
+ g_free( bd->cid );
+ bd->cid = g_strdup( id );
+
+ imcb_rename_buddy( ic, handle, display_name );
+
+ if( group_id && ( group = msn_group_by_id( ic, group_id ) ) )
+ imcb_add_buddy( ic, handle, group->name );
+
+ if( getenv( "BITLBEE_DEBUG" ) )
+ printf( "%s %s %s %s\n", id, type, handle, display_name );
+
+ return XT_HANDLED;
+}
+
+static const struct xt_handler_entry msn_soap_addressbook_parser[] = {
+ { "contactInfo", "Contact", msn_soap_addressbook_contact },
+ { "groupInfo", "Group", msn_soap_addressbook_group },
+ { NULL, NULL, NULL }
+};
+
+static int msn_soap_addressbook_handle_response( struct msn_soap_req_data *soap_req )
+{
+ GSList *l;
+ int wtf = 0;
+
+ for( l = soap_req->ic->bee->users; l; l = l->next )
+ {
+ struct bee_user *bu = l->data;
+ struct msn_buddy_data *bd = bu->data;
+
+ if( bu->ic == soap_req->ic && bd )
+ {
+ msn_buddy_ask( bu );
+
+ if( ( bd->flags & ( MSN_BUDDY_AL | MSN_BUDDY_BL ) ) ==
+ ( MSN_BUDDY_AL | MSN_BUDDY_BL ) )
+ {
+ bd->flags &= ~MSN_BUDDY_BL;
+ wtf++;
+ }
+ }
+ }
+
+ if( wtf )
+ imcb_log( soap_req->ic, "Warning: %d contacts were in both your "
+ "block and your allow list. Assuming they're all "
+ "allowed. Use the official WLM client once to fix "
+ "this.", wtf );
+
+ msn_auth_got_contact_list( soap_req->ic );
+
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_addressbook_free_data( struct msn_soap_req_data *soap_req )
+{
+ return 0;
+}
+
+int msn_soap_addressbook_request( struct im_connection *ic )
+{
+ return msn_soap_start( ic, NULL, msn_soap_addressbook_build_request,
+ msn_soap_addressbook_parser,
+ msn_soap_addressbook_handle_response,
+ msn_soap_addressbook_free_data );
+}
+
+/* Variant: Change our display name. */
+static int msn_soap_ab_namechange_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL );
+ soap_req->action = g_strdup( SOAP_AB_NAMECHANGE_ACTION );
+ soap_req->payload = msn_soap_abservice_build( SOAP_AB_NAMECHANGE_PAYLOAD,
+ "Timer", md->tokens[1], (char *) soap_req->data );
+
+ return 1;
+}
+
+static int msn_soap_ab_namechange_handle_response( struct msn_soap_req_data *soap_req )
+{
+ /* TODO: Ack the change? Not sure what the NAKs look like.. */
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_ab_namechange_free_data( struct msn_soap_req_data *soap_req )
+{
+ g_free( soap_req->data );
+ return 0;
+}
+
+int msn_soap_addressbook_set_display_name( struct im_connection *ic, const char *new )
+{
+ return msn_soap_start( ic, g_strdup( new ),
+ msn_soap_ab_namechange_build_request,
+ NULL,
+ msn_soap_ab_namechange_handle_response,
+ msn_soap_ab_namechange_free_data );
+}
+
+/* Add a contact. */
+static int msn_soap_ab_contact_add_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+ bee_user_t *bu = soap_req->data;
+
+ soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL );
+ soap_req->action = g_strdup( SOAP_AB_CONTACT_ADD_ACTION );
+ soap_req->payload = msn_soap_abservice_build( SOAP_AB_CONTACT_ADD_PAYLOAD,
+ "ContactSave", md->tokens[1], bu->handle, bu->fullname ? bu->fullname : bu->handle );
+
+ return 1;
+}
+
+static xt_status msn_soap_ab_contact_add_cid( struct xt_node *node, gpointer data )
+{
+ struct msn_soap_req_data *soap_req = data;
+ bee_user_t *bu = soap_req->data;
+ struct msn_buddy_data *bd = bu->data;
+
+ g_free( bd->cid );
+ bd->cid = g_strdup( node->text );
+
+ return XT_HANDLED;
+}
+
+static const struct xt_handler_entry msn_soap_ab_contact_add_parser[] = {
+ { "guid", "ABContactAddResult", msn_soap_ab_contact_add_cid },
+ { NULL, NULL, NULL }
+};
+
+static int msn_soap_ab_contact_add_handle_response( struct msn_soap_req_data *soap_req )
+{
+ /* TODO: Ack the change? Not sure what the NAKs look like.. */
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_ab_contact_add_free_data( struct msn_soap_req_data *soap_req )
+{
+ return 0;
+}
+
+int msn_soap_ab_contact_add( struct im_connection *ic, bee_user_t *bu )
+{
+ return msn_soap_start( ic, bu,
+ msn_soap_ab_contact_add_build_request,
+ msn_soap_ab_contact_add_parser,
+ msn_soap_ab_contact_add_handle_response,
+ msn_soap_ab_contact_add_free_data );
+}
+
+/* Remove a contact. */
+static int msn_soap_ab_contact_del_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+ const char *cid = soap_req->data;
+
+ soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL );
+ soap_req->action = g_strdup( SOAP_AB_CONTACT_DEL_ACTION );
+ soap_req->payload = msn_soap_abservice_build( SOAP_AB_CONTACT_DEL_PAYLOAD,
+ "Timer", md->tokens[1], cid );
+
+ return 1;
+}
+
+static int msn_soap_ab_contact_del_handle_response( struct msn_soap_req_data *soap_req )
+{
+ /* TODO: Ack the change? Not sure what the NAKs look like.. */
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_ab_contact_del_free_data( struct msn_soap_req_data *soap_req )
+{
+ g_free( soap_req->data );
+ return 0;
+}
+
+int msn_soap_ab_contact_del( struct im_connection *ic, bee_user_t *bu )
+{
+ struct msn_buddy_data *bd = bu->data;
+
+ return msn_soap_start( ic, g_strdup( bd->cid ),
+ msn_soap_ab_contact_del_build_request,
+ NULL,
+ msn_soap_ab_contact_del_handle_response,
+ msn_soap_ab_contact_del_free_data );
+}
+
+
+
+/* Storage stuff: Fetch profile. */
+static int msn_soap_profile_get_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ soap_req->url = g_strdup( SOAP_STORAGE_URL );
+ soap_req->action = g_strdup( SOAP_PROFILE_GET_ACTION );
+ soap_req->payload = g_markup_printf_escaped( SOAP_PROFILE_GET_PAYLOAD,
+ md->tokens[3], (char*) soap_req->data );
+
+ return 1;
+}
+
+static xt_status msn_soap_profile_get_result( struct xt_node *node, gpointer data )
+{
+ struct msn_soap_req_data *soap_req = data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_data *md = soap_req->ic->proto_data;
+ struct xt_node *dn;
+
+ if( ( dn = xt_find_node( node->children, "DisplayName" ) ) && dn->text )
+ {
+ set_t *set = set_find( &ic->acc->set, "display_name" );
+ g_free( set->value );
+ set->value = g_strdup( dn->text );
+
+ md->flags |= MSN_GOT_PROFILE_DN;
+ }
+
+ return XT_HANDLED;
+}
+
+static xt_status msn_soap_profile_get_rid( struct xt_node *node, gpointer data )
+{
+ struct msn_soap_req_data *soap_req = data;
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ g_free( md->profile_rid );
+ md->profile_rid = g_strdup( node->text );
+
+ return XT_HANDLED;
+}
+
+static const struct xt_handler_entry msn_soap_profile_get_parser[] = {
+ { "ExpressionProfile", "GetProfileResult", msn_soap_profile_get_result },
+ { "ResourceID", "GetProfileResult", msn_soap_profile_get_rid },
+ { NULL, NULL, NULL }
+};
+
+static int msn_soap_profile_get_handle_response( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ md->flags |= MSN_GOT_PROFILE;
+ msn_ns_finish_login( soap_req->ic );
+
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_profile_get_free_data( struct msn_soap_req_data *soap_req )
+{
+ g_free( soap_req->data );
+ return 0;
+}
+
+int msn_soap_profile_get( struct im_connection *ic, const char *cid )
+{
+ return msn_soap_start( ic, g_strdup( cid ),
+ msn_soap_profile_get_build_request,
+ msn_soap_profile_get_parser,
+ msn_soap_profile_get_handle_response,
+ msn_soap_profile_get_free_data );
+}
+
+/* Update profile (display name). */
+static int msn_soap_profile_set_dn_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_data *md = soap_req->ic->proto_data;
+
+ soap_req->url = g_strdup( SOAP_STORAGE_URL );
+ soap_req->action = g_strdup( SOAP_PROFILE_SET_DN_ACTION );
+ soap_req->payload = g_markup_printf_escaped( SOAP_PROFILE_SET_DN_PAYLOAD,
+ md->tokens[3], md->profile_rid, (char*) soap_req->data );
+
+ return 1;
+}
+
+static const struct xt_handler_entry msn_soap_profile_set_dn_parser[] = {
+ { NULL, NULL, NULL }
+};
+
+static int msn_soap_profile_set_dn_handle_response( struct msn_soap_req_data *soap_req )
+{
+ return MSN_SOAP_OK;
+}
+
+static int msn_soap_profile_set_dn_free_data( struct msn_soap_req_data *soap_req )
+{
+ g_free( soap_req->data );
+ return 0;
+}
+
+int msn_soap_profile_set_dn( struct im_connection *ic, const char *dn )
+{
+ return msn_soap_start( ic, g_strdup( dn ),
+ msn_soap_profile_set_dn_build_request,
+ msn_soap_profile_set_dn_parser,
+ msn_soap_profile_set_dn_handle_response,
+ msn_soap_profile_set_dn_free_data );
+}
diff --git a/protocols/msn/soap.h b/protocols/msn/soap.h
new file mode 100644
index 00000000..a767e00d
--- /dev/null
+++ b/protocols/msn/soap.h
@@ -0,0 +1,378 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module - All the SOAPy XML stuff.
+ Some manager at Microsoft apparently thought MSNP wasn't XMLy enough so
+ someone stepped up and changed that. This is the result. Kilobytes and
+ more kilobytes of XML vomit to transfer tiny bits of informaiton. */
+
+/*
+ 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., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+/* Thanks to http://msnpiki.msnfanatic.com/ for lots of info on this! */
+
+#ifndef __SOAP_H__
+#define __SOAP_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#endif
+#include "nogaim.h"
+
+
+int msn_soapq_flush( struct im_connection *ic, gboolean resend );
+
+
+#define SOAP_HTTP_REQUEST \
+"POST %s HTTP/1.0\r\n" \
+"Host: %s\r\n" \
+"Accept: */*\r\n" \
+"User-Agent: BitlBee " BITLBEE_VERSION "\r\n" \
+"Content-Type: text/xml; charset=utf-8\r\n" \
+"%s" \
+"Content-Length: %zd\r\n" \
+"Cache-Control: no-cache\r\n" \
+"\r\n" \
+"%s"
+
+
+#define SOAP_PASSPORT_SSO_URL "https://login.live.com/RST.srf"
+#define SOAP_PASSPORT_SSO_URL_MSN "https://msnia.login.live.com/pp900/RST.srf"
+#define MAX_PASSPORT_PWLEN 16
+
+#define SOAP_PASSPORT_SSO_PAYLOAD \
+"<Envelope xmlns=\"http://schemas.xmlsoap.org/soap/envelope/\" " \
+ "xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" " \
+ "xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" " \
+ "xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2002/12/policy\" " \
+ "xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" " \
+ "xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/03/addressing\" " \
+ "xmlns:wssc=\"http://schemas.xmlsoap.org/ws/2004/04/sc\" " \
+ "xmlns:wst=\"http://schemas.xmlsoap.org/ws/2004/04/trust\">" \
+ "<Header>" \
+ "<ps:AuthInfo " \
+ "xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" " \
+ "Id=\"PPAuthInfo\">" \
+ "<ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>" \
+ "<ps:BinaryVersion>4</ps:BinaryVersion>" \
+ "<ps:UIVersion>1</ps:UIVersion>" \
+ "<ps:Cookies></ps:Cookies>" \
+ "<ps:RequestParams>AQAAAAIAAABsYwQAAAAxMDMz</ps:RequestParams>" \
+ "</ps:AuthInfo>" \
+ "<wsse:Security>" \
+ "<wsse:UsernameToken Id=\"user\">" \
+ "<wsse:Username>%s</wsse:Username>" \
+ "<wsse:Password>%s</wsse:Password>" \
+ "</wsse:UsernameToken>" \
+ "</wsse:Security>" \
+ "</Header>" \
+ "<Body>" \
+ "<ps:RequestMultipleSecurityTokens " \
+ "xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" " \
+ "Id=\"RSTS\">" \
+ "<wst:RequestSecurityToken Id=\"RST0\">" \
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \
+ "<wsp:AppliesTo>" \
+ "<wsa:EndpointReference>" \
+ "<wsa:Address>http://Passport.NET/tb</wsa:Address>" \
+ "</wsa:EndpointReference>" \
+ "</wsp:AppliesTo>" \
+ "</wst:RequestSecurityToken>" \
+ "<wst:RequestSecurityToken Id=\"RST1\">" \
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \
+ "<wsp:AppliesTo>" \
+ "<wsa:EndpointReference>" \
+ "<wsa:Address>messengerclear.live.com</wsa:Address>" \
+ "</wsa:EndpointReference>" \
+ "</wsp:AppliesTo>" \
+ "<wsse:PolicyReference URI=\"%s\"></wsse:PolicyReference>" \
+ "</wst:RequestSecurityToken>" \
+ "<wst:RequestSecurityToken Id=\"RST2\">" \
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \
+ "<wsp:AppliesTo>" \
+ "<wsa:EndpointReference>" \
+ "<wsa:Address>contacts.msn.com</wsa:Address>" \
+ "</wsa:EndpointReference>" \
+ "</wsp:AppliesTo>" \
+ "<wsse:PolicyReference xmlns=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" URI=\"MBI\"></wsse:PolicyReference>" \
+ "</wst:RequestSecurityToken>" \
+ "<wst:RequestSecurityToken Id=\"RST3\">" \
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \
+ "<wsp:AppliesTo>" \
+ "<wsa:EndpointReference>" \
+ "<wsa:Address>messengersecure.live.com</wsa:Address>" \
+ "</wsa:EndpointReference>" \
+ "</wsp:AppliesTo>" \
+ "<wsse:PolicyReference xmlns=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" URI=\"MBI_SSL\"></wsse:PolicyReference>" \
+ "</wst:RequestSecurityToken>" \
+ "<wst:RequestSecurityToken Id=\"RST4\">" \
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>" \
+ "<wsp:AppliesTo>" \
+ "<wsa:EndpointReference>" \
+ "<wsa:Address>storage.msn.com</wsa:Address>" \
+ "</wsa:EndpointReference>" \
+ "</wsp:AppliesTo>" \
+ "<wsse:PolicyReference xmlns=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" URI=\"MBI_SSL\"></wsse:PolicyReference>" \
+ "</wst:RequestSecurityToken>" \
+ "</ps:RequestMultipleSecurityTokens>" \
+ "</Body>" \
+"</Envelope>"
+
+int msn_soap_passport_sso_request( struct im_connection *ic, const char *nonce );
+
+
+#define SOAP_OIM_SEND_URL "https://ows.messenger.msn.com/OimWS/oim.asmx"
+#define SOAP_OIM_SEND_ACTION "http://messenger.live.com/ws/2006/09/oim/Store2"
+
+#define SOAP_OIM_SEND_PAYLOAD \
+"<?xml version=\"1.0\" encoding=\"utf-8\"?>" \
+"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+"<soap:Header>" \
+ "<From memberName=\"%s\" friendlyName=\"=?utf-8?B?%s?=\" xml:lang=\"nl-nl\" proxy=\"MSNMSGR\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\" msnpVer=\"%s\" buildVer=\"%s\"/>" \
+ "<To memberName=\"%s\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\"/>" \
+ "<Ticket passport=\"%s\" appid=\"%s\" lockkey=\"%s\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\"/>" \
+ "<Sequence xmlns=\"http://schemas.xmlsoap.org/ws/2003/03/rm\">" \
+ "<Identifier xmlns=\"http://schemas.xmlsoap.org/ws/2002/07/utility\">http://messenger.msn.com</Identifier>" \
+ "<MessageNumber>%d</MessageNumber>" \
+ "</Sequence>" \
+"</soap:Header>" \
+"<soap:Body>" \
+ "<MessageType xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\">text</MessageType>" \
+ "<Content xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\">" \
+ "MIME-Version: 1.0\r\n" \
+ "Content-Type: text/plain; charset=UTF-8\r\n" \
+ "Content-Transfer-Encoding: base64\r\n" \
+ "X-OIM-Message-Type: OfflineMessage\r\n" \
+ "X-OIM-Run-Id: {F9A6C9DD-0D94-4E85-9CC6-F9D118CC1CAF}\r\n" \
+ "X-OIM-Sequence-Num: %d\r\n" \
+ "\r\n" \
+ "%s" \
+ "</Content>" \
+"</soap:Body>" \
+"</soap:Envelope>"
+
+int msn_soap_oim_send( struct im_connection *ic, const char *to, const char *msg );
+int msn_soap_oim_send_queue( struct im_connection *ic, GSList **msgq );
+
+
+#define SOAP_ABSERVICE_PAYLOAD \
+"<?xml version=\"1.0\" encoding=\"utf-8\"?>" \
+"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<soap:Header xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<ApplicationId xmlns=\"http://www.msn.com/webservices/AddressBook\">CFE80F9D-180F-4399-82AB-413F33A1FA11</ApplicationId>" \
+ "<IsMigration xmlns=\"http://www.msn.com/webservices/AddressBook\">false</IsMigration>" \
+ "<PartnerScenario xmlns=\"http://www.msn.com/webservices/AddressBook\">%s</PartnerScenario>" \
+ "</ABApplicationHeader>" \
+ "<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<ManagedGroupRequest xmlns=\"http://www.msn.com/webservices/AddressBook\">false</ManagedGroupRequest>" \
+ "<TicketToken>%s</TicketToken>" \
+ "</ABAuthHeader>" \
+ "</soap:Header>" \
+ "<soap:Body xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "%%s" \
+ "</soap:Body>" \
+"</soap:Envelope>"
+
+#define SOAP_MEMLIST_URL "http://contacts.msn.com/abservice/SharingService.asmx"
+#define SOAP_MEMLIST_ACTION "http://www.msn.com/webservices/AddressBook/FindMembership"
+
+#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>"
+
+#define SOAP_MEMLIST_ADD_ACTION "http://www.msn.com/webservices/AddressBook/AddMember"
+#define SOAP_MEMLIST_DEL_ACTION "http://www.msn.com/webservices/AddressBook/DeleteMember"
+
+#define SOAP_MEMLIST_EDIT_PAYLOAD \
+ "<%sMember xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<serviceHandle>" \
+ "<Id>0</Id>" \
+ "<Type>Messenger</Type>" \
+ "<ForeignId></ForeignId>" \
+ "</serviceHandle>" \
+ "<memberships>" \
+ "<Membership>" \
+ "<MemberRole>%s</MemberRole>" \
+ "<Members>" \
+ "<Member xsi:type=\"PassportMember\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" \
+ "<Type>Passport</Type>" \
+ "<State>Accepted</State>" \
+ "<PassportName>%s</PassportName>" \
+ "</Member>" \
+ "</Members>" \
+ "</Membership>" \
+ "</memberships>" \
+ "</%sMember>"
+
+int msn_soap_memlist_request( struct im_connection *ic );
+int msn_soap_memlist_edit( struct im_connection *ic, const char *handle, gboolean add, int list );
+
+
+#define SOAP_ADDRESSBOOK_URL "http://contacts.msn.com/abservice/abservice.asmx"
+#define SOAP_ADDRESSBOOK_ACTION "http://www.msn.com/webservices/AddressBook/ABFindAll"
+
+#define SOAP_ADDRESSBOOK_PAYLOAD \
+ "<ABFindAll xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<abId>00000000-0000-0000-0000-000000000000</abId>" \
+ "<abView>Full</abView>" \
+ "<deltasOnly>false</deltasOnly>" \
+ "<lastChange>0001-01-01T00:00:00.0000000-08:00</lastChange>" \
+ "</ABFindAll>"
+
+#define SOAP_AB_NAMECHANGE_ACTION "http://www.msn.com/webservices/AddressBook/ABContactUpdate"
+
+#define SOAP_AB_NAMECHANGE_PAYLOAD \
+ "<ABContactUpdate xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<abId>00000000-0000-0000-0000-000000000000</abId>" \
+ "<contacts>" \
+ "<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<contactInfo>" \
+ "<contactType>Me</contactType>" \
+ "<displayName>%s</displayName>" \
+ "</contactInfo>" \
+ "<propertiesChanged>DisplayName</propertiesChanged>" \
+ "</Contact>" \
+ "</contacts>" \
+ "</ABContactUpdate>"
+
+#define SOAP_AB_CONTACT_ADD_ACTION "http://www.msn.com/webservices/AddressBook/ABContactAdd"
+
+#define SOAP_AB_CONTACT_ADD_PAYLOAD \
+ "<ABContactAdd xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<abId>00000000-0000-0000-0000-000000000000</abId>" \
+ "<contacts>" \
+ "<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<contactInfo>" \
+ "<contactType>LivePending</contactType>" \
+ "<passportName>%s</passportName>" \
+ "<isMessengerUser>true</isMessengerUser>" \
+ "<MessengerMemberInfo>" \
+ "<DisplayName>%s</DisplayName>" \
+ "</MessengerMemberInfo>" \
+ "</contactInfo>" \
+ "</Contact>" \
+ "</contacts>" \
+ "<options>" \
+ "<EnableAllowListManagement>true</EnableAllowListManagement>" \
+ "</options>" \
+ "</ABContactAdd>"
+
+#define SOAP_AB_CONTACT_DEL_ACTION "http://www.msn.com/webservices/AddressBook/ABContactDelete"
+
+#define SOAP_AB_CONTACT_DEL_PAYLOAD \
+ "<ABContactDelete xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<abId>00000000-0000-0000-0000-000000000000</abId>" \
+ "<contacts>" \
+ "<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">" \
+ "<contactId>%s</contactId>" \
+ "</Contact>" \
+ "</contacts>" \
+ "</ABContactDelete>"
+
+int msn_soap_addressbook_request( struct im_connection *ic );
+int msn_soap_addressbook_set_display_name( struct im_connection *ic, const char *new );
+int msn_soap_ab_contact_add( struct im_connection *ic, bee_user_t *bu );
+int msn_soap_ab_contact_del( struct im_connection *ic, bee_user_t *bu );
+
+
+#define SOAP_STORAGE_URL "https://storage.msn.com/storageservice/SchematizedStore.asmx"
+#define SOAP_PROFILE_GET_ACTION "http://www.msn.com/webservices/storage/w10/GetProfile"
+#define SOAP_PROFILE_SET_DN_ACTION "http://www.msn.com/webservices/storage/w10/UpdateProfile"
+
+#define SOAP_PROFILE_GET_PAYLOAD \
+"<?xml version=\"1.0\" encoding=\"utf-8\"?>" \
+"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<soap:Header xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<StorageApplicationHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">" \
+ "<ApplicationID>Messenger Client 9.0</ApplicationID>" \
+ "<Scenario>Initial</Scenario>" \
+ "</StorageApplicationHeader>" \
+ "<StorageUserHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">" \
+ "<Puid>0</Puid>" \
+ "<TicketToken>%s</TicketToken>" \
+ "</StorageUserHeader>" \
+ "</soap:Header>" \
+ "<soap:Body xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<GetProfile xmlns=\"http://www.msn.com/webservices/storage/w10\">" \
+ "<profileHandle>" \
+ "<Alias>" \
+ "<Name>%s</Name>" \
+ "<NameSpace>MyCidStuff</NameSpace>" \
+ "</Alias>" \
+ "<RelationshipName>MyProfile</RelationshipName>" \
+ "</profileHandle>" \
+ "<profileAttributes>" \
+ "<ResourceID>true</ResourceID>" \
+ "<DateModified>true</DateModified>" \
+ "<ExpressionProfileAttributes>" \
+ "<ResourceID>true</ResourceID>" \
+ "<DateModified>true</DateModified>" \
+ "<DisplayName>true</DisplayName>" \
+ "<DisplayNameLastModified>true</DisplayNameLastModified>" \
+ "<PersonalStatus>true</PersonalStatus>" \
+ "<PersonalStatusLastModified>true</PersonalStatusLastModified>" \
+ "<StaticUserTilePublicURL>true</StaticUserTilePublicURL>" \
+ "<Photo>true</Photo>" \
+ "<Flags>true</Flags>" \
+ "</ExpressionProfileAttributes>" \
+ "</profileAttributes>" \
+ "</GetProfile>" \
+ "</soap:Body>" \
+"</soap:Envelope>"
+
+#define SOAP_PROFILE_SET_DN_PAYLOAD \
+"<?xml version=\"1.0\" encoding=\"utf-8\"?>" \
+"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<soap:Header xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<StorageApplicationHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">" \
+ "<ApplicationID>Messenger Client 9.0</ApplicationID>" \
+ "<Scenario>Initial</Scenario>" \
+ "</StorageApplicationHeader>" \
+ "<StorageUserHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">" \
+ "<Puid>0</Puid>" \
+ "<TicketToken>%s</TicketToken>" \
+ "</StorageUserHeader>" \
+ "</soap:Header>" \
+ "<soap:Body xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+ "<UpdateProfile xmlns=\"http://www.msn.com/webservices/storage/w10\">" \
+ "<profile>" \
+ "<ResourceID>%s</ResourceID>" \
+ "<ExpressionProfile>" \
+ "<FreeText>Update</FreeText>" \
+ "<DisplayName>%s</DisplayName>" \
+ "<Flags>0</Flags>" \
+ "</ExpressionProfile>" \
+ "</profile>" \
+ "</UpdateProfile>" \
+ "</soap:Body>" \
+"</soap:Envelope>"
+
+int msn_soap_profile_get( struct im_connection *ic, const char *cid );
+int msn_soap_profile_set_dn( struct im_connection *ic, const char *dn );
+
+#endif /* __SOAP_H__ */
diff --git a/protocols/msn/tables.c b/protocols/msn/tables.c
new file mode 100644
index 00000000..273d291e
--- /dev/null
+++ b/protocols/msn/tables.c
@@ -0,0 +1,157 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* MSN module - Some tables with useful data */
+
+/*
+ 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., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "nogaim.h"
+#include "msn.h"
+
+const struct msn_away_state msn_away_state_list[] =
+{
+ { "NLN", "" },
+ { "AWY", "Away" },
+ { "BSY", "Busy" },
+ { "IDL", "Idle" },
+ { "BRB", "Be Right Back" },
+ { "PHN", "On the Phone" },
+ { "LUN", "Out to Lunch" },
+ { "HDN", "Hidden" },
+ { "", "" }
+};
+
+const struct msn_away_state *msn_away_state_by_code( char *code )
+{
+ int i;
+
+ for( i = 0; *msn_away_state_list[i].code; i ++ )
+ if( g_strcasecmp( msn_away_state_list[i].code, code ) == 0 )
+ return( msn_away_state_list + i );
+
+ return NULL;
+}
+
+const struct msn_away_state *msn_away_state_by_name( char *name )
+{
+ int i;
+
+ for( i = 0; *msn_away_state_list[i].code; i ++ )
+ if( g_strcasecmp( msn_away_state_list[i].name, name ) == 0 )
+ return( msn_away_state_list + i );
+
+ return NULL;
+}
+
+const struct msn_status_code msn_status_code_list[] =
+{
+ { 200, "Invalid syntax", 0 },
+ { 201, "Invalid parameter", 0 },
+ { 205, "Invalid (non-existent) handle", 0 },
+ { 206, "Domain name missing", 0 },
+ { 207, "Already logged in", 0 },
+ { 208, "Invalid handle", STATUS_SB_IM_SPARE },
+ { 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 },
+ { 218, "Already in that mode", 0 },
+ { 219, "Handle is already in opposite list", 0 },
+ { 223, "Too many groups", 0 },
+ { 224, "Invalid group or already in list", 0 },
+ { 225, "Handle is not in that group", 0 },
+ { 229, "Group name too long", 0 },
+ { 230, "Cannot remove that group", 0 },
+ { 231, "Invalid group", 0 },
+ { 240, "ADL/RML command with corrupted payload", STATUS_FATAL },
+ { 241, "ADL/RML command with invalid modification", 0 },
+ { 280, "Switchboard failed", STATUS_SB_FATAL },
+ { 281, "Transfer to switchboard failed", 0 },
+
+ { 300, "Required field missing", 0 },
+ { 302, "Not logged in", 0 },
+
+ { 500, "Internal server error/Account banned", STATUS_FATAL },
+ { 501, "Database server error", STATUS_FATAL },
+ { 502, "Command disabled", 0 },
+ { 510, "File operation failed", STATUS_FATAL },
+ { 520, "Memory allocation failed", STATUS_FATAL },
+ { 540, "Challenge response invalid", STATUS_FATAL },
+
+ { 600, "Server is busy", STATUS_FATAL },
+ { 601, "Server is unavailable", STATUS_FATAL },
+ { 602, "Peer nameserver is down", STATUS_FATAL },
+ { 603, "Database connection failed", STATUS_FATAL },
+ { 604, "Server is going down", STATUS_FATAL },
+ { 605, "Server is unavailable", STATUS_FATAL },
+
+ { 700, "Could not create connection", STATUS_FATAL },
+ { 710, "Invalid CVR parameters", STATUS_FATAL },
+ { 711, "Write is blocking", STATUS_FATAL },
+ { 712, "Session is overloaded", STATUS_FATAL },
+ { 713, "Calling too rapidly", STATUS_SB_IM_SPARE },
+ { 714, "Too many sessions", STATUS_FATAL },
+ { 715, "Not expected/Invalid argument/action", 0 },
+ { 717, "Bad friend file", STATUS_FATAL },
+ { 731, "Not expected/Invalid argument", 0 },
+
+ { 800, "Changing too rapidly", 0 },
+
+ { 910, "Server is busy", STATUS_FATAL },
+ { 911, "Authentication failed", STATUS_SB_FATAL | STATUS_FATAL },
+ { 912, "Server is busy", STATUS_FATAL },
+ { 913, "Not allowed when hiding", 0 },
+ { 914, "Server is unavailable", STATUS_FATAL },
+ { 915, "Server is unavailable", STATUS_FATAL },
+ { 916, "Server is unavailable", STATUS_FATAL },
+ { 917, "Authentication failed", STATUS_FATAL },
+ { 918, "Server is busy", STATUS_FATAL },
+ { 919, "Server is busy", STATUS_FATAL },
+ { 920, "Not accepting new principals", 0 }, /* When a sb is full? */
+ { 922, "Server is busy", STATUS_FATAL },
+ { 923, "Kids Passport without parental consent", STATUS_FATAL },
+ { 924, "Passport account not yet verified", STATUS_FATAL },
+ { 928, "Bad ticket", STATUS_FATAL },
+ { -1, NULL, 0 }
+};
+
+const struct msn_status_code *msn_status_by_number( int number )
+{
+ static struct msn_status_code *unknown = NULL;
+ int i;
+
+ for( i = 0; msn_status_code_list[i].number >= 0; i ++ )
+ if( msn_status_code_list[i].number == number )
+ return( msn_status_code_list + i );
+
+ if( unknown == NULL )
+ {
+ unknown = g_new0( struct msn_status_code, 1 );
+ unknown->text = g_new0( char, 128 );
+ }
+
+ unknown->number = number;
+ unknown->flags = 0;
+ g_snprintf( unknown->text, 128, "Unknown error (%d)", number );
+
+ return( unknown );
+}