/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* MSN module - Switchboard server callbacks and utilities */ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License with the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include "nogaim.h" #include "msn.h" #include "md5.h" #include "soap.h" #include "invitation.h" static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond ); static int msn_sb_command( struct msn_handler_data *handler, char **cmd, int num_parts ); static int msn_sb_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts ); int msn_sb_write( struct msn_switchboard *sb, const char *fmt, ... ) { va_list params; char *out; size_t len; int st; va_start( params, fmt ); out = g_strdup_vprintf( fmt, params ); va_end( params ); if( getenv( "BITLBEE_DEBUG" ) ) fprintf( stderr, "->SB%d:%s\n", sb->fd, out ); len = strlen( out ); st = write( sb->fd, out, len ); g_free( out ); if( st != len ) { msn_sb_destroy( sb ); return 0; } return 1; } int msn_sb_write_msg( struct im_connection *ic, struct msn_message *m ) { struct msn_data *md = ic->proto_data; struct msn_switchboard *sb; /* FIXME: *CHECK* the reliability of using spare sb's! */ if( ( sb = msn_sb_spare( ic ) ) ) { debug( "Trying to use a spare switchboard to message %s", m->who ); sb->who = g_strdup( m->who ); if( msn_sb_write( sb, "CAL %d %s\r\n", ++sb->trId, m->who ) ) { /* He/She should join the switchboard soon, let's queue the message. */ sb->msgq = g_slist_append( sb->msgq, m ); return( 1 ); } } debug( "Creating a new switchboard to message %s", m->who ); /* If we reach this line, there was no spare switchboard, so let's make one. */ if( !msn_ns_write( ic, -1, "XFR %d SB\r\n", ++md->trId ) ) { g_free( m->who ); g_free( m->text ); g_free( m ); return( 0 ); } /* And queue the message to md. We'll pick it up when the switchboard comes up. */ md->msgq = g_slist_append( md->msgq, m ); /* FIXME: If the switchboard creation fails, the message will not be sent. */ return( 1 ); } struct msn_switchboard *msn_sb_create( struct im_connection *ic, char *host, int port, char *key, int session ) { struct msn_data *md = ic->proto_data; struct msn_switchboard *sb = g_new0( struct msn_switchboard, 1 ); sb->fd = proxy_connect( host, port, msn_sb_connected, sb ); if( sb->fd < 0 ) { g_free( sb ); return( NULL ); } sb->ic = ic; sb->key = g_strdup( key ); sb->session = session; msn_switchboards = g_slist_append( msn_switchboards, sb ); md->switchboards = g_slist_append( md->switchboards, sb ); return( sb ); } struct msn_switchboard *msn_sb_by_handle( struct im_connection *ic, const char *handle ) { struct msn_data *md = ic->proto_data; struct msn_switchboard *sb; GSList *l; for( l = md->switchboards; l; l = l->next ) { sb = l->data; if( sb->who && strcmp( sb->who, handle ) == 0 ) return( sb ); } return( NULL ); } struct msn_switchboard *msn_sb_by_chat( struct groupchat *c ) { struct msn_data *md = c->ic->proto_data; struct msn_switchboard *sb; GSList *l; for( l = md->switchboards; l; l = l->next ) { sb = l->data; if( sb->chat == c ) return( sb ); } return( NULL ); } struct msn_switchboard *msn_sb_spare( struct im_connection *ic ) { struct msn_data *md = ic->proto_data; struct msn_switchboard *sb; GSList *l; for( l = md->switchboards; l; l = l->next ) { sb = l->data; if( !sb->who && !sb->chat ) return( sb ); } return( NULL ); } int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) { if( sb->ready
#ifndef __OSCAR_INFO_H__
#define __OSCAR_INFO_H__

#define AIM_CB_FAM_LOC 0x0002

/*
 * SNAC Family: Location Services.
 */ 
#define AIM_CB_LOC_ERROR 0x0001
#define AIM_CB_LOC_REQRIGHTS 0x0002
#define AIM_CB_LOC_RIGHTSINFO 0x0003
#define AIM_CB_LOC_SETUSERINFO 0x0004
#define AIM_CB_LOC_REQUSERINFO 0x0005
#define AIM_CB_LOC_USERINFO 0x0006
#define AIM_CB_LOC_WATCHERSUBREQ 0x0007
#define AIM_CB_LOC_WATCHERNOT 0x0008
#define AIM_CB_LOC_DEFAULT 0xffff

#define AIM_CAPS_BUDDYICON      0x00000001
#define AIM_CAPS_VOICE          0x00000002
#define AIM_CAPS_IMIMAGE        0x00000004
#define AIM_CAPS_CHAT           0x00000008
#define AIM_CAPS_GETFILE        0x00000010
#define AIM_CAPS_SENDFILE       0x00000020
#define AIM_CAPS_GAMES          0x00000040
#define AIM_CAPS_SAVESTOCKS     0x00000080
#define AIM_CAPS_SENDBUDDYLIST  0x00000100
#define AIM_CAPS_GAMES2         0x00000200
#define AIM_CAPS_ICQ            0x00000400
#define AIM_CAPS_APINFO         0x00000800
#define AIM_CAPS_ICQRTF	        0x00001000
#define AIM_CAPS_EMPTY	        0x00002000
#define AIM_CAPS_ICQSERVERRELAY 0x00004000
#define AIM_CAPS_ICQUNKNOWN     0x00008000
#define AIM_CAPS_TRILLIANCRYPT  0x00010000
#define AIM_CAPS_UTF8           0x00020000
#define AIM_CAPS_INTEROP        0x00040000
#define AIM_CAPS_ICHAT          0x00080000
#define AIM_CAPS_EXTCHAN2       0x00100000
#define AIM_CAPS_LAST           0x00200000

#endif /* __OSCAR_INFO_H__ */
OI" ) == 0 ) { if( num_parts < 3 ) { msn_sb_destroy( sb ); return( 0 ); } /* See IRO above. Handle "bare JIDs" only. */ if( strchr( cmd[1], ';' ) ) return 1; if( sb->who && g_strcasecmp( cmd[1], sb->who ) == 0 ) { /* The user we wanted to talk to is finally there, let's send the queued messages then. */ struct msn_message *m; GSList *l; int st = 1; debug( "%s arrived in the switchboard session, now sending queued message(s)", cmd[1] ); /* Without this, sendmessage() will put everything back on the queue... */ sb->ready = 1; while( ( l = sb->msgq ) ) { m = l->data; if( st ) { /* This hack is meant to convert a regular new chat into a groupchat */ if( strcmp( m->text, GROUPCHAT_SWITCHBOARD_MESSAGE ) == 0 ) msn_sb_to_chat( sb ); else st = msn_sb_sendmessage( sb, m->text ); } g_free( m->text ); g_free( m->who ); g_free( m ); sb->msgq = g_slist_remove( sb->msgq, m ); } msn_sb_start_keepalives( sb, FALSE ); return( st ); } else if( strcmp( cmd[1], ic->acc->user ) == 0 ) { /* Well, gee thanks. Thanks for letting me know I've arrived.. */ } else if( sb->who ) { debug( "Converting chat with %s to a groupchat because %s joined the session.", sb->who, cmd[1] ); /* This SB is a one-to-one chat right now, but someone else is joining. */ msn_sb_to_chat( sb ); imcb_chat_add_buddy( sb->chat, cmd[1] ); } else if( sb->chat ) { imcb_chat_add_buddy( sb->chat, cmd[1] ); sb->ready = 1; } else { /* PANIC! */ } } else if( strcmp( cmd[0], "MSG" ) == 0 ) { if( num_parts < 4 ) { msn_sb_destroy( sb ); return( 0 ); } sb->handler->msglen = atoi( cmd[3] ); if( sb->handler->msglen <= 0 ) { debug( "Received a corrupted message on the switchboard, the switchboard will be closed" ); msn_sb_destroy( sb ); return( 0 ); } } else if( strcmp( cmd[0], "NAK" ) == 0 ) { if( sb->who ) { imcb_log( ic, "The MSN servers could not deliver one of your messages to %s.", sb->who ); } else { imcb_log( ic, "The MSN servers could not deliver one of your groupchat messages to all participants." ); } } else if( strcmp( cmd[0], "BYE" ) == 0 ) { if( num_parts < 2 ) { msn_sb_destroy( sb ); return( 0 ); } /* if( cmd[2] && *cmd[2] == '1' ) -=> Chat is being cleaned up because of idleness */ if( sb->who ) { msn_sb_stop_keepalives( sb ); /* This is a single-person chat, and the other person is leaving. */ g_free( sb->who ); sb->who = NULL; sb->ready = 0; debug( "Person %s left the one-to-one switchboard connection. Keeping it around as a spare...", cmd[1] ); /* We could clean up the switchboard now, but keeping it around as a spare for a next conversation sounds more sane to me. The server will clean it up when it's idle for too long. */ } else if( sb->chat && !strchr( cmd[1], ';' ) ) { imcb_chat_remove_buddy( sb->chat, cmd[1], "" ); } else { /* PANIC! */ } } else if( isdigit( cmd[0][0] ) ) { int num = atoi( cmd[0] ); const struct msn_status_code *err = msn_status_by_number( num ); /* If the person is offline, send an offline message instead, and don't report an error. */ if( num == 217 ) msn_ns_oim_send_queue( ic, &sb->msgq ); else imcb_error( ic, "Error reported by switchboard server: %s", err->text ); if( err->flags & STATUS_SB_FATAL ) { msn_sb_destroy( sb ); return 0; } else if( err->flags & STATUS_FATAL ) { imc_logout( ic, TRUE ); return 0; } else if( err->flags & STATUS_SB_IM_SPARE ) { if( sb->who ) { /* Apparently some invitation failed. We might want to use this board later, so keep it as a spare. */ g_free( sb->who ); sb->who = NULL; /* Also clear the msgq, otherwise someone else might get them. */ msn_msgq_purge( ic, &sb->msgq ); } /* Do NOT return 0 here, we want to keep this sb. */ } } else { /* debug( "Received unknown command from switchboard server: %s", cmd[0] ); */ } return( 1 ); } static int msn_sb_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts ) { struct msn_switchboard *sb = handler->data; struct im_connection *ic = sb->ic; char *body; if( !num_parts ) return( 1 ); if( ( body = strstr( msg, "\r\n\r\n" ) ) ) body += 4; if( strcmp( cmd[0], "MSG" ) == 0 ) { char *ct = get_rfc822_header( msg, "Content-Type:", msglen ); if( !ct ) return( 1 ); if( g_strncasecmp( ct, "text/plain", 10 ) == 0 ) { g_free( ct ); if( !body ) return( 1 ); if( sb->who ) { imcb_buddy_msg( ic, cmd[1], body, 0, 0 ); } else if( sb->chat ) { imcb_chat_msg( sb->chat, cmd[1], body, 0, 0 ); } else { /* PANIC! */ } } #if 0 // Disable MSN ft support for now. else if( g_strncasecmp( ct, "text/x-msmsgsinvite", 19 ) == 0 ) { char *command = get_rfc822_header( body, "Invitation-Command:", blen ); char *cookie = get_rfc822_header( body, "Invitation-Cookie:", blen ); unsigned int icookie; g_free( ct ); /* Every invite should have both a Command and Cookie header */ if( !command || !cookie ) { g_free( command ); g_free( cookie ); imcb_log( ic, "Warning: No command or cookie from %s", sb->who ); return 1; } icookie = strtoul( cookie, NULL, 10 ); g_free( cookie ); if( g_strncasecmp( command, "INVITE", 6 ) == 0 ) { msn_invitation_invite( sb, cmd[1], icookie, body, blen ); } else if( g_strncasecmp( command, "ACCEPT", 6 ) == 0 ) { msn_invitation_accept( sb, cmd[1], icookie, body, blen ); } else if( g_strncasecmp( command, "CANCEL", 6 ) == 0 ) { msn_invitation_cancel( sb, cmd[1], icookie, body, blen ); } else { imcb_log( ic, "Warning: Received invalid invitation with " "command %s from %s", command, sb->who ); } g_free( command ); } #endif else if( g_strncasecmp( ct, "application/x-msnmsgrp2p", 24 ) == 0 ) { /* Not currently implemented. Don't warn about it since this seems to be used for avatars now. */ g_free( ct ); } else if( g_strncasecmp( ct, "text/x-msmsgscontrol", 20 ) == 0 ) { char *who = get_rfc822_header( msg, "TypingUser:", msglen ); if( who ) { imcb_buddy_typing( ic, who, OPT_TYPING ); g_free( who ); } g_free( ct ); } else { g_free( ct ); } } return( 1 ); } static gboolean msn_sb_keepalive( gpointer data, gint source, b_input_condition cond ) { struct msn_switchboard *sb = data; return sb->ready && msn_sb_sendmessage( sb, SB_KEEPALIVE_MESSAGE ); } void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial ) { bee_user_t *bu; if( sb && sb->who && sb->keepalive == 0 && ( bu = bee_user_by_handle( sb->ic->bee, sb->ic, sb->who ) ) && !( bu->flags & BEE_USER_ONLINE ) && set_getbool( &sb->ic->acc->set, "switchboard_keepalives" ) ) { if( initial ) msn_sb_keepalive( sb, 0, 0 ); sb->keepalive = b_timeout_add( 20000, msn_sb_keepalive, sb ); } } void msn_sb_stop_keepalives( struct msn_switchboard *sb ) { if( sb && sb->keepalive > 0 ) { b_event_remove( sb->keepalive ); sb->keepalive = 0; } }