diff options
Diffstat (limited to 'protocols/msn')
-rw-r--r-- | protocols/msn/Makefile | 39 | ||||
-rw-r--r-- | protocols/msn/msn.c | 402 | ||||
-rw-r--r-- | protocols/msn/msn.h | 170 | ||||
-rw-r--r-- | protocols/msn/msn_util.c | 359 | ||||
-rw-r--r-- | protocols/msn/ns.c | 661 | ||||
-rw-r--r-- | protocols/msn/passport.c | 313 | ||||
-rw-r--r-- | protocols/msn/passport.h | 47 | ||||
-rw-r--r-- | protocols/msn/sb.c | 659 | ||||
-rw-r--r-- | protocols/msn/tables.c | 166 |
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 ); +} |