aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/msn
diff options
context:
space:
mode:
authorWilmer van der Gaast <wilmer@gaast.net>2005-11-06 19:23:18 +0100
committerWilmer van der Gaast <wilmer@gaast.net>2005-11-06 19:23:18 +0100
commitb7d3cc34f68dab7b8f7d0777711317b334fc2219 (patch)
tree6aa4d6332c96654fda79fe18993ab0e35d36a52b /protocols/msn
Initial repository (0.99 release tree)0.99
Diffstat (limited to 'protocols/msn')
-rw-r--r--protocols/msn/Makefile39
-rw-r--r--protocols/msn/msn.c402
-rw-r--r--protocols/msn/msn.h170
-rw-r--r--protocols/msn/msn_util.c359
-rw-r--r--protocols/msn/ns.c661
-rw-r--r--protocols/msn/passport.c313
-rw-r--r--protocols/msn/passport.h47
-rw-r--r--protocols/msn/sb.c659
-rw-r--r--protocols/msn/tables.c166
9 files changed, 2816 insertions, 0 deletions
diff --git a/protocols/msn/Makefile b/protocols/msn/Makefile
new file mode 100644
index 00000000..e6620323
--- /dev/null
+++ b/protocols/msn/Makefile
@@ -0,0 +1,39 @@
+###########################
+## Makefile for BitlBee ##
+## ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+
+# [SH] Program variables
+objects = msn.o msn_util.o ns.o passport.o sb.o tables.o
+
+CFLAGS += -Wall
+LFLAGS += -r
+
+# [SH] Phony targets
+all: msnn.o
+
+.PHONY: all clean distclean
+
+clean:
+ rm -f *.o core
+
+distclean: clean
+
+### MAIN PROGRAM
+
+$(objects): ../../Makefile.settings Makefile
+
+$(objects): %.o: %.c
+ @echo '*' Compiling $<
+ @$(CC) -c $(CFLAGS) $< -o $@
+
+msnn.o: $(objects)
+ @echo '*' Linking msnn.o
+ @$(LD) $(LFLAGS) $(objects) -o msnn.o
+
+
diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c
new file mode 100644
index 00000000..33e12af5
--- /dev/null
+++ b/protocols/msn/msn.c
@@ -0,0 +1,402 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2004 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 "msn.h"
+
+static struct prpl *my_protocol = NULL;
+
+static void msn_login( struct aim_user *acct )
+{
+ struct gaim_connection *gc = new_gaim_conn( acct );
+ struct msn_data *md = g_new0( struct msn_data, 1 );
+
+ set_login_progress( gc, 1, "Connecting" );
+
+ gc->proto_data = md;
+ md->fd = -1;
+
+ if( strchr( acct->username, '@' ) == NULL )
+ {
+ hide_login_progress( gc, "Invalid account name" );
+ signoff( gc );
+ return;
+ }
+
+ md->fd = proxy_connect( "messenger.hotmail.com", 1863, msn_ns_connected, gc );
+ if( md->fd < 0 )
+ {
+ hide_login_progress( gc, "Could not connect to server" );
+ signoff( gc );
+ }
+ else
+ {
+ md->gc = gc;
+ md->away_state = msn_away_state_list;
+
+ msn_connections = g_slist_append( msn_connections, gc );
+ }
+}
+
+static void msn_close( struct gaim_connection *gc )
+{
+ struct msn_data *md = gc->proto_data;
+ GSList *l;
+
+ if( md->fd >= 0 )
+ closesocket( md->fd );
+
+ if( md->handler )
+ {
+ if( md->handler->rxq ) g_free( md->handler->rxq );
+ if( md->handler->cmd_text ) g_free( md->handler->cmd_text );
+ g_free( md->handler );
+ }
+
+ while( md->switchboards )
+ msn_sb_destroy( md->switchboards->data );
+
+ if( md->msgq )
+ {
+ struct msn_message *m;
+
+ for( l = md->msgq; l; l = l->next )
+ {
+ m = l->data;
+ g_free( m->who );
+ g_free( m->text );
+ g_free( m );
+ }
+ g_slist_free( md->msgq );
+
+ serv_got_crap( gc, "Warning: Closing down MSN connection with unsent message(s), you'll have to resend them." );
+ }
+
+ for( l = gc->permit; l; l = l->next )
+ g_free( l->data );
+ g_slist_free( gc->permit );
+
+ for( l = gc->deny; l; l = l->next )
+ g_free( l->data );
+ g_slist_free( gc->deny );
+
+ g_free( md );
+
+ msn_connections = g_slist_remove( msn_connections, gc );
+}
+
+static int msn_send_im( struct gaim_connection *gc, char *who, char *message, int len, int away )
+{
+ struct msn_switchboard *sb;
+ struct msn_data *md = gc->proto_data;
+
+ if( ( sb = msn_sb_by_handle( gc, who ) ) )
+ {
+ return( msn_sb_sendmessage( sb, message ) );
+ }
+ else
+ {
+ struct msn_message *m;
+ char buf[1024];
+
+ /* 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 );
+
+ /* FIXME: *CHECK* the reliability of using spare sb's! */
+ if( ( sb = msn_sb_spare( gc ) ) )
+ {
+ debug( "Trying to use a spare switchboard to message %s", who );
+
+ sb->who = g_strdup( who );
+ g_snprintf( buf, sizeof( buf ), "CAL %d %s\r\n", ++sb->trId, who );
+ if( msn_sb_write( sb, buf, strlen( buf ) ) )
+ {
+ /* 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", who );
+
+ /* If we reach this line, there was no spare switchboard, so let's make one. */
+ g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId );
+ if( !msn_write( gc, buf, strlen( buf ) ) )
+ {
+ 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 );
+ }
+
+ return( 0 );
+}
+
+static GList *msn_away_states( struct gaim_connection *gc )
+{
+ GList *l = NULL;
+ int i;
+
+ for( i = 0; msn_away_state_list[i].number > -1; i ++ )
+ l = g_list_append( l, msn_away_state_list[i].name );
+
+ return( l );
+}
+
+static char *msn_get_status_string( struct gaim_connection *gc, int number )
+{
+ struct msn_away_state *st = msn_away_state_by_number( number );
+
+ if( st )
+ return( st->name );
+ else
+ return( "" );
+}
+
+static void msn_set_away( struct gaim_connection *gc, char *state, char *message )
+{
+ char buf[1024];
+ struct msn_data *md = gc->proto_data;
+ struct msn_away_state *st;
+
+ if( strcmp( state, GAIM_AWAY_CUSTOM ) == 0 )
+ st = msn_away_state_by_name( "Away" );
+ else
+ st = msn_away_state_by_name( state );
+
+ if( !st ) st = msn_away_state_list;
+ md->away_state = st;
+
+ g_snprintf( buf, sizeof( buf ), "CHG %d %s\r\n", ++md->trId, st->code );
+ msn_write( gc, buf, strlen( buf ) );
+}
+
+static void msn_set_info( struct gaim_connection *gc, char *info )
+{
+ int i;
+ char buf[1024], *fn, *s;
+ struct msn_data *md = gc->proto_data;
+
+ if( strlen( info ) > 129 )
+ {
+ do_error_dialog( gc, "Maximum name length exceeded", "MSN" );
+ return;
+ }
+
+ /* Of course we could use http_encode() here, but when we encode
+ every character, the server is less likely to complain about the
+ chosen name. However, the MSN server doesn't seem to like escaped
+ non-ASCII chars, so we keep those unescaped. */
+ s = fn = g_new0( char, strlen( info ) * 3 + 1 );
+ for( i = 0; info[i]; i ++ )
+ if( info[i] & 128 )
+ {
+ *s = info[i];
+ s ++;
+ }
+ else
+ {
+ g_snprintf( s, 4, "%%%02X", info[i] );
+ s += 3;
+ }
+
+ g_snprintf( buf, sizeof( buf ), "REA %d %s %s\r\n", ++md->trId, gc->username, fn );
+ msn_write( gc, buf, strlen( buf ) );
+ g_free( fn );
+}
+
+static void msn_get_info(struct gaim_connection *gc, char *who)
+{
+ /* Just make an URL and let the user fetch the info */
+ serv_got_crap( gc, "%s\n%s: %s%s", _("User Info"), _("For now, fetch yourself"), PROFILE_URL, who );
+}
+
+static void msn_add_buddy( struct gaim_connection *gc, char *who )
+{
+ msn_buddy_list_add( gc, "FL", who, who );
+}
+
+static void msn_remove_buddy( struct gaim_connection *gc, char *who, char *group )
+{
+ msn_buddy_list_remove( gc, "FL", who );
+}
+
+static int msn_chat_send( struct gaim_connection *gc, int id, char *message )
+{
+ struct msn_switchboard *sb = msn_sb_by_id( gc, id );
+
+ if( sb )
+ return( msn_sb_sendmessage( sb, message ) );
+ else
+ return( 0 );
+}
+
+static void msn_chat_invite( struct gaim_connection *gc, int id, char *msg, char *who )
+{
+ struct msn_switchboard *sb = msn_sb_by_id( gc, id );
+ 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 gaim_connection *gc, int id )
+{
+ struct msn_switchboard *sb = msn_sb_by_id( gc, id );
+
+ if( sb )
+ msn_sb_write( sb, "OUT\r\n", 5 );
+}
+
+static int msn_chat_open( struct gaim_connection *gc, char *who )
+{
+ struct msn_switchboard *sb;
+ struct msn_data *md = gc->proto_data;
+ char buf[1024];
+
+ if( ( sb = msn_sb_by_handle( gc, who ) ) )
+ {
+ debug( "Converting existing switchboard to %s to a groupchat", who );
+ msn_sb_to_chat( sb );
+ return( 1 );
+ }
+ else
+ {
+ struct msn_message *m;
+
+ if( ( sb = msn_sb_spare( gc ) ) )
+ {
+ debug( "Trying to reuse an existing switchboard as a groupchat with %s", who );
+ g_snprintf( buf, sizeof( buf ), "CAL %d %s\r\n", ++sb->trId, who );
+ if( msn_sb_write( sb, buf, strlen( buf ) ) )
+ {
+ msn_sb_to_chat( sb );
+ return( 1 );
+ }
+ }
+
+ /* If the stuff above failed for some reason: */
+ debug( "Creating a new switchboard to groupchat with %s", who );
+
+ /* Request a new switchboard. */
+ g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId );
+ if( !msn_write( gc, buf, strlen( buf ) ) )
+ return( 0 );
+
+ /* 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 );
+
+ /* Queue the magic message and cross your fingers. */
+ md->msgq = g_slist_append( md->msgq, m );
+
+ return( 1 );
+ }
+
+ return( 0 );
+}
+
+static void msn_keepalive( struct gaim_connection *gc )
+{
+ msn_write( gc, "PNG\r\n", strlen( "PNG\r\n" ) );
+}
+
+static void msn_add_permit( struct gaim_connection *gc, char *who )
+{
+ msn_buddy_list_add( gc, "AL", who, who );
+}
+
+static void msn_rem_permit( struct gaim_connection *gc, char *who )
+{
+ msn_buddy_list_remove( gc, "AL", who );
+}
+
+static void msn_add_deny( struct gaim_connection *gc, char *who )
+{
+ struct msn_switchboard *sb;
+
+ msn_buddy_list_add( gc, "BL", who, who );
+
+ /* If there's still a conversation with this person, close it. */
+ if( ( sb = msn_sb_by_handle( gc, who ) ) )
+ {
+ msn_sb_destroy( sb );
+ }
+}
+
+static void msn_rem_deny( struct gaim_connection *gc, char *who )
+{
+ msn_buddy_list_remove( gc, "BL", who );
+}
+
+static int msn_send_typing( struct gaim_connection *gc, char *who, int typing )
+{
+ if( typing )
+ return( msn_send_im( gc, who, TYPING_NOTIFICATION_MESSAGE, strlen( TYPING_NOTIFICATION_MESSAGE ), 0 ) );
+ else
+ return( 1 );
+}
+
+void msn_init(struct prpl *ret)
+{
+ ret->protocol = PROTO_MSN;
+ ret->login = msn_login;
+ ret->close = msn_close;
+ ret->send_im = msn_send_im;
+ ret->away_states = msn_away_states;
+ ret->get_status_string = msn_get_status_string;
+ ret->set_away = msn_set_away;
+ ret->set_info = msn_set_info;
+ ret->get_info = msn_get_info;
+ ret->add_buddy = msn_add_buddy;
+ ret->remove_buddy = msn_remove_buddy;
+ ret->chat_send = msn_chat_send;
+ ret->chat_invite = msn_chat_invite;
+ ret->chat_leave = msn_chat_leave;
+ ret->chat_open = msn_chat_open;
+ 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;
+
+ my_protocol = ret;
+}
diff --git a/protocols/msn/msn.h b/protocols/msn/msn.h
new file mode 100644
index 00000000..61231d8a
--- /dev/null
+++ b/protocols/msn/msn.h
@@ -0,0 +1,170 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2004 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
+*/
+
+/* Some hackish magicstrings to make special-purpose messages/switchboards.
+ */
+#define TYPING_NOTIFICATION_MESSAGE "\r\r\rBEWARE, ME R TYPINK MESSAGE!!!!\r\r\r"
+#define GROUPCHAT_SWITCHBOARD_MESSAGE "\r\r\rME WANT TALK TO MANY PEOPLE\r\r\r"
+
+#ifdef _WIN32
+#define debug
+#else
+#define debug( text... ) irc_usermsg( IRC, text );
+#undef debug
+#define debug( text... )
+#endif
+
+#define QRY_NAME "msmsgs@msnmsgr.com"
+#define QRY_CODE "Q1P7W2E4J9R8U3S5"
+
+#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 PROFILE_URL "http://members.msn.com/"
+
+struct msn_data
+{
+ struct gaim_connection *gc;
+
+ int fd;
+ struct msn_handler_data *handler;
+
+ int trId;
+
+ GSList *msgq;
+ GSList *switchboards;
+ int buddycount;
+ struct msn_away_state *away_state;
+};
+
+struct msn_switchboard
+{
+ struct gaim_connection *gc;
+
+ int fd;
+ gint inp;
+ struct msn_handler_data *handler;
+
+ int trId;
+ int ready;
+
+ int session;
+ char *key;
+
+ GSList *msgq;
+ char *who;
+ struct conversation *chat;
+};
+
+struct msn_away_state
+{
+ int number;
+ 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_handler_data
+{
+ int fd;
+ int rxlen;
+ char *rxq;
+
+ int msglen;
+ char *cmd_text;
+
+ gpointer data;
+
+ int (*exec_command) ( gpointer data, char **cmd, int count );
+ int (*exec_message) ( gpointer data, char *msg, int msglen, char **cmd, int count );
+};
+
+/* Bitfield values for msn_status_code.flags */
+#define STATUS_FATAL 1
+#define STATUS_SB_FATAL 2
+
+int msn_chat_id;
+extern struct msn_away_state msn_away_state_list[];
+extern 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. */
+GSList *msn_connections;
+GSList *msn_switchboards;
+
+/* ns.c */
+void msn_ns_connected( gpointer data, gint source, GaimInputCondition cond );
+
+/* msn_util.c */
+int msn_write( struct gaim_connection *gc, char *s, int len );
+int msn_logged_in( struct gaim_connection *gc );
+int msn_buddy_list_add( struct gaim_connection *gc, char *list, char *who, char *realname );
+int msn_buddy_list_remove( struct gaim_connection *gc, char *list, char *who );
+void msn_buddy_ask( struct gaim_connection *gc, char *handle, char *realname );
+char *msn_findheader( char *text, char *header, int len );
+char **msn_linesplit( char *line );
+int msn_handler( struct msn_handler_data *h );
+
+/* tables.c */
+struct msn_away_state *msn_away_state_by_number( int number );
+struct msn_away_state *msn_away_state_by_code( char *code );
+struct msn_away_state *msn_away_state_by_name( char *name );
+struct msn_status_code *msn_status_by_number( int number );
+
+/* sb.c */
+int msn_sb_write( struct msn_switchboard *sb, char *s, int len );
+struct msn_switchboard *msn_sb_create( struct gaim_connection *gc, char *host, int port, char *key, int session );
+struct msn_switchboard *msn_sb_by_handle( struct gaim_connection *gc, char *handle );
+struct msn_switchboard *msn_sb_by_id( struct gaim_connection *gc, int id );
+struct msn_switchboard *msn_sb_spare( struct gaim_connection *gc );
+int msn_sb_sendmessage( struct msn_switchboard *sb, char *text );
+void msn_sb_to_chat( struct msn_switchboard *sb );
+void msn_sb_destroy( struct msn_switchboard *sb );
+void msn_sb_connected( gpointer data, gint source, GaimInputCondition cond );
diff --git a/protocols/msn/msn_util.c b/protocols/msn/msn_util.c
new file mode 100644
index 00000000..2f22acfc
--- /dev/null
+++ b/protocols/msn/msn_util.c
@@ -0,0 +1,359 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2004 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 <ctype.h>
+
+int msn_write( struct gaim_connection *gc, char *s, int len )
+{
+ struct msn_data *md = gc->proto_data;
+ int st;
+
+ st = write( md->fd, s, len );
+ if( st != len )
+ {
+ hide_login_progress_error( gc, "Short write() to main server" );
+ signoff( gc );
+ return( 0 );
+ }
+
+ return( 1 );
+}
+
+int msn_logged_in( struct gaim_connection *gc )
+{
+ struct msn_data *md = gc->proto_data;
+ char buf[1024];
+
+ account_online( gc );
+
+ /* account_online() sets an away state if there is any, so only
+ execute this code if we're not away. */
+ if( md->away_state == msn_away_state_list )
+ {
+ g_snprintf( buf, sizeof( buf ), "CHG %d %s %d\r\n", ++md->trId, md->away_state->code, 0 );
+ return( msn_write( gc, buf, strlen( buf ) ) );
+ }
+
+ return( 0 );
+}
+
+int msn_buddy_list_add( struct gaim_connection *gc, char *list, char *who, char *realname_ )
+{
+ struct msn_data *md = gc->proto_data;
+ GSList *l, **lp = NULL;
+ char buf[1024], *realname;
+
+ if( strcmp( list, "AL" ) == 0 )
+ lp = &gc->permit;
+ else if( strcmp( list, "BL" ) == 0 )
+ lp = &gc->deny;
+
+ if( lp )
+ for( l = *lp; l; l = l->next )
+ if( g_strcasecmp( l->data, who ) == 0 )
+ return( 1 );
+
+ realname = g_new0( char, strlen( realname_ ) * 3 + 1 );
+ strcpy( realname, realname_ );
+ http_encode( realname );
+
+ g_snprintf( buf, sizeof( buf ), "ADD %d %s %s %s\r\n", ++md->trId, list, who, realname );
+ if( msn_write( gc, buf, strlen( buf ) ) )
+ {
+ g_free( realname );
+
+ if( lp )
+ *lp = g_slist_append( *lp, g_strdup( who ) );
+
+ return( 1 );
+ }
+
+ g_free( realname );
+
+ return( 0 );
+}
+
+int msn_buddy_list_remove( struct gaim_connection *gc, char *list, char *who )
+{
+ struct msn_data *md = gc->proto_data;
+ GSList *l = NULL, **lp = NULL;
+ char buf[1024];
+
+ if( strcmp( list, "AL" ) == 0 )
+ lp = &gc->permit;
+ else if( strcmp( list, "BL" ) == 0 )
+ lp = &gc->deny;
+
+ if( lp )
+ {
+ for( l = *lp; l; l = l->next )
+ if( g_strcasecmp( l->data, who ) == 0 )
+ break;
+
+ if( !l )
+ return( 1 );
+ }
+
+ g_snprintf( buf, sizeof( buf ), "REM %d %s %s\r\n", ++md->trId, list, who );
+ if( msn_write( gc, buf, strlen( buf ) ) )
+ {
+ if( lp )
+ *lp = g_slist_remove( *lp, l->data );
+
+ return( 1 );
+ }
+
+ return( 0 );
+}
+
+struct msn_buddy_ask_data
+{
+ struct gaim_connection *gc;
+ char *handle;
+ char *realname;
+};
+
+static void msn_buddy_ask_yes( gpointer w, struct msn_buddy_ask_data *bla )
+{
+ msn_buddy_list_add( bla->gc, "AL", bla->handle, bla->realname );
+
+ g_free( bla->handle );
+ g_free( bla->realname );
+ g_free( bla );
+}
+
+static void msn_buddy_ask_no( gpointer w, struct msn_buddy_ask_data *bla )
+{
+ msn_buddy_list_add( bla->gc, "BL", bla->handle, bla->realname );
+
+ g_free( bla->handle );
+ g_free( bla->realname );
+ g_free( bla );
+}
+
+void msn_buddy_ask( struct gaim_connection *gc, char *handle, char *realname )
+{
+ struct msn_buddy_ask_data *bla = g_new0( struct msn_buddy_ask_data, 1 );
+ char buf[1024];
+
+ bla->gc = gc;
+ bla->handle = g_strdup( handle );
+ bla->realname = g_strdup( realname );
+
+ g_snprintf( buf, sizeof( buf ),
+ "The user %s (%s) wants to add you to his/her buddy list. Do you want to allow this?",
+ handle, realname );
+ do_ask_dialog( gc, 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( strncmp( text + i - 2, "\n\n", 2 ) == 0 ||
+ strncmp( text + i - 4, "\r\n\r\n", 4 ) == 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 );
+
+ 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->data, 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->data, 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 );
+}
diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c
new file mode 100644
index 00000000..f1bda1a4
--- /dev/null
+++ b/protocols/msn/ns.c
@@ -0,0 +1,661 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2004 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 "passport.h"
+#include "md5.h"
+
+static void msn_ns_callback( gpointer data, gint source, GaimInputCondition cond );
+static int msn_ns_command( gpointer data, char **cmd, int num_parts );
+static int msn_ns_message( gpointer data, char *msg, int msglen, char **cmd, int num_parts );
+
+static void msn_auth_got_passport_id( struct passport_reply *rep );
+
+void msn_ns_connected( gpointer data, gint source, GaimInputCondition cond )
+{
+ struct gaim_connection *gc = data;
+ struct msn_data *md;
+ char s[1024];
+
+ if( !g_slist_find( msn_connections, gc ) )
+ return;
+
+ if( source == -1 )
+ {
+ hide_login_progress( gc, "Could not connect to server" );
+ signoff( gc );
+ return;
+ }
+
+ md = gc->proto_data;
+
+ if( !md->handler )
+ {
+ md->handler = g_new0( struct msn_handler_data, 1 );
+ md->handler->data = gc;
+ md->handler->exec_command = msn_ns_command;
+ md->handler->exec_message = msn_ns_message;
+ }
+ else
+ {
+ if( md->handler->rxq )
+ g_free( md->handler->rxq );
+
+ md->handler->rxlen = 0;
+ }
+
+ md->handler->fd = md->fd;
+ md->handler->rxq = g_new0( char, 1 );
+
+ g_snprintf( s, sizeof( s ), "VER %d MSNP8 CVR0\r\n", ++md->trId );
+ if( msn_write( gc, s, strlen( s ) ) )
+ {
+ gc->inpa = gaim_input_add( md->fd, GAIM_INPUT_READ, msn_ns_callback, gc );
+ set_login_progress( gc, 1, "Connected to server, waiting for reply" );
+ }
+}
+
+void msn_ns_callback( gpointer data, gint source, GaimInputCondition cond )
+{
+ struct gaim_connection *gc = data;
+ struct msn_data *md = gc->proto_data;
+
+ if( msn_handler( md->handler ) == -1 ) /* Don't do this on ret == 0, it's already done then. */
+ {
+ hide_login_progress( gc, "Error while reading from server" );
+ signoff( gc );
+ }
+}
+
+static int msn_ns_command( gpointer data, char **cmd, int num_parts )
+{
+ struct gaim_connection *gc = data;
+ struct msn_data *md = gc->proto_data;
+ char buf[1024];
+
+ if( num_parts == 0 )
+ {
+ /* Hrrm... Empty command...? Ignore? */
+ return( 1 );
+ }
+
+ if( strcmp( cmd[0], "VER" ) == 0 )
+ {
+ if( cmd[2] && strncmp( cmd[2], "MSNP8", 5 ) != 0 )
+ {
+ hide_login_progress( gc, "Unsupported protocol" );
+ signoff( gc );
+ return( 0 );
+ }
+
+ g_snprintf( buf, sizeof( buf ), "CVR %d 0x0409 mac 10.2.0 ppc macmsgs 3.5.1 macmsgs %s\r\n",
+ ++md->trId, gc->username );
+ return( msn_write( gc, buf, strlen( buf ) ) );
+ }
+ else if( strcmp( cmd[0], "CVR" ) == 0 )
+ {
+ /* We don't give a damn about the information we just received */
+ g_snprintf( buf, sizeof( buf ), "USR %d TWN I %s\r\n", ++md->trId, gc->username );
+ return( msn_write( gc, buf, strlen( buf ) ) );
+ }
+ else if( strcmp( cmd[0], "XFR" ) == 0 )
+ {
+ char *server;
+ int port;
+
+ if( num_parts == 6 && strcmp( cmd[2], "NS" ) == 0 )
+ {
+ gaim_input_remove( gc->inpa );
+ gc->inpa = 0;
+ closesocket( md->fd );
+
+ server = strchr( cmd[3], ':' );
+ if( !server )
+ {
+ hide_login_progress_error( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+ *server = 0;
+ port = atoi( server + 1 );
+ server = cmd[3];
+
+ set_login_progress( gc, 1, "Transferring to other server" );
+
+ md->fd = proxy_connect( server, port, msn_ns_connected, gc );
+ }
+ else if( num_parts == 6 && strcmp( cmd[2], "SB" ) == 0 )
+ {
+ struct msn_switchboard *sb;
+
+ server = strchr( cmd[3], ':' );
+ if( !server )
+ {
+ hide_login_progress_error( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+ *server = 0;
+ port = atoi( server + 1 );
+ server = cmd[3];
+
+ if( strcmp( cmd[4], "CKI" ) != 0 )
+ {
+ hide_login_progress_error( gc, "Unknown authentication method for switchboard" );
+ signoff( gc );
+ return( 0 );
+ }
+
+ debug( "Connecting to a new switchboard with key %s", cmd[5] );
+ sb = msn_sb_create( gc, server, port, cmd[5], MSN_SB_NEW );
+
+ 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
+ {
+ hide_login_progress_error( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+ }
+ else if( strcmp( cmd[0], "USR" ) == 0 )
+ {
+ if( num_parts == 5 && strcmp( cmd[2], "TWN" ) == 0 && strcmp( cmd[3], "S" ) == 0 )
+ {
+ /* Time for some Passport black magic... */
+ if( !passport_get_id( gc, gc->username, gc->password, cmd[4], msn_auth_got_passport_id ) )
+ {
+ hide_login_progress_error( gc, "Error while contacting Passport server" );
+ signoff( gc );
+ return( 0 );
+ }
+ }
+ else if( num_parts == 7 && strcmp( cmd[2], "OK" ) == 0 )
+ {
+ http_decode( cmd[4] );
+
+ strncpy( gc->displayname, cmd[4], sizeof( gc->displayname ) );
+ gc->displayname[sizeof(gc->displayname)-1] = 0;
+
+ set_login_progress( gc, 1, "Authenticated, getting buddy list" );
+
+ g_snprintf( buf, sizeof( buf ), "SYN %d 0\r\n", ++md->trId );
+ return( msn_write( gc, buf, strlen( buf ) ) );
+ }
+ else
+ {
+ hide_login_progress( gc, "Unknown authentication type" );
+ signoff( gc );
+ return( 0 );
+ }
+ }
+ else if( strcmp( cmd[0], "MSG" ) == 0 )
+ {
+ if( num_parts != 4 )
+ {
+ hide_login_progress_error( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+
+ md->handler->msglen = atoi( cmd[3] );
+
+ if( md->handler->msglen <= 0 )
+ {
+ hide_login_progress_error( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+ }
+ else if( strcmp( cmd[0], "SYN" ) == 0 )
+ {
+ if( num_parts == 5 )
+ {
+ md->buddycount = atoi( cmd[3] );
+
+ if( !*cmd[3] || md->buddycount == 0 )
+ msn_logged_in( gc );
+ }
+ else
+ {
+ /* Hrrm... This SYN reply doesn't really look like something we expected.
+ Let's assume everything is okay. */
+
+ msn_logged_in( gc );
+ }
+ }
+ else if( strcmp( cmd[0], "GTC" ) == 0 )
+ {
+ }
+ else if( strcmp( cmd[0], "BLP" ) == 0 )
+ {
+ }
+ else if( strcmp( cmd[0], "PRP" ) == 0 )
+ {
+ }
+ else if( strcmp( cmd[0], "LSG" ) == 0 )
+ {
+ }
+ else if( strcmp( cmd[0], "LST" ) == 0 )
+ {
+ int list;
+
+ if( num_parts != 4 && num_parts != 5 )
+ {
+ hide_login_progress( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+
+ http_decode( cmd[2] );
+ list = atoi( cmd[3] );
+
+ if( list & 1 ) /* FL */
+ {
+ add_buddy( gc, NULL, cmd[1], cmd[2] );
+ }
+ if( list & 2 ) /* AL */
+ {
+ gc->permit = g_slist_append( gc->permit, g_strdup( cmd[1] ) );
+ }
+ if( list & 4 ) /* BL */
+ {
+ gc->deny = g_slist_append( gc->deny, g_strdup( cmd[1] ) );
+ }
+ if( list & 8 ) /* RL */
+ {
+ if( ( list & 6 ) == 0 )
+ msn_buddy_ask( gc, cmd[1], cmd[2] );
+ }
+
+ if( --md->buddycount == 0 )
+ {
+ if( gc->flags & OPT_LOGGED_IN )
+ {
+ serv_got_crap( gc, "Successfully transferred to different server" );
+ g_snprintf( buf, sizeof( buf ), "CHG %d %s %d\r\n", ++md->trId, md->away_state->code, 0 );
+ return( msn_write( gc, buf, strlen( buf ) ) );
+ }
+ else
+ {
+ msn_logged_in( gc );
+ }
+ }
+ }
+ else if( strcmp( cmd[0], "BPR" ) == 0 )
+ {
+ }
+ else if( strcmp( cmd[0], "CHG" ) == 0 )
+ {
+ }
+ else if( strcmp( cmd[0], "CHL" ) == 0 )
+ {
+ md5_state_t state;
+ md5_byte_t digest[16];
+ int i;
+
+ if( num_parts != 3 )
+ {
+ hide_login_progress_error( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+
+ md5_init( &state );
+ md5_append( &state, (const md5_byte_t *) cmd[2], strlen( cmd[2] ) );
+ md5_append( &state, (const md5_byte_t *) QRY_CODE, strlen( QRY_CODE ) );
+ md5_finish( &state, digest );
+
+ g_snprintf( buf, sizeof( buf ), "QRY %d %s %d\r\n", ++md->trId, QRY_NAME, 32 );
+ for( i = 0; i < 16; i ++ )
+ g_snprintf( buf + strlen( buf ), 3, "%02x", digest[i] );
+
+ return( msn_write( gc, buf, strlen( buf ) ) );
+ }
+ else if( strcmp( cmd[0], "QRY" ) == 0 )
+ {
+ }
+ else if( strcmp( cmd[0], "QNG" ) == 0 )
+ {
+ }
+ else if( strcmp( cmd[0], "ILN" ) == 0 )
+ {
+ struct msn_away_state *st;
+
+ if( num_parts != 6 )
+ {
+ hide_login_progress_error( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+
+ http_decode( cmd[4] );
+ serv_buddy_rename( gc, cmd[3], cmd[4] );
+
+ st = msn_away_state_by_code( cmd[2] );
+ if( !st )
+ {
+ /* FIXME: Warn/Bomb about unknown away state? */
+ st = msn_away_state_list;
+ }
+
+ serv_got_update( gc, cmd[3], 1, 0, 0, 0, st->number, 0 );
+ }
+ else if( strcmp( cmd[0], "FLN" ) == 0 )
+ {
+ if( cmd[1] )
+ serv_got_update( gc, cmd[1], 0, 0, 0, 0, 0, 0 );
+ }
+ else if( strcmp( cmd[0], "NLN" ) == 0 )
+ {
+ struct msn_away_state *st;
+
+ if( num_parts != 5 )
+ {
+ hide_login_progress_error( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+
+ http_decode( cmd[3] );
+ serv_buddy_rename( gc, cmd[2], cmd[3] );
+
+ st = msn_away_state_by_code( cmd[1] );
+ if( !st )
+ {
+ /* FIXME: Warn/Bomb about unknown away state? */
+ st = msn_away_state_list;
+ }
+
+ serv_got_update( gc, cmd[2], 1, 0, 0, 0, st->number, 0 );
+ }
+ else if( strcmp( cmd[0], "RNG" ) == 0 )
+ {
+ struct msn_switchboard *sb;
+ char *server;
+ int session, port;
+
+ if( num_parts != 7 )
+ {
+ hide_login_progress_error( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+
+ session = atoi( cmd[1] );
+
+ server = strchr( cmd[2], ':' );
+ if( !server )
+ {
+ hide_login_progress_error( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+ *server = 0;
+ port = atoi( server + 1 );
+ server = cmd[2];
+
+ if( strcmp( cmd[3], "CKI" ) != 0 )
+ {
+ hide_login_progress_error( gc, "Unknown authentication method for switchboard" );
+ signoff( gc );
+ return( 0 );
+ }
+
+ debug( "Got a call from %s (session %d). Key = %s", cmd[5], session, cmd[4] );
+
+ sb = msn_sb_create( gc, server, port, cmd[4], session );
+ sb->who = g_strdup( cmd[5] );
+ }
+ else if( strcmp( cmd[0], "ADD" ) == 0 )
+ {
+ if( num_parts == 6 && strcmp( cmd[2], "RL" ) == 0 )
+ {
+ GSList *l;
+
+ http_decode( cmd[5] );
+
+ if( strchr( cmd[4], '@' ) == NULL )
+ {
+ hide_login_progress_error( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+
+ /* We got added by someone. If we don't have this person in permit/deny yet, inform the user. */
+ for( l = gc->permit; l; l = l->next )
+ if( g_strcasecmp( l->data, cmd[4] ) == 0 )
+ return( 1 );
+
+ for( l = gc->deny; l; l = l->next )
+ if( g_strcasecmp( l->data, cmd[4] ) == 0 )
+ return( 1 );
+
+ msn_buddy_ask( gc, cmd[4], cmd[5] );
+ }
+ }
+ else if( strcmp( cmd[0], "REM" ) == 0 )
+ {
+ }
+ else if( strcmp( cmd[0], "OUT" ) == 0 )
+ {
+ if( cmd[1] && strcmp( cmd[1], "OTH" ) == 0 )
+ {
+ hide_login_progress_error( gc, "Someone else logged in with your account" );
+ gc->wants_to_die = 1;
+ }
+ else if( cmd[1] && strcmp( cmd[1], "SSD" ) == 0 )
+ {
+ hide_login_progress_error( gc, "Terminating session because of server shutdown" );
+ }
+ else
+ {
+ hide_login_progress_error( gc, "Session terminated by remote server (reason unknown)" );
+ }
+
+ signoff( gc );
+ return( 0 );
+ }
+ else if( strcmp( cmd[0], "REA" ) == 0 )
+ {
+ if( num_parts != 5 )
+ {
+ hide_login_progress_error( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+
+ if( g_strcasecmp( cmd[3], gc->username ) == 0 )
+ {
+ http_decode( cmd[4] );
+ strncpy( gc->displayname, cmd[4], sizeof( gc->displayname ) );
+ gc->displayname[sizeof(gc->displayname)-1] = 0;
+ }
+ else
+ {
+ /* This is not supposed to happen, but let's handle it anyway... */
+ http_decode( cmd[4] );
+ serv_buddy_rename( gc, cmd[3], cmd[4] );
+ }
+ }
+ else if( strcmp( cmd[0], "IPG" ) == 0 )
+ {
+ do_error_dialog( gc, "Received IPG command, we don't handle them yet.", "MSN" );
+
+ md->handler->msglen = atoi( cmd[1] );
+
+ if( md->handler->msglen <= 0 )
+ {
+ hide_login_progress_error( gc, "Syntax error" );
+ signoff( gc );
+ return( 0 );
+ }
+ }
+ else if( isdigit( cmd[0][0] ) )
+ {
+ int num = atoi( cmd[0] );
+ struct msn_status_code *err = msn_status_by_number( num );
+
+ g_snprintf( buf, sizeof( buf ), "Error reported by MSN server: %s", err->text );
+ do_error_dialog( gc, buf, "MSN" );
+
+ if( err->flags & STATUS_FATAL )
+ {
+ signoff( gc );
+ return( 0 );
+ }
+ }
+ else
+ {
+ debug( "Received unknown command from main server: %s", cmd[0] );
+ }
+
+ return( 1 );
+}
+
+static int msn_ns_message( gpointer data, char *msg, int msglen, char **cmd, int num_parts )
+{
+ struct gaim_connection *gc = 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 )
+ serv_got_crap( gc, "The server is going down for maintenance in %s minutes.", arg1 );
+ }
+
+ if( arg1 ) g_free( arg1 );
+ if( mtype ) 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 )
+ {
+ char *inbox = msn_findheader( body, "Inbox-Unread:", blen );
+ char *folders = msn_findheader( body, "Folders-Unread:", blen );
+
+ if( inbox && folders )
+ {
+ serv_got_crap( gc, "INBOX contains %s new messages, plus %s messages in other folders.", inbox, folders );
+ }
+ }
+ else if( g_strncasecmp( ct, "text/x-msmsgsemailnotification", 30 ) == 0 )
+ {
+ char *from = msn_findheader( body, "From-Addr:", blen );
+ char *fromname = msn_findheader( body, "From:", blen );
+
+ if( from && fromname )
+ {
+ serv_got_crap( gc, "Received an e-mail message from %s <%s>.", fromname, from );
+ }
+ }
+ 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 );
+ }
+ }
+
+ return( 1 );
+}
+
+static void msn_auth_got_passport_id( struct passport_reply *rep )
+{
+ struct gaim_connection *gc = rep->data;
+ struct msn_data *md = gc->proto_data;
+ char *key = rep->result;
+ char buf[1024];
+
+ if( key == NULL )
+ {
+ hide_login_progress( gc, "Error during Passport authentication" );
+ signoff( gc );
+ }
+ else
+ {
+ g_snprintf( buf, sizeof( buf ), "USR %d TWN S %s\r\n", ++md->trId, key );
+ msn_write( gc, buf, strlen( buf ) );
+ }
+}
diff --git a/protocols/msn/passport.c b/protocols/msn/passport.c
new file mode 100644
index 00000000..640126a0
--- /dev/null
+++ b/protocols/msn/passport.c
@@ -0,0 +1,313 @@
+/* passport.c
+ *
+ * Functions to login to microsoft passport service for Messenger
+ * Copyright (C) 2004 Wouter Paesen <wouter@blue-gate.be>
+ * Copyright (C) 2004 Wilmer van der Gaast <wilmer@gaast.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that is will be useful,
+ * bit WITHOU ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "ssl_client.h"
+#include "passport.h"
+#include "msn.h"
+#include "bitlbee.h"
+#include <ctype.h>
+#include <errno.h>
+
+#define MSN_BUF_LEN 8192
+
+static char *prd_cached = NULL;
+
+static char *passport_create_header( char *reply, char *email, char *pwd );
+static int passport_retrieve_dalogin( gpointer data, gpointer func, char *header );
+static void passport_retrieve_dalogin_connected( gpointer data, void *ssl, GaimInputCondition cond );
+static int passport_get_id_from( gpointer data, gpointer func, char *header_i, char *url );
+static void passport_get_id_connected( gpointer data, void *ssl, GaimInputCondition cond );
+static void destroy_reply( struct passport_reply *rep );
+
+
+int passport_get_id( gpointer data, char *username, char *password, char *cookie, gpointer func )
+{
+ char *header = passport_create_header( cookie, username, password );
+
+ if( prd_cached )
+ {
+ int st;
+
+ st = passport_get_id_from( data, func, header, prd_cached );
+ g_free( header );
+ return( st );
+ }
+ else
+ {
+ return( passport_retrieve_dalogin( data, func, header ) );
+ }
+}
+
+
+static char *passport_create_header( char *reply, char *email, char *pwd )
+{
+ char *buffer = g_new0( char, 2048 );
+ char *currenttoken;
+ char *email_enc, *pwd_enc;
+
+ email_enc = g_new0( char, strlen( email ) * 3 + 1 );
+ strcpy( email_enc, email );
+ http_encode( email_enc );
+
+ pwd_enc = g_new0( char, strlen( pwd ) * 3 + 1 );
+ strcpy( pwd_enc, pwd );
+ http_encode( pwd_enc );
+
+ currenttoken = strstr( reply, "lc=" );
+ if( currenttoken == NULL )
+ return( NULL );
+
+ g_snprintf( buffer, 2048,
+ "Authorization: Passport1.4 OrgVerb=GET,"
+ "OrgURL=http%%3A%%2F%%2Fmessenger%%2Emsn%%2Ecom,"
+ "sign-in=%s,pwd=%s,%s", email_enc, pwd_enc,
+ currenttoken );
+
+ g_free( email_enc );
+ g_free( pwd_enc );
+
+ return( buffer );
+}
+
+
+static int passport_retrieve_dalogin( gpointer data, gpointer func, char *header )
+{
+ struct passport_reply *rep = g_new0( struct passport_reply, 1 );
+ void *ssl;
+
+ rep->data = data;
+ rep->func = func;
+ rep->header = header;
+
+ ssl = ssl_connect( "nexus.passport.com", 443, passport_retrieve_dalogin_connected, rep );
+
+ if( !ssl )
+ destroy_reply( rep );
+
+ return( ssl != NULL );
+}
+
+#define PPR_BUFFERSIZE 2048
+#define PPR_REQUEST "GET /rdr/pprdr.asp HTTP/1.0\r\n\r\n"
+static void passport_retrieve_dalogin_connected( gpointer data, void *ssl, GaimInputCondition cond )
+{
+ int ret;
+ char buffer[PPR_BUFFERSIZE+1];
+ struct passport_reply *rep = data;
+
+ if( !g_slist_find( msn_connections, rep->data ) )
+ {
+ if( ssl ) ssl_disconnect( ssl );
+ destroy_reply( rep );
+ return;
+ }
+
+ if( !ssl )
+ {
+ rep->func( rep );
+ destroy_reply( rep );
+ return;
+ }
+
+ ssl_write( ssl, PPR_REQUEST, strlen( PPR_REQUEST ) );
+
+ if( ( ret = ssl_read( ssl, buffer, PPR_BUFFERSIZE ) ) <= 0 )
+ {
+ goto failure;
+ }
+
+ {
+ char *dalogin = strstr( buffer, "DALogin=" );
+ char *urlend;
+
+ if( !dalogin )
+ goto failure;
+
+ dalogin += strlen( "DALogin=" );
+ urlend = strchr( dalogin, ',' );
+ if( urlend )
+ *urlend = 0;
+
+ /* strip the http(s):// part from the url */
+ urlend = strstr( urlend, "://" );
+ if( urlend )
+ dalogin = urlend + strlen( "://" );
+
+ if( prd_cached == NULL )
+ prd_cached = g_strdup( dalogin );
+ }
+
+ if( passport_get_id_from( rep->data, rep->func, rep->header, prd_cached ) )
+ {
+ ssl_disconnect( ssl );
+ destroy_reply( rep );
+ return;
+ }
+
+failure:
+ ssl_disconnect( ssl );
+ rep->func( rep );
+ destroy_reply( rep );
+}
+
+
+static int passport_get_id_from( gpointer data, gpointer func, char *header_i, char *url )
+{
+ struct passport_reply *rep = g_new0( struct passport_reply, 1 );
+ char server[512], *dummy;
+ void *ssl;
+
+ rep->data = data;
+ rep->func = func;
+ rep->redirects = 4;
+
+ strncpy( server, url, 512 );
+ dummy = strchr( server, '/' );
+ if( dummy )
+ *dummy = 0;
+
+ ssl = ssl_connect( server, 443, passport_get_id_connected, rep );
+
+ if( ssl )
+ {
+ rep->header = g_strdup( header_i );
+ rep->url = g_strdup( url );
+ }
+ else
+ {
+ destroy_reply( rep );
+ }
+
+ return( ssl != NULL );
+}
+
+#define PPG_BUFFERSIZE 4096
+static void passport_get_id_connected( gpointer data, void *ssl, GaimInputCondition cond )
+{
+ struct passport_reply *rep = data;
+ char server[512], buffer[PPG_BUFFERSIZE+1], *dummy;
+ int ret;
+
+ if( !g_slist_find( msn_connections, rep->data ) )
+ {
+ if( ssl ) ssl_disconnect( ssl );
+ destroy_reply( rep );
+ return;
+ }
+
+ if( !ssl )
+ {
+ rep->func( rep );
+ destroy_reply( rep );
+ return;
+ }
+
+ memset( buffer, 0, PPG_BUFFERSIZE + 1 );
+
+ strncpy( server, rep->url, 512 );
+ dummy = strchr( server, '/' );
+ if( dummy == NULL )
+ goto end;
+
+ g_snprintf( buffer, PPG_BUFFERSIZE - 1, "GET %s HTTP/1.0\r\n"
+ "%s\r\n\r\n", dummy, rep->header );
+
+ ssl_write( ssl, buffer, strlen( buffer ) );
+ memset( buffer, 0, PPG_BUFFERSIZE + 1 );
+
+ {
+ char *buffer2 = buffer;
+
+ while( ( ( ret = ssl_read( ssl, buffer2, 512 ) ) > 0 ) &&
+ ( buffer + PPG_BUFFERSIZE - buffer2 - ret - 512 >= 0 ) )
+ {
+ buffer2 += ret;
+ }
+ }
+
+ if( *buffer == 0 )
+ goto end;
+
+ if( ( dummy = strstr( buffer, "Location:" ) ) )
+ {
+ char *urlend;
+
+ rep->redirects --;
+ if( rep->redirects == 0 )
+ goto end;
+
+ dummy += strlen( "Location:" );
+ while( isspace( *dummy ) ) dummy ++;
+ urlend = dummy;
+ while( !isspace( *urlend ) ) urlend ++;
+ *urlend = 0;
+ if( ( urlend = strstr( dummy, "://" ) ) )
+ dummy = urlend + strlen( "://" );
+
+ g_free( rep->url );
+ rep->url = g_strdup( dummy );
+
+ strncpy( server, dummy, sizeof( server ) - 1 );
+ dummy = strchr( server, '/' );
+ if( dummy ) *dummy = 0;
+
+ ssl_disconnect( ssl );
+
+ if( ssl_connect( server, 443, passport_get_id_connected, rep ) )
+ {
+ return;
+ }
+ else
+ {
+ rep->func( rep );
+ destroy_reply( rep );
+ return;
+ }
+ }
+ else if( strstr( buffer, "200 OK" ) )
+ {
+ if( ( dummy = strstr( buffer, "from-PP='" ) ) )
+ {
+ char *responseend;
+
+ dummy += strlen( "from-PP='" );
+ responseend = strchr( dummy, '\'' );
+ if( responseend )
+ *responseend = 0;
+
+ rep->result = g_strdup( dummy );
+ }
+ }
+
+end:
+ ssl_disconnect( ssl );
+ rep->func( rep );
+ destroy_reply( rep );
+}
+
+
+static void destroy_reply( struct passport_reply *rep )
+{
+ if( rep->result ) g_free( rep->result );
+ if( rep->url ) g_free( rep->url );
+ if( rep->header ) g_free( rep->header );
+ if( rep ) g_free( rep );
+}
diff --git a/protocols/msn/passport.h b/protocols/msn/passport.h
new file mode 100644
index 00000000..63fef2e9
--- /dev/null
+++ b/protocols/msn/passport.h
@@ -0,0 +1,47 @@
+#ifndef __PASSPORT_H__
+#define __PASSPORT_H__
+/* passport.h
+ *
+ * Functions to login to Microsoft Passport Service for Messenger
+ * Copyright (C) 2004 Wouter Paesen <wouter@blue-gate.be>,
+ * Wilmer van der Gaast <wilmer@gaast.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that is will be useful,
+ * bit WITHOU ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <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"
+
+struct passport_reply
+{
+ void *data;
+ char *result;
+ void (*func)( struct passport_reply * );
+ char *url;
+ char *header;
+ int redirects;
+};
+
+int passport_get_id( gpointer data, char *username, char *password, char *cookie, gpointer func );
+
+#endif /* __PASSPORT_H__ */
diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c
new file mode 100644
index 00000000..38ead0bd
--- /dev/null
+++ b/protocols/msn/sb.c
@@ -0,0 +1,659 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2004 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 "passport.h"
+#include "md5.h"
+
+static void msn_sb_callback( gpointer data, gint source, GaimInputCondition cond );
+static int msn_sb_command( gpointer data, char **cmd, int num_parts );
+static int msn_sb_message( gpointer data, char *msg, int msglen, char **cmd, int num_parts );
+
+int msn_sb_write( struct msn_switchboard *sb, char *s, int len )
+{
+ int st;
+
+ st = write( sb->fd, s, len );
+ if( st != len )
+ {
+ msn_sb_destroy( sb );
+ return( 0 );
+ }
+
+ return( 1 );
+}
+
+struct msn_switchboard *msn_sb_create( struct gaim_connection *gc, char *host, int port, char *key, int session )
+{
+ struct msn_data *md = gc->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->gc = gc;
+ 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 gaim_connection *gc, char *handle )
+{
+ struct msn_data *md = gc->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_id( struct gaim_connection *gc, int id )
+{
+ struct msn_data *md = gc->proto_data;
+ struct msn_switchboard *sb;
+ GSList *l;
+
+ for( l = md->switchboards; l; l = l->next )
+ {
+ sb = l->data;
+ if( sb->chat && sb->chat->id == id )
+ return( sb );
+ }
+
+ return( NULL );
+}
+
+struct msn_switchboard *msn_sb_spare( struct gaim_connection *gc )
+{
+ struct msn_data *md = gc->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 cmd[1024], *buf;
+ int i, j;
+
+ if( strcmp( text, TYPING_NOTIFICATION_MESSAGE ) != 0 )
+ {
+ buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 );
+ 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];
+ }
+ }
+ else
+ {
+ i = strlen( MSN_TYPING_HEADERS ) + strlen( sb->gc->username );
+ buf = g_new0( char, strlen( MSN_TYPING_HEADERS ) + strlen( sb->gc->username ) );
+ i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->gc->username );
+ }
+
+ g_snprintf( cmd, sizeof( cmd ), "MSG %d N %d\r\n", ++sb->trId, i );
+ if( msn_sb_write( sb, cmd, strlen( cmd ) ) && msn_sb_write( sb, buf, i ) )
+ {
+ 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 );
+ }
+}
+
+void msn_sb_to_chat( struct msn_switchboard *sb )
+{
+ struct gaim_connection *gc = sb->gc;
+ char buf[1024];
+
+ /* Create the groupchat structure. */
+ g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session );
+ sb->chat = serv_got_joined_chat( gc, ++msn_chat_id, buf );
+
+ /* Populate the channel. */
+ if( sb->who ) add_chat_buddy( sb->chat, sb->who );
+ add_chat_buddy( sb->chat, gc->username );
+
+ /* And make sure the switchboard doesn't look like a regular chat anymore. */
+ if( sb->who )
+ {
+ g_free( sb->who );
+ sb->who = NULL;
+ }
+}
+
+void msn_sb_destroy( struct msn_switchboard *sb )
+{
+ struct gaim_connection *gc = sb->gc;
+ struct msn_data *md = gc->proto_data;
+
+ debug( "Destroying switchboard: %s", sb->who ? sb->who : sb->key ? sb->key : "" );
+
+ if( sb->key ) g_free( sb->key );
+ if( sb->who ) g_free( sb->who );
+
+ if( sb->msgq )
+ {
+ struct msn_message *m;
+ GSList *l;
+
+ for( l = sb->msgq; l; l = l->next )
+ {
+ m = l->data;
+ g_free( m->who );
+ g_free( m->text );
+ g_free( m );
+ }
+ g_slist_free( sb->msgq );
+
+ serv_got_crap( gc, "Warning: Closing down MSN switchboard connection with unsent message(s), you'll have to resend them." );
+ }
+
+ if( sb->chat )
+ {
+ serv_got_chat_left( gc, sb->chat->id );
+ }
+
+ 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 ) gaim_input_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 );
+}
+
+void msn_sb_connected( gpointer data, gint source, GaimInputCondition cond )
+{
+ struct msn_switchboard *sb = data;
+ struct gaim_connection *gc;
+ struct msn_data *md;
+ char buf[1024];
+
+ /* Are we still alive? */
+ if( !g_slist_find( msn_switchboards, sb ) )
+ return;
+
+ gc = sb->gc;
+ md = gc->proto_data;
+
+ if( source != sb->fd )
+ {
+ debug( "ERROR %d while connecting to switchboard server", 1 );
+ msn_sb_destroy( sb );
+ return;
+ }
+
+ /* 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, gc->username, sb->key );
+ else
+ g_snprintf( buf, sizeof( buf ), "ANS %d %s %s %d\r\n", ++sb->trId, gc->username, sb->key, sb->session );
+
+ if( msn_sb_write( sb, buf, strlen( buf ) ) )
+ sb->inp = gaim_input_add( sb->fd, GAIM_INPUT_READ, msn_sb_callback, sb );
+ else
+ debug( "ERROR %d while connecting to switchboard server", 2 );
+}
+
+static void msn_sb_callback( gpointer data, gint source, GaimInputCondition cond )
+{
+ struct msn_switchboard *sb = data;
+
+ if( msn_handler( sb->handler ) == -1 )
+ {
+ debug( "ERROR: Switchboard died" );
+ msn_sb_destroy( sb );
+ }
+}
+
+static int msn_sb_command( gpointer data, char **cmd, int num_parts )
+{
+ struct msn_switchboard *sb = data;
+ struct gaim_connection *gc = sb->gc;
+ char buf[1024];
+
+ if( !num_parts )
+ {
+ /* Hrrm... Empty command...? Ignore? */
+ return( 1 );
+ }
+
+ if( strcmp( cmd[0], "XFR" ) == 0 )
+ {
+ hide_login_progress_error( gc, "Received an XFR from a switchboard server, unable to comply! This is likely to be a bug, please report it!" );
+ signoff( gc );
+ 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 )
+ {
+ g_snprintf( buf, sizeof( buf ), "CAL %d %s\r\n", ++sb->trId, sb->who );
+ return( msn_sb_write( sb, buf, strlen( buf ) ) );
+ }
+ 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 = serv_got_joined_chat( gc, ++msn_chat_id, buf );
+
+ g_free( sb->who );
+ sb->who = NULL;
+ }
+
+ add_chat_buddy( sb->chat, cmd[4] );
+
+ if( num == tot )
+ {
+ add_chat_buddy( sb->chat, gc->username );
+ }
+ }
+ }
+ 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;
+ }
+ 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 );
+ }
+
+ 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 );
+
+ add_chat_buddy( sb->chat, cmd[1] );
+ }
+ else if( sb->chat )
+ {
+ add_chat_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], "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 )
+ {
+ /* 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 )
+ {
+ remove_chat_buddy( sb->chat, cmd[1], "" );
+ }
+ else
+ {
+ /* PANIC! */
+ }
+ }
+ else if( isdigit( cmd[0][0] ) )
+ {
+ int num = atoi( cmd[0] );
+ struct msn_status_code *err = msn_status_by_number( num );
+
+ g_snprintf( buf, sizeof( buf ), "Error reported by switchboard server: %s", err->text );
+ do_error_dialog( gc, buf, "MSN" );
+
+ if( err->flags & STATUS_SB_FATAL )
+ {
+ msn_sb_destroy( sb );
+ return( 0 );
+ }
+ else if( err->flags & STATUS_FATAL )
+ {
+ signoff( gc );
+ return( 0 );
+ }
+ }
+ else
+ {
+ debug( "Received unknown command from switchboard server: %s", cmd[0] );
+ }
+
+ return( 1 );
+}
+
+static int msn_sb_message( gpointer data, char *msg, int msglen, char **cmd, int num_parts )
+{
+ struct msn_switchboard *sb = data;
+ struct gaim_connection *gc = sb->gc;
+ 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 )
+ {
+ serv_got_im( gc, cmd[1], body, 0, 0, blen );
+ }
+ else if( sb->chat )
+ {
+ serv_got_chat_in( gc, sb->chat->id, cmd[1], 0, body, 0 );
+ }
+ else
+ {
+ /* PANIC! */
+ }
+ }
+ else if( g_strncasecmp( ct, "text/x-msmsgsinvite", 19 ) == 0 )
+ {
+ char *itype = msn_findheader( body, "Application-GUID:", blen );
+ char buf[1024];
+
+ g_free( ct );
+
+ *buf = 0;
+
+ if( !itype )
+ return( 1 );
+
+ /* File transfer. */
+ if( strcmp( itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" ) == 0 )
+ {
+ char *name = msn_findheader( body, "Application-File:", blen );
+ char *size = msn_findheader( body, "Application-FileSize:", blen );
+
+ if( name && size )
+ {
+ g_snprintf( buf, sizeof( buf ), "<< \x02""BitlBee\x02"" - Filetransfer: `%s', %s bytes >>\n"
+ "Filetransfers are not supported by BitlBee for now...", name, size );
+ }
+ else
+ {
+ strcpy( buf, "<< \x02""BitlBee\x02"" - Corrupted MSN filetransfer invitation message >>" );
+ }
+
+ if( name ) g_free( name );
+ if( size ) g_free( size );
+ }
+ else
+ {
+ char *iname = msn_findheader( body, "Application-Name:", blen );
+
+ g_snprintf( buf, sizeof( buf ), "<< \x02""BitlBee\x02"" - Unknown MSN invitation - %s (%s) >>",
+ itype, iname ? iname : "no name" );
+
+ if( iname ) g_free( iname );
+ }
+
+ g_free( itype );
+
+ if( !*buf )
+ return( 1 );
+
+ if( sb->who )
+ {
+ serv_got_im( gc, cmd[1], buf, 0, 0, strlen( buf ) );
+ }
+ else if( sb->chat )
+ {
+ serv_got_chat_in( gc, sb->chat->id, cmd[1], 0, buf, 0 );
+ }
+ else
+ {
+ /* PANIC! */
+ }
+ }
+ else if( g_strncasecmp( ct, "text/x-msmsgscontrol", 20 ) == 0 )
+ {
+ char *who = msn_findheader( msg, "TypingUser:", msglen );
+
+ if( who )
+ {
+ serv_got_typing( gc, who, 5 );
+ g_free( who );
+ }
+
+ g_free( ct );
+ }
+ else
+ {
+ g_free( ct );
+ }
+ }
+
+ return( 1 );
+}
diff --git a/protocols/msn/tables.c b/protocols/msn/tables.c
new file mode 100644
index 00000000..0cd80c01
--- /dev/null
+++ b/protocols/msn/tables.c
@@ -0,0 +1,166 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2004 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"
+
+struct msn_away_state msn_away_state_list[] =
+{
+ { 0, "NLN", "Available" },
+ { 1, "BSY", "Busy" },
+ { 3, "IDL", "Idle" },
+ { 5, "BRB", "Be Right Back" },
+ { 7, "AWY", "Away" },
+ { 9, "PHN", "On the Phone" },
+ { 11, "LUN", "Out to Lunch" },
+ { 13, "HDN", "Hidden" },
+ { -1, "", "" }
+};
+
+struct msn_away_state *msn_away_state_by_number( int number )
+{
+ int i;
+
+ for( i = 0; msn_away_state_list[i].number > -1; i ++ )
+ if( msn_away_state_list[i].number == number )
+ return( msn_away_state_list + i );
+
+ return( NULL );
+}
+
+struct msn_away_state *msn_away_state_by_code( char *code )
+{
+ int i;
+
+ for( i = 0; msn_away_state_list[i].number > -1; i ++ )
+ if( g_strcasecmp( msn_away_state_list[i].code, code ) == 0 )
+ return( msn_away_state_list + i );
+
+ return( NULL );
+}
+
+struct msn_away_state *msn_away_state_by_name( char *name )
+{
+ int i;
+
+ for( i = 0; msn_away_state_list[i].number > -1; i ++ )
+ if( g_strcasecmp( msn_away_state_list[i].name, name ) == 0 )
+ return( msn_away_state_list + i );
+
+ return( NULL );
+}
+
+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", 0 },
+ { 209, "Forbidden nickname", 0 },
+ { 210, "Buddy list too long", 0 },
+ { 215, "Handle is already in list", 0 },
+ { 216, "Handle is not in list", 0 },
+ { 217, "Person is off-line or non-existent", 0 },
+ { 218, "Already in that mode", 0 },
+ { 219, "Handle is already in opposite list", 0 },
+ { 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 },
+ { 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", 0 },
+ { 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_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 }
+};
+
+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 );
+}