diff options
Diffstat (limited to 'protocols/msn')
-rw-r--r-- | protocols/msn/Makefile | 6 | ||||
-rw-r--r-- | protocols/msn/invitation.c | 622 | ||||
-rw-r--r-- | protocols/msn/invitation.h | 82 | ||||
-rw-r--r-- | protocols/msn/msn.c | 60 | ||||
-rw-r--r-- | protocols/msn/msn.h | 16 | ||||
-rw-r--r-- | protocols/msn/msn_util.c | 91 | ||||
-rw-r--r-- | protocols/msn/ns.c | 74 | ||||
-rw-r--r-- | protocols/msn/sb.c | 101 |
8 files changed, 956 insertions, 96 deletions
diff --git a/protocols/msn/Makefile b/protocols/msn/Makefile index 6a588613..781482f5 100644 --- a/protocols/msn/Makefile +++ b/protocols/msn/Makefile @@ -7,11 +7,13 @@ ### DEFINITIONS -include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/msn/ +endif # [SH] Program variables objects = msn.o msn_util.o ns.o passport.o sb.o tables.o -CFLAGS += -Wall LFLAGS += -r # [SH] Phony targets @@ -32,7 +34,7 @@ distclean: clean $(objects): ../../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/protocols/msn/invitation.c b/protocols/msn/invitation.c new file mode 100644 index 00000000..9f8b9a6e --- /dev/null +++ b/protocols/msn/invitation.c @@ -0,0 +1,622 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2008 Uli Meis * +* Copyright 2006 Marijn Kruisselbrink and others * +\********************************************************************/ + +/* MSN module - File transfer support */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA + */ + +#include "bitlbee.h" +#include "invitation.h" +#include "msn.h" +#include "lib/ftutil.h" + +#ifdef debug +#undef debug +#endif +#define debug(msg...) log_message( LOGLVL_INFO, msg ) + +static void msn_ftp_free( file_transfer_t *file ); +static void msn_ftpr_accept( file_transfer_t *file ); +static void msn_ftp_finished( file_transfer_t *file ); +static void msn_ftp_canceled( file_transfer_t *file, char *reason ); +static gboolean msn_ftpr_write_request( file_transfer_t *file ); + +static gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond ); +static gboolean msn_ftp_read( gpointer data, gint fd, b_input_condition cond ); +gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len ); + +/* + * Vararg wrapper for imcb_file_canceled(). + */ +gboolean msn_ftp_abort( file_transfer_t *file, char *format, ... ) +{ + va_list params; + va_start( params, format ); + char error[128]; + + if( vsnprintf( error, 128, format, params ) < 0 ) + sprintf( error, "internal error parsing error string (BUG)" ); + va_end( params ); + imcb_file_canceled( file, error ); + return FALSE; +} + +/* very useful */ +#define ASSERTSOCKOP(op, msg) \ + if( (op) == -1 ) \ + return msn_ftp_abort( file , msg ": %s", strerror( errno ) ); + +void msn_ftp_invitation_cmd( struct im_connection *ic, char *who, int cookie, char *icmd, + char *trailer ) +{ + struct msn_message *m = g_new0( struct msn_message, 1 ); + + m->text = g_strdup_printf( "%s" + "Invitation-Command: %s\r\n" + "Invitation-Cookie: %u\r\n" + "%s", + MSN_INVITE_HEADERS, + icmd, + cookie, + trailer); + + m->who = g_strdup( who ); + + msn_sb_write_msg( ic, m ); +} + +void msn_ftp_cancel_invite( struct im_connection *ic, char *who, int cookie, char *code ) +{ + char buf[64]; + + g_snprintf( buf, sizeof( buf ), "Cancel-Code: %s\r\n", code ); + msn_ftp_invitation_cmd( ic, who, cookie, "CANCEL", buf ); +} + +void msn_ftp_transfer_request( struct im_connection *ic, file_transfer_t *file, char *who ) +{ + unsigned int cookie = time( NULL ); /* TODO: randomize */ + char buf[2048]; + + msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 ); + file->data = msn_file; + file->free = msn_ftp_free; + file->canceled = msn_ftp_canceled; + file->write = msn_ftps_write; + msn_file->md = ic->proto_data; + msn_file->invite_cookie = cookie; + msn_file->handle = g_strdup( who ); + msn_file->dcc = file; + msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc ); + msn_file->fd = -1; + msn_file->sbufpos = 3; + + g_snprintf( buf, sizeof( buf ), + "Application-Name: File Transfer\r\n" + "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n" + "Application-File: %s\r\n" + "Application-FileSize: %zd\r\n", + file->file_name, + file->file_size); + + msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, cookie, "INVITE", buf ); + + imcb_file_recv_start( file ); +} + +void msn_invitation_invite( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + char *itype = msn_findheader( body, "Application-GUID:", blen ); + char *name, *size, *invitecookie, *reject = NULL; + user_t *u; + size_t isize; + file_transfer_t *file; + + if( !itype || strcmp( itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" ) != 0 ) { + /* Don't know what that is - don't care */ + char *iname = msn_findheader( body, "Application-Name:", blen ); + imcb_log( sb->ic, "Received unknown MSN invitation %s (%s) from %s", + itype ? : "with no GUID", iname ? iname : "no application name", handle ); + g_free( iname ); + reject = "REJECT_NOT_INSTALLED"; + } else if ( + !( name = msn_findheader( body, "Application-File:", blen )) || + !( size = msn_findheader( body, "Application-FileSize:", blen )) || + !( invitecookie = msn_findheader( body, "Invitation-Cookie:", blen)) || + !( isize = atoll( size ) ) ) { + imcb_log( sb->ic, "Received corrupted transfer request from %s" + "(name=%s, size=%s, invitecookie=%s)", + handle, name, size, invitecookie ); + reject = "REJECT"; + } else if ( !( u = user_findhandle( sb->ic, handle ) ) ) { + imcb_log( sb->ic, "Error in parsing transfer request, User '%s'" + "is not in contact list", handle ); + reject = "REJECT"; + } else if ( !( file = imcb_file_send_start( sb->ic, handle, name, isize ) ) ) { + imcb_log( sb->ic, "Error initiating transfer for request from %s for %s", + handle, name ); + reject = "REJECT"; + } else { + msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 ); + file->data = msn_file; + file->accept = msn_ftpr_accept; + file->free = msn_ftp_free; + file->finished = msn_ftp_finished; + file->canceled = msn_ftp_canceled; + file->write_request = msn_ftpr_write_request; + msn_file->md = sb->ic->proto_data; + msn_file->invite_cookie = cookie; + msn_file->handle = g_strdup( handle ); + msn_file->dcc = file; + msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc ); + msn_file->fd = -1; + } + + if( reject ) + msn_ftp_cancel_invite( sb->ic, sb->who, cookie, reject ); + + g_free( name ); + g_free( size ); + g_free( invitecookie ); + g_free( itype ); +} + +msn_filetransfer_t* msn_find_filetransfer( struct msn_data *md, unsigned int cookie, char *handle ) +{ + GSList *l; + + for( l = md->filetransfers; l; l = l->next ) { + msn_filetransfer_t *file = ( (file_transfer_t*) l->data )->data; + if( file->invite_cookie == cookie && strcmp( handle, file->handle ) == 0 ) { + return file; + } + } + return NULL; +} + +gboolean msn_ftps_connected( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + struct sockaddr_storage clt_addr; + socklen_t ssize = sizeof( clt_addr ); + + debug( "Connected to MSNFTP client" ); + + ASSERTSOCKOP( msn_file->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" ); + + closesocket( fd ); + fd = msn_file->fd; + sock_make_nonblocking( fd ); + + msn_file->r_event_id = b_input_add( fd, B_EV_IO_READ, msn_ftp_read, file ); + + return FALSE; +} + +void msn_invitations_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + file_transfer_t *file = msn_file->dcc; + char buf[1024]; + unsigned int acookie = time ( NULL ); + char host[HOST_NAME_MAX+1]; + char port[6]; + char *errmsg; + + msn_file->auth_cookie = acookie; + + if( ( msn_file->fd = ft_listen( NULL, host, port, FALSE, &errmsg ) ) == -1 ) { + msn_ftp_abort( file, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s", errmsg ); + return; + } + + msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftps_connected, file ); + + g_snprintf( buf, sizeof( buf ), + "IP-Address: %s\r\n" + "Port: %s\r\n" + "AuthCookie: %d\r\n" + "Launch-Application: FALSE\r\n" + "Request-Data: IP-Address:\r\n\r\n", + host, + port, + msn_file->auth_cookie ); + + msn_ftp_invitation_cmd( msn_file->md->ic, handle, msn_file->invite_cookie, "ACCEPT", buf ); +} + +void msn_invitationr_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) { + file_transfer_t *file = msn_file->dcc; + char *authcookie, *ip, *port; + + if( !( authcookie = msn_findheader( body, "AuthCookie:", blen ) ) || + !( ip = msn_findheader( body, "IP-Address:", blen ) ) || + !( port = msn_findheader( body, "Port:", blen ) ) ) { + msn_ftp_abort( file, "Received invalid accept reply" ); + } else if( + ( msn_file->fd = proxy_connect( ip, atoi( port ), msn_ftp_connected, file ) ) + < 0 ) { + msn_ftp_abort( file, "Error connecting to MSN client" ); + } else + msn_file->auth_cookie = strtoul( authcookie, NULL, 10 ); + + g_free( authcookie ); + g_free( ip ); + g_free( port ); +} + +void msn_invitation_accept( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle ); + file_transfer_t *file = msn_file ? msn_file->dcc : NULL; + + if( !msn_file ) + imcb_log( sb->ic, "Received invitation ACCEPT message for unknown invitation (already aborted?)" ); + else if( file->sending ) + msn_invitations_accept( msn_file, sb, handle, cookie, body, blen ); + else + msn_invitationr_accept( msn_file, sb, handle, cookie, body, blen ); +} + +void msn_invitation_cancel( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle ); + + if( !msn_file ) + imcb_log( sb->ic, "Received invitation CANCEL message for unknown invitation (already aborted?)" ); + else + msn_ftp_abort( msn_file->dcc, msn_findheader( body, "Cancel-Code:", blen ) ); +} + +int msn_ftp_write( file_transfer_t *file, char *format, ... ) +{ + msn_filetransfer_t *msn_file = file->data; + va_list params; + int st; + char *s; + + va_start( params, format ); + s = g_strdup_vprintf( format, params ); + va_end( params ); + + st = write( msn_file->fd, s, strlen( s ) ); + if( st != strlen( s ) ) + return msn_ftp_abort( file, "Error sending data over MSNFTP connection: %s", + strerror( errno ) ); + + g_free( s ); + return 1; +} + +gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + debug( "Connected to MSNFTP server, starting authentication" ); + if( !msn_ftp_write( file, "VER MSNFTP\r\n" ) ) + return FALSE; + + sock_make_nonblocking( msn_file->fd ); + msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file ); + + return FALSE; +} + +gboolean msn_ftp_handle_command( file_transfer_t *file, char* line ) +{ + msn_filetransfer_t *msn_file = file->data; + char **cmd = msn_linesplit( line ); + int count = 0; + if( cmd[0] ) while( cmd[++count] ); + + if( count < 1 ) + return msn_ftp_abort( file, "Missing command in MSNFTP communication" ); + + if( strcmp( cmd[0], "VER" ) == 0 ) { + if( strcmp( cmd[1], "MSNFTP" ) != 0 ) + return msn_ftp_abort( file, "Unsupported filetransfer protocol: %s", cmd[1] ); + if( file->sending ) + msn_ftp_write( file, "VER MSNFTP\r\n" ); + else + msn_ftp_write( file, "USR %s %u\r\n", msn_file->md->ic->acc->user, msn_file->auth_cookie ); + } else if( strcmp( cmd[0], "FIL" ) == 0 ) { + if( strtoul( cmd[1], NULL, 10 ) != file->file_size ) + return msn_ftp_abort( file, "FIL reply contains a different file size than the size in the invitation" ); + msn_ftp_write( file, "TFR\r\n" ); + msn_file->status |= MSN_TRANSFER_RECEIVING; + } else if( strcmp( cmd[0], "USR" ) == 0 ) { + if( ( strcmp( cmd[1], msn_file->handle ) != 0 ) || + ( strtoul( cmd[2], NULL, 10 ) != msn_file->auth_cookie ) ) + msn_ftp_abort( file, "Authentication failed. " + "Expected handle: %s (got %s), cookie: %u (got %s)", + msn_file->handle, cmd[1], + msn_file->auth_cookie, cmd[2] ); + msn_ftp_write( file, "FIL %zu\r\n", file->file_size); + } else if( strcmp( cmd[0], "TFR" ) == 0 ) { + file->write_request( file ); + } else if( strcmp( cmd[0], "BYE" ) == 0 ) { + unsigned int retcode = count > 1 ? atoi(cmd[1]) : 1; + + if( ( retcode==16777989 ) || ( retcode==16777987 ) ) + imcb_file_finished( file ); + else if( retcode==2147942405 ) + imcb_file_canceled( file, "Failure: receiver is out of disk space" ); + else if( retcode==2164261682 ) + imcb_file_canceled( file, "Failure: receiver cancelled the transfer" ); + else if( retcode==2164261683 ) + imcb_file_canceled( file, "Failure: sender has cancelled the transfer" ); + else if( retcode==2164261694 ) + imcb_file_canceled( file, "Failure: connection is blocked" ); + else { + char buf[128]; + + sprintf( buf, "Failure: unknown BYE code: %d", retcode); + imcb_file_canceled( file, buf ); + } + } else if( strcmp( cmd[0], "CCL" ) == 0 ) { + imcb_file_canceled( file, "Failure: receiver cancelled the transfer" ); + } else { + msn_ftp_abort( file, "Received invalid command %s from msn client", cmd[0] ); + } + return TRUE; +} + +gboolean msn_ftp_send( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + msn_file->w_event_id = 0; + + file->write_request( file ); + + return FALSE; +} + +/* + * This should only be called if we can write, so just do it. + * Add a write watch so we can write more during the next cycle (if possible). + * This got a bit complicated because (at least) amsn expects packets of size 2045. + */ +gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len ) +{ + msn_filetransfer_t *msn_file = file->data; + int ret, overflow; + + /* what we can't send now */ + overflow = msn_file->sbufpos + len - MSNFTP_PSIZE; + + /* append what we can do the send buffer */ + memcpy( msn_file->sbuf + msn_file->sbufpos, buffer, MIN( len, MSNFTP_PSIZE - msn_file->sbufpos ) ); + msn_file->sbufpos += MIN( len, MSNFTP_PSIZE - msn_file->sbufpos ); + + /* if we don't have enough for a full packet and there's more wait for it */ + if( ( msn_file->sbufpos < MSNFTP_PSIZE ) && + ( msn_file->data_sent + msn_file->sbufpos - 3 < file->file_size ) ) { + if( !msn_file->w_event_id ) + msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file ); + return TRUE; + } + + /* Accumulated enough data, lets send something out */ + + msn_file->sbuf[0] = 0; + msn_file->sbuf[1] = ( msn_file->sbufpos - 3 ) & 0xff; + msn_file->sbuf[2] = ( ( msn_file->sbufpos - 3 ) >> 8 ) & 0xff; + + ASSERTSOCKOP( ret = send( msn_file->fd, msn_file->sbuf, msn_file->sbufpos, 0 ), "Sending" ); + + msn_file->data_sent += ret - 3; + + /* TODO: this should really not be fatal */ + if( ret < msn_file->sbufpos ) + return msn_ftp_abort( file, "send() sent %d instead of %d (send buffer full!)", ret, msn_file->sbufpos ); + + msn_file->sbufpos = 3; + + if( overflow > 0 ) { + while( overflow > ( MSNFTP_PSIZE - 3 ) ) { + if( !msn_ftps_write( file, buffer + len - overflow, MSNFTP_PSIZE - 3 ) ) + return FALSE; + overflow -= MSNFTP_PSIZE - 3; + } + return msn_ftps_write( file, buffer + len - overflow, overflow ); + } + + if( msn_file->data_sent == file->file_size ) { + if( msn_file->w_event_id ) { + b_event_remove( msn_file->w_event_id ); + msn_file->w_event_id = 0; + } + } else { + /* we might already be listening if this is data from an overflow */ + if( !msn_file->w_event_id ) + msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file ); + } + + return TRUE; +} + +/* Binary part of the file transfer protocol */ +gboolean msn_ftpr_read( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + int st; + unsigned char buf[3]; + + if( msn_file->data_remaining ) { + msn_file->r_event_id = 0; + + ASSERTSOCKOP( st = read( msn_file->fd, file->buffer, MIN( sizeof( file->buffer ), msn_file->data_remaining ) ), "Receiving" ); + + if( st == 0 ) + return msn_ftp_abort( file, "Remote end closed connection"); + + msn_file->data_sent += st; + + msn_file->data_remaining -= st; + + file->write( file, file->buffer, st ); + + if( msn_file->data_sent >= file->file_size ) + imcb_file_finished( file ); + + return FALSE; + } else { + ASSERTSOCKOP( st = read( msn_file->fd, buf, 1 ), "Receiving" ); + if( st == 0 ) { + return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" ); + } else if( buf[0] == '\r' || buf[0] == '\n' ) { + debug( "Discarding extraneous newline" ); + } else if( buf[0] != 0 ) { + msn_ftp_abort( file, "Remote end canceled the transfer"); + /* don't really care about these last 2 (should be 0,0) */ + read( msn_file->fd, buf, 2 ); + return FALSE; + } else { + unsigned int size; + ASSERTSOCKOP( st = read( msn_file->fd, buf, 2 ), "Receiving" ); + if( st < 2 ) + return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" ); + + size = buf[0] + ((unsigned int) buf[1] << 8); + msn_file->data_remaining = size; + } + } + return TRUE; +} + +/* Text mode part of the file transfer protocol */ +gboolean msn_ftp_txtproto( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + int i = msn_file->tbufpos, st; + char *tbuf = msn_file->tbuf; + + ASSERTSOCKOP( st = read( msn_file->fd, + tbuf + msn_file->tbufpos, + sizeof( msn_file->tbuf ) - msn_file->tbufpos ), + "Receiving" ); + + if( st == 0 ) + return msn_ftp_abort( file, "read returned EOF while reading text from msn client" ); + + msn_file->tbufpos += st; + + do { + for( ;i < msn_file->tbufpos; i++ ) { + if( tbuf[i] == '\n' || tbuf[i] == '\r' ) { + tbuf[i] = '\0'; + if( i > 0 ) + msn_ftp_handle_command( file, tbuf ); + else + while( tbuf[i] == '\n' || tbuf[i] == '\r' ) i++; + memmove( tbuf, tbuf + i + 1, msn_file->tbufpos - i - 1 ); + msn_file->tbufpos -= i + 1; + i = 0; + break; + } + } + } while ( i < msn_file->tbufpos ); + + if( msn_file->tbufpos == sizeof( msn_file->tbuf ) ) + return msn_ftp_abort( file, + "Line exceeded %d bytes in text protocol", + sizeof( msn_file->tbuf ) ); + return TRUE; +} + +gboolean msn_ftp_read( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + if( msn_file->status & MSN_TRANSFER_RECEIVING ) + return msn_ftpr_read( file ); + else + return msn_ftp_txtproto( file ); +} + +void msn_ftp_free( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + + if( msn_file->r_event_id ) + b_event_remove( msn_file->r_event_id ); + + if( msn_file->w_event_id ) + b_event_remove( msn_file->w_event_id ); + + if( msn_file->fd != -1 ) + closesocket( msn_file->fd ); + + msn_file->md->filetransfers = g_slist_remove( msn_file->md->filetransfers, msn_file->dcc ); + + g_free( msn_file->handle ); + + g_free( msn_file ); +} + +void msn_ftpr_accept( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + + msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, msn_file->invite_cookie, "ACCEPT", + "Launch-Application: FALSE\r\n" + "Request-Data: IP-Address:\r\n"); +} + +void msn_ftp_finished( file_transfer_t *file ) +{ + msn_ftp_write( file, "BYE 16777989\r\n" ); +} + +void msn_ftp_canceled( file_transfer_t *file, char *reason ) +{ + msn_filetransfer_t *msn_file = file->data; + + msn_ftp_cancel_invite( msn_file->md->ic, msn_file->handle, + msn_file->invite_cookie, + file->status & FT_STATUS_TRANSFERRING ? + "FTTIMEOUT" : + "FAIL" ); + + imcb_log( msn_file->md->ic, "File transfer aborted: %s", reason ); +} + +gboolean msn_ftpr_write_request( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + if( msn_file->r_event_id != 0 ) { + msn_ftp_abort( file, + "BUG in MSN file transfer:" + "write_request called when" + "already watching for input" ); + return FALSE; + } + + msn_file->r_event_id = + b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file ); + + return TRUE; +} diff --git a/protocols/msn/invitation.h b/protocols/msn/invitation.h new file mode 100644 index 00000000..289efd7b --- /dev/null +++ b/protocols/msn/invitation.h @@ -0,0 +1,82 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2006 Marijn Kruisselbrink and others * +\********************************************************************/ + +/* MSN module - File transfer support */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MSN_INVITATION_H +#define _MSN_INVITATION_H + +#include "msn.h" + +#define MSN_INVITE_HEADERS "MIME-Version: 1.0\r\n" \ + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" \ + "\r\n" + +#define MSNFTP_PSIZE 2048 + +typedef enum { + MSN_TRANSFER_RECEIVING = 1, + MSN_TRANSFER_SENDING = 2 +} msn_filetransfer_status_t; + +typedef struct msn_filetransfer +{ +/* Generic invitation data */ + /* msn_data instance this invitation was received with. */ + struct msn_data *md; + /* Cookie specifying this invitation. */ + unsigned int invite_cookie; + /* Handle of user that started this invitation. */ + char *handle; + +/* File transfer specific data */ + /* Current status of the file transfer. */ + msn_filetransfer_status_t status; + /* Pointer to the dcc structure for this transfer. */ + file_transfer_t *dcc; + /* Socket the transfer is taking place over. */ + int fd; + /* Cookie received in the original invitation, this must be sent as soon as + a connection has been established. */ + unsigned int auth_cookie; + /* Data remaining to be received in the current packet. */ + unsigned int data_remaining; + /* Buffer containing received, but unprocessed text. */ + char tbuf[256]; + unsigned int tbufpos; + + unsigned int data_sent; + + gint r_event_id; + gint w_event_id; + + unsigned char sbuf[2048]; + int sbufpos; + +} msn_filetransfer_t; + +void msn_invitation_invite( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); +void msn_invitation_accept( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); +void msn_invitation_cancel( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); + +#endif diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c index 8bf61aa1..29b7cecc 100644 --- a/protocols/msn/msn.c +++ b/protocols/msn/msn.c @@ -78,6 +78,12 @@ static void msn_logout( struct im_connection *ic ) if( md ) { + /** Disabling MSN ft support for now. + while( md->filetransfers ) { + imcb_file_canceled( md->filetransfers->data, "Closing connection" ); + } + */ + if( md->fd >= 0 ) closesocket( md->fd ); @@ -97,6 +103,15 @@ static void msn_logout( struct im_connection *ic ) g_free( md->grouplist[--md->groupcount] ); g_free( md->grouplist ); + while( md->grpq ) + { + struct msn_groupadd *ga = md->grpq->data; + g_free( ga->group ); + g_free( ga->who ); + g_free( ga ); + md->grpq = g_slist_remove( md->grpq, ga ); + } + g_free( md ); } @@ -115,6 +130,14 @@ static int msn_buddy_msg( struct im_connection *ic, char *who, char *message, in { struct msn_switchboard *sb; +#ifdef DEBUG + if( strcmp( who, "raw" ) == 0 ) + { + msn_write( ic, message, strlen( message ) ); + msn_write( ic, "\r\n", 2 ); + } + else +#endif if( ( sb = msn_sb_by_handle( ic, who ) ) ) { return( msn_sb_sendmessage( sb, message ) ); @@ -152,11 +175,10 @@ static void msn_set_away( struct im_connection *ic, char *state, char *message ) char buf[1024]; struct msn_data *md = ic->proto_data; - if( state ) - md->away_state = msn_away_state_by_name( state ) ? : - msn_away_state_list + 1; - else + if( state == NULL ) md->away_state = msn_away_state_list; + else if( ( md->away_state = msn_away_state_by_name( state ) ) == NULL ) + md->away_state = msn_away_state_list + 1; g_snprintf( buf, sizeof( buf ), "CHG %d %s\r\n", ++md->trId, md->away_state->code ); msn_write( ic, buf, strlen( buf ) ); @@ -175,12 +197,16 @@ static void msn_get_info(struct im_connection *ic, char *who) static void msn_add_buddy( struct im_connection *ic, char *who, char *group ) { - msn_buddy_list_add( ic, "FL", who, who ); + struct bee_user *bu = bee_user_by_handle( ic->bee, ic, who ); + + msn_buddy_list_add( ic, "FL", who, who, group ); + if( bu && bu->group ) + msn_buddy_list_remove( ic, "FL", who, bu->group->name ); } static void msn_remove_buddy( struct im_connection *ic, char *who, char *group ) { - msn_buddy_list_remove( ic, "FL", who ); + msn_buddy_list_remove( ic, "FL", who, NULL ); } static void msn_chat_msg( struct groupchat *c, char *message, int flags ) @@ -216,6 +242,7 @@ static void msn_chat_leave( struct groupchat *c ) static struct groupchat *msn_chat_with( struct im_connection *ic, char *who ) { struct msn_switchboard *sb; + struct groupchat *c = imcb_chat_new( ic, who ); if( ( sb = msn_sb_by_handle( ic, who ) ) ) { @@ -233,10 +260,8 @@ static struct groupchat *msn_chat_with( struct im_connection *ic, char *who ) msn_sb_write_msg( ic, m ); - return NULL; + return c; } - - return NULL; } static void msn_keepalive( struct im_connection *ic ) @@ -246,19 +271,19 @@ static void msn_keepalive( struct im_connection *ic ) static void msn_add_permit( struct im_connection *ic, char *who ) { - msn_buddy_list_add( ic, "AL", who, who ); + msn_buddy_list_add( ic, "AL", who, who, NULL ); } static void msn_rem_permit( struct im_connection *ic, char *who ) { - msn_buddy_list_remove( ic, "AL", who ); + msn_buddy_list_remove( ic, "AL", who, NULL ); } static void msn_add_deny( struct im_connection *ic, char *who ) { struct msn_switchboard *sb; - msn_buddy_list_add( ic, "BL", who, who ); + msn_buddy_list_add( ic, "BL", who, who, NULL ); /* If there's still a conversation with this person, close it. */ if( ( sb = msn_sb_by_handle( ic, who ) ) ) @@ -269,15 +294,19 @@ static void msn_add_deny( struct im_connection *ic, char *who ) static void msn_rem_deny( struct im_connection *ic, char *who ) { - msn_buddy_list_remove( ic, "BL", who ); + msn_buddy_list_remove( ic, "BL", who, NULL ); } static int msn_send_typing( struct im_connection *ic, char *who, int typing ) { - if( typing & OPT_TYPING ) + struct bee_user *bu = bee_user_by_handle( ic->bee, ic, who ); + + if( !( bu->flags & BEE_USER_ONLINE ) ) + return 0; + else if( typing & OPT_TYPING ) return( msn_buddy_msg( ic, who, TYPING_NOTIFICATION_MESSAGE, 0 ) ); else - return( 1 ); + return 1; } static char *set_eval_display_name( set_t *set, char *value ) @@ -328,6 +357,7 @@ void msn_initmodule() ret->rem_deny = msn_rem_deny; ret->send_typing = msn_send_typing; ret->handle_cmp = g_strcasecmp; + //ret->transfer_request = msn_ftp_transfer_request; register_protocol(ret); } diff --git a/protocols/msn/msn.h b/protocols/msn/msn.h index 83a080a3..31683cb5 100644 --- a/protocols/msn/msn.h +++ b/protocols/msn/msn.h @@ -69,10 +69,11 @@ struct msn_data int trId; - GSList *msgq; + GSList *msgq, *grpq; GSList *switchboards; int sb_failures; time_t first_sb_failure; + GSList *filetransfers; const struct msn_away_state *away_state; int buddycount; @@ -119,6 +120,12 @@ struct msn_message char *text; }; +struct msn_groupadd +{ + char *who; + char *group; +}; + struct msn_handler_data { int fd; @@ -158,8 +165,8 @@ gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond ); /* msn_util.c */ int msn_write( struct im_connection *ic, char *s, int len ); int msn_logged_in( struct im_connection *ic ); -int msn_buddy_list_add( struct im_connection *ic, char *list, char *who, char *realname ); -int msn_buddy_list_remove( struct im_connection *ic, char *list, char *who ); +int msn_buddy_list_add( struct im_connection *ic, const char *list, const char *who, const char *realname_, const char *group ); +int msn_buddy_list_remove( struct im_connection *ic, char *list, const char *who, const char *group ); void msn_buddy_ask( struct im_connection *ic, char *handle, char *realname ); char *msn_findheader( char *text, char *header, int len ); char **msn_linesplit( char *line ); @@ -188,4 +195,7 @@ int msn_sb_write_msg( struct im_connection *ic, struct msn_message *m ); void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial ); void msn_sb_stop_keepalives( struct msn_switchboard *sb ); +/* invitation.c */ +void msn_ftp_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ); + #endif //_MSN_H diff --git a/protocols/msn/msn_util.c b/protocols/msn/msn_util.c index 9c9d2720..23447403 100644 --- a/protocols/msn/msn_util.c +++ b/protocols/msn/msn_util.c @@ -50,32 +50,82 @@ int msn_logged_in( struct im_connection *ic ) return( 0 ); } -int msn_buddy_list_add( struct im_connection *ic, char *list, char *who, char *realname_ ) +int msn_buddy_list_add( struct im_connection *ic, const char *list, const char *who, const char *realname_, const char *group ) { struct msn_data *md = ic->proto_data; - char buf[1024], *realname; + char buf[1024], *realname, groupid[8]; - realname = msn_http_encode( realname_ ); - - g_snprintf( buf, sizeof( buf ), "ADD %d %s %s %s\r\n", ++md->trId, list, who, realname ); - if( msn_write( ic, buf, strlen( buf ) ) ) + *groupid = '\0'; + if( group ) { - g_free( realname ); + int i; + for( i = 0; i < md->groupcount; i ++ ) + if( g_strcasecmp( md->grouplist[i], group ) == 0 ) + { + g_snprintf( groupid, sizeof( groupid ), " %d", i ); + break; + } - return( 1 ); + if( *groupid == '\0' ) + { + /* Have to create this group, it doesn't exist yet. */ + struct msn_groupadd *ga; + GSList *l; + + for( l = md->grpq; l; l = l->next ) + { + ga = l->data; + if( g_strcasecmp( ga->group, group ) == 0 ) + break; + } + + ga = g_new0( struct msn_groupadd, 1 ); + ga->who = g_strdup( who ); + ga->group = g_strdup( group ); + md->grpq = g_slist_prepend( md->grpq, ga ); + + if( l == NULL ) + { + char *groupname = msn_http_encode( group ); + g_snprintf( buf, sizeof( buf ), "ADG %d %s %d\r\n", ++md->trId, groupname, 0 ); + g_free( groupname ); + return msn_write( ic, buf, strlen( buf ) ); + } + else + { + /* This can happen if the user's doing lots of adds to a + new group at once; we're still waiting for the server + to confirm group creation. */ + return 1; + } + } } + realname = msn_http_encode( realname_ ); + g_snprintf( buf, sizeof( buf ), "ADD %d %s %s %s%s\r\n", ++md->trId, list, who, realname, groupid ); g_free( realname ); - return( 0 ); + return msn_write( ic, buf, strlen( buf ) ); } -int msn_buddy_list_remove( struct im_connection *ic, char *list, char *who ) +int msn_buddy_list_remove( struct im_connection *ic, char *list, const char *who, const char *group ) { struct msn_data *md = ic->proto_data; - char buf[1024]; + char buf[1024], groupid[8]; + + *groupid = '\0'; + if( group ) + { + int i; + for( i = 0; i < md->groupcount; i ++ ) + if( g_strcasecmp( md->grouplist[i], group ) == 0 ) + { + g_snprintf( groupid, sizeof( groupid ), " %d", i ); + break; + } + } - g_snprintf( buf, sizeof( buf ), "REM %d %s %s\r\n", ++md->trId, list, who ); + g_snprintf( buf, sizeof( buf ), "REM %d %s %s%s\r\n", ++md->trId, list, who, groupid ); if( msn_write( ic, buf, strlen( buf ) ) ) return( 1 ); @@ -93,10 +143,9 @@ static void msn_buddy_ask_yes( void *data ) { struct msn_buddy_ask_data *bla = data; - msn_buddy_list_add( bla->ic, "AL", bla->handle, bla->realname ); + msn_buddy_list_add( bla->ic, "AL", bla->handle, bla->realname, NULL ); - if( imcb_find_buddy( bla->ic, bla->handle ) == NULL ) - imcb_ask_add( bla->ic, bla->handle, NULL ); + imcb_ask_add( bla->ic, bla->handle, NULL ); g_free( bla->handle ); g_free( bla->realname ); @@ -107,7 +156,7 @@ static void msn_buddy_ask_no( void *data ) { struct msn_buddy_ask_data *bla = data; - msn_buddy_list_add( bla->ic, "BL", bla->handle, bla->realname ); + msn_buddy_list_add( bla->ic, "BL", bla->handle, bla->realname, NULL ); g_free( bla->handle ); g_free( bla->realname ); @@ -348,6 +397,7 @@ void msn_msgq_purge( struct im_connection *ic, GSList **list ) struct msn_message *m; GString *ret; GSList *l; + int n = 0; l = *list; if( l == NULL ) @@ -362,7 +412,11 @@ void msn_msgq_purge( struct im_connection *ic, GSList **list ) { m = l->data; - g_string_append_printf( ret, "\n%s", m->text ); + if( strncmp( m->text, "\r\r\r", 3 ) != 0 ) + { + g_string_append_printf( ret, "\n%s", m->text ); + n ++; + } g_free( m->who ); g_free( m->text ); @@ -373,7 +427,8 @@ void msn_msgq_purge( struct im_connection *ic, GSList **list ) g_slist_free( *list ); *list = NULL; - imcb_log( ic, "%s", ret->str ); + if( n > 0 ) + imcb_log( ic, "%s", ret->str ); g_string_free( ret, TRUE ); } diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c index ca3c38e2..0be9e727 100644 --- a/protocols/msn/ns.c +++ b/protocols/msn/ns.c @@ -75,7 +75,7 @@ gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond ) g_snprintf( s, sizeof( s ), "VER %d MSNP8 CVR0\r\n", ++md->trId ); if( msn_write( ic, s, strlen( s ) ) ) { - ic->inpa = b_input_add( md->fd, GAIM_INPUT_READ, msn_ns_callback, ic ); + ic->inpa = b_input_add( md->fd, B_EV_IO_READ, msn_ns_callback, ic ); imcb_log( ic, "Connected to server, waiting for reply" ); } @@ -270,11 +270,25 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) { if( num_parts == 5 ) { - md->buddycount = atoi( cmd[3] ); - md->groupcount = atoi( cmd[4] ); - if( md->groupcount > 0 ) + int i, groupcount; + + groupcount = atoi( cmd[4] ); + if( groupcount > 0 ) + { + /* valgrind says this is leaking memory, I'm guessing + that this happens during server redirects. */ + if( md->grouplist ) + { + for( i = 0; i < md->groupcount; i ++ ) + g_free( md->grouplist[i] ); + g_free( md->grouplist ); + } + + md->groupcount = groupcount; md->grouplist = g_new0( char *, md->groupcount ); + } + md->buddycount = atoi( cmd[3] ); if( !*cmd[3] || md->buddycount == 0 ) msn_logged_in( ic ); } @@ -518,8 +532,14 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) } else if( num_parts >= 6 && strcmp( cmd[2], "FL" ) == 0 ) { + const char *group = NULL; + int num; + + if( cmd[6] != NULL && sscanf( cmd[6], "%d", &num ) == 1 && num < md->groupcount ) + group = md->grouplist[num]; + http_decode( cmd[5] ); - imcb_add_buddy( ic, cmd[4], NULL ); + imcb_add_buddy( ic, cmd[4], group ); imcb_rename_buddy( ic, cmd[4], cmd[5] ); } } @@ -591,6 +611,50 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) return( 0 ); } } + else if( strcmp( cmd[0], "ADG" ) == 0 ) + { + char *group = g_strdup( cmd[3] ); + int groupnum, i; + GSList *l, *next; + + http_decode( group ); + if( sscanf( cmd[4], "%d", &groupnum ) == 1 ) + { + if( groupnum >= md->groupcount ) + { + md->grouplist = g_renew( char *, md->grouplist, groupnum + 1 ); + for( i = md->groupcount; i <= groupnum; i ++ ) + md->grouplist[i] = NULL; + md->groupcount = groupnum + 1; + } + g_free( md->grouplist[groupnum] ); + md->grouplist[groupnum] = group; + } + else + { + /* Shouldn't happen, but if it does, give up on the group. */ + g_free( group ); + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return 0; + } + + for( l = md->grpq; l; l = next ) + { + struct msn_groupadd *ga = l->data; + next = l->next; + if( g_strcasecmp( ga->group, group ) == 0 ) + { + if( !msn_buddy_list_add( ic, "FL", ga->who, ga->who, group ) ) + return 0; + + g_free( ga->group ); + g_free( ga->who ); + g_free( ga ); + md->grpq = g_slist_remove( md->grpq, ga ); + } + } + } else if( isdigit( cmd[0][0] ) ) { int num = atoi( cmd[0] ); diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c index 49eed601..cb5789b8 100644 --- a/protocols/msn/sb.c +++ b/protocols/msn/sb.c @@ -28,6 +28,7 @@ #include "msn.h" #include "passport.h" #include "md5.h" +#include "invitation.h" static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond ); static int msn_sb_command( gpointer data, char **cmd, int num_parts ); @@ -178,6 +179,11 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) buf = g_strdup( SB_KEEPALIVE_HEADERS ); i = strlen( buf ); } + else if( strncmp( text, MSN_INVITE_HEADERS, sizeof( MSN_INVITE_HEADERS ) - 1 ) == 0 ) + { + buf = g_strdup( text ); + i = strlen( buf ); + } else { buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 + 1 ); @@ -226,11 +232,17 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) struct groupchat *msn_sb_to_chat( struct msn_switchboard *sb ) { struct im_connection *ic = sb->ic; + struct groupchat *c = NULL; char buf[1024]; /* Create the groupchat structure. */ g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session ); - sb->chat = imcb_chat_new( ic, buf ); + if( sb->who ) + c = bee_chat_by_title( ic->bee, ic, sb->who ); + if( c && !msn_sb_by_chat( c ) ) + sb->chat = c; + else + sb->chat = imcb_chat_new( ic, buf ); /* Populate the channel. */ if( sb->who ) imcb_chat_add_buddy( sb->chat, sb->who ); @@ -314,7 +326,7 @@ gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond ) g_snprintf( buf, sizeof( buf ), "ANS %d %s %s %d\r\n", ++sb->trId, ic->acc->user, sb->key, sb->session ); if( msn_sb_write( sb, buf, strlen( buf ) ) ) - sb->inp = b_input_add( sb->fd, GAIM_INPUT_READ, msn_sb_callback, sb ); + sb->inp = b_input_add( sb->fd, B_EV_IO_READ, msn_sb_callback, sb ); else debug( "Error %d while connecting to switchboard server", 2 ); @@ -691,64 +703,46 @@ static int msn_sb_message( gpointer data, char *msg, int msglen, char **cmd, int /* PANIC! */ } } +#if 0 + // Disable MSN ft support for now. else if( g_strncasecmp( ct, "text/x-msmsgsinvite", 19 ) == 0 ) { - char *itype = msn_findheader( body, "Application-GUID:", blen ); - char buf[1024]; + char *command = msn_findheader( body, "Invitation-Command:", blen ); + char *cookie = msn_findheader( body, "Invitation-Cookie:", blen ); + unsigned int icookie; 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 ); + /* Every invite should have both a Command and Cookie header */ + if( !command || !cookie ) { + g_free( command ); + g_free( cookie ); + imcb_log( ic, "Warning: No command or cookie from %s", sb->who ); + return 1; } - g_free( itype ); - - if( !*buf ) - return( 1 ); + icookie = strtoul( cookie, NULL, 10 ); + g_free( cookie ); - if( sb->who ) - { - imcb_buddy_msg( ic, cmd[1], buf, 0, 0 ); - } - else if( sb->chat ) - { - imcb_chat_msg( sb->chat, cmd[1], buf, 0, 0 ); - } - else - { - /* PANIC! */ + if( g_strncasecmp( command, "INVITE", 6 ) == 0 ) { + msn_invitation_invite( sb, cmd[1], icookie, body, blen ); + } else if( g_strncasecmp( command, "ACCEPT", 6 ) == 0 ) { + msn_invitation_accept( sb, cmd[1], icookie, body, blen ); + } else if( g_strncasecmp( command, "CANCEL", 6 ) == 0 ) { + msn_invitation_cancel( sb, cmd[1], icookie, body, blen ); + } else { + imcb_log( ic, "Warning: Received invalid invitation with " + "command %s from %s", command, sb->who ); } + + g_free( command ); + } +#endif + else if( g_strncasecmp( ct, "application/x-msnmsgrp2p", 24 ) == 0 ) + { + imcb_error( sb->ic, "Cannot receive file from %s: BitlBee does not " + "support msnmsgrp2p yet.", sb->who ); + g_free( ct ); } else if( g_strncasecmp( ct, "text/x-msmsgscontrol", 20 ) == 0 ) { @@ -779,10 +773,11 @@ static gboolean msn_sb_keepalive( gpointer data, gint source, b_input_condition void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial ) { - struct buddy *b; + bee_user_t *bu; if( sb && sb->who && sb->keepalive == 0 && - ( b = imcb_find_buddy( sb->ic, sb->who ) ) && !b->present && + ( bu = bee_user_by_handle( sb->ic->bee, sb->ic, sb->who ) ) && + !( bu->flags & BEE_USER_ONLINE ) && set_getbool( &sb->ic->acc->set, "switchboard_keepalives" ) ) { if( initial ) |