diff options
Diffstat (limited to 'protocols/msn/sb.c')
| -rw-r--r-- | protocols/msn/sb.c | 806 | 
1 files changed, 806 insertions, 0 deletions
| diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c new file mode 100644 index 00000000..37ac2889 --- /dev/null +++ b/protocols/msn/sb.c @@ -0,0 +1,806 @@ +  /********************************************************************\ +  * BitlBee -- An IRC to other IM-networks gateway                     * +  *                                                                    * +  * Copyright 2002-2010 Wilmer van der Gaast and others                * +  \********************************************************************/ + +/* MSN module - Switchboard server callbacks and utilities              */ + +/* +  This program is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as published by +  the Free Software Foundation; either version 2 of the License, or +  (at your option) any later version. + +  This program is distributed in the hope that it will be useful, +  but WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License with +  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; +  if not, write to the Free Software Foundation, Inc., 59 Temple Place, +  Suite 330, Boston, MA  02111-1307  USA +*/ + +#include <ctype.h> +#include "nogaim.h" +#include "msn.h" +#include "md5.h" +#include "soap.h" +#include "invitation.h" + +static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond ); +static int msn_sb_command( struct msn_handler_data *handler, char **cmd, int num_parts ); +static int msn_sb_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts ); + +int msn_sb_write( struct msn_switchboard *sb, const char *fmt, ... ) +{ +	va_list params; +	char *out; +	size_t len; +	int st; +	 +	va_start( params, fmt ); +	out = g_strdup_vprintf( fmt, params ); +	va_end( params ); +	 +	if( getenv( "BITLBEE_DEBUG" ) ) +		fprintf( stderr, "->SB%d:%s", sb->fd, out ); +	 +	len = strlen( out ); +	st = write( sb->fd, out, len ); +	g_free( out ); +	if( st != len ) +	{ +		msn_sb_destroy( sb ); +		return 0; +	} +	 +	return 1; +} + +int msn_sb_write_msg( struct im_connection *ic, struct msn_message *m ) +{ +	struct msn_data *md = ic->proto_data; +	struct msn_switchboard *sb; + +	/* FIXME: *CHECK* the reliability of using spare sb's! */ +	if( ( sb = msn_sb_spare( ic ) ) ) +	{ +		debug( "Trying to use a spare switchboard to message %s", m->who ); +		 +		sb->who = g_strdup( m->who ); +		if( msn_sb_write( sb, "CAL %d %s\r\n", ++sb->trId, m->who ) ) +		{ +			/* He/She should join the switchboard soon, let's queue the message. */ +			sb->msgq = g_slist_append( sb->msgq, m ); +			return( 1 ); +		} +	} +	 +	debug( "Creating a new switchboard to message %s", m->who ); +	 +	/* If we reach this line, there was no spare switchboard, so let's make one. */ +	if( !msn_ns_write( ic, -1, "XFR %d SB\r\n", ++md->trId ) ) +	{ +		g_free( m->who ); +		g_free( m->text ); +		g_free( m ); +		 +		return( 0 ); +	} +	 +	/* And queue the message to md. We'll pick it up when the switchboard comes up. */ +	md->msgq = g_slist_append( md->msgq, m ); +	 +	/* FIXME: If the switchboard creation fails, the message will not be sent. */ +	 +	return( 1 ); +} + +struct msn_switchboard *msn_sb_create( struct im_connection *ic, char *host, int port, char *key, int session ) +{ +	struct msn_data *md = ic->proto_data; +	struct msn_switchboard *sb = g_new0( struct msn_switchboard, 1 ); +	 +	sb->fd = proxy_connect( host, port, msn_sb_connected, sb ); +	if( sb->fd < 0 ) +	{ +		g_free( sb ); +		return( NULL ); +	} +	 +	sb->ic = ic; +	sb->key = g_strdup( key ); +	sb->session = session; +	 +	msn_switchboards = g_slist_append( msn_switchboards, sb ); +	md->switchboards = g_slist_append( md->switchboards, sb ); +	 +	return( sb ); +} + +struct msn_switchboard *msn_sb_by_handle( struct im_connection *ic, char *handle ) +{ +	struct msn_data *md = ic->proto_data; +	struct msn_switchboard *sb; +	GSList *l; +	 +	for( l = md->switchboards; l; l = l->next ) +	{ +		sb = l->data; +		if( sb->who && strcmp( sb->who, handle ) == 0 ) +			return( sb ); +	} +	 +	return( NULL ); +} + +struct msn_switchboard *msn_sb_by_chat( struct groupchat *c ) +{ +	struct msn_data *md = c->ic->proto_data; +	struct msn_switchboard *sb; +	GSList *l; +	 +	for( l = md->switchboards; l; l = l->next ) +	{ +		sb = l->data; +		if( sb->chat == c ) +			return( sb ); +	} +	 +	return( NULL ); +} + +struct msn_switchboard *msn_sb_spare( struct im_connection *ic ) +{ +	struct msn_data *md = ic->proto_data; +	struct msn_switchboard *sb; +	GSList *l; +	 +	for( l = md->switchboards; l; l = l->next ) +	{ +		sb = l->data; +		if( !sb->who && !sb->chat ) +			return( sb ); +	} +	 +	return( NULL ); +} + +int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) +{ +	if( sb->ready ) +	{ +		char *buf; +		int i, j; +		 +		/* Build the message. Convert LF to CR-LF for normal messages. */ +		if( strcmp( text, TYPING_NOTIFICATION_MESSAGE ) == 0 ) +		{ +			i = strlen( MSN_TYPING_HEADERS ) + strlen( sb->ic->acc->user ); +			buf = g_new0( char, i ); +			i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->ic->acc->user ); +		} +		else if( strcmp( text, NUDGE_MESSAGE ) == 0 ) +		{ +			buf = g_strdup( MSN_NUDGE_HEADERS ); +			i = strlen( buf ); +		} +		else if( strcmp( text, SB_KEEPALIVE_MESSAGE ) == 0 ) +		{ +			buf = g_strdup( MSN_SB_KEEPALIVE_HEADERS ); +			i = strlen( buf ); +		} +		else if( strncmp( text, MSN_INVITE_HEADERS, sizeof( MSN_INVITE_HEADERS ) - 1 ) == 0 )  +		{ +			buf = g_strdup( text ); +			i = strlen( buf ); +		} +		else +		{ +			buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 + 1 ); +			i = strlen( MSN_MESSAGE_HEADERS ); +			 +			strcpy( buf, MSN_MESSAGE_HEADERS ); +			for( j = 0; text[j]; j ++ ) +			{ +				if( text[j] == '\n' ) +					buf[i++] = '\r'; +				 +				buf[i++] = text[j]; +			} +		} +		 +		/* Build the final packet (MSG command + the message). */ +		if( msn_sb_write( sb, "MSG %d N %d\r\n%s", ++sb->trId, i, buf ) ) +		{ +			g_free( buf ); +			return 1; +		} +		else +		{ +			g_free( buf ); +			return 0; +		} +	} +	else if( sb->who ) +	{ +		struct msn_message *m = g_new0( struct msn_message, 1 ); +		 +		m->who = g_strdup( "" ); +		m->text = g_strdup( text ); +		sb->msgq = g_slist_append( sb->msgq, m ); +		 +		return( 1 ); +	} +	else +	{ +		return( 0 ); +	} +} + +struct groupchat *msn_sb_to_chat( struct msn_switchboard *sb ) +{ +	struct im_connection *ic = sb->ic; +	struct groupchat *c = NULL; +	char buf[1024]; +	 +	/* Create the groupchat structure. */ +	g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session ); +	if( sb->who ) +		c = bee_chat_by_title( ic->bee, ic, sb->who ); +	if( c && !msn_sb_by_chat( c ) ) +		sb->chat = c; +	else +		sb->chat = imcb_chat_new( ic, buf ); +	 +	/* Populate the channel. */ +	if( sb->who ) imcb_chat_add_buddy( sb->chat, sb->who ); +	imcb_chat_add_buddy( sb->chat, ic->acc->user ); +	 +	/* And make sure the switchboard doesn't look like a regular chat anymore. */ +	if( sb->who ) +	{ +		g_free( sb->who ); +		sb->who = NULL; +	} +	 +	return sb->chat; +} + +void msn_sb_destroy( struct msn_switchboard *sb ) +{ +	struct im_connection *ic = sb->ic; +	struct msn_data *md = ic->proto_data; +	 +	debug( "Destroying switchboard: %s", sb->who ? sb->who : sb->key ? sb->key : "" ); +	 +	msn_msgq_purge( ic, &sb->msgq ); +	msn_sb_stop_keepalives( sb ); +	 +	if( sb->key ) g_free( sb->key ); +	if( sb->who ) g_free( sb->who ); +	 +	if( sb->chat ) +	{ +		imcb_chat_free( sb->chat ); +	} +	 +	if( sb->handler ) +	{ +		if( sb->handler->rxq ) g_free( sb->handler->rxq ); +		if( sb->handler->cmd_text ) g_free( sb->handler->cmd_text ); +		g_free( sb->handler ); +	} +	 +	if( sb->inp ) b_event_remove( sb->inp ); +	closesocket( sb->fd ); +	 +	msn_switchboards = g_slist_remove( msn_switchboards, sb ); +	md->switchboards = g_slist_remove( md->switchboards, sb ); +	g_free( sb ); +} + +gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond ) +{ +	struct msn_switchboard *sb = data; +	struct im_connection *ic; +	struct msn_data *md; +	char buf[1024]; +	 +	/* Are we still alive? */ +	if( !g_slist_find( msn_switchboards, sb ) ) +		return FALSE; +	 +	ic = sb->ic; +	md = ic->proto_data; +	 +	if( source != sb->fd ) +	{ +		debug( "Error %d while connecting to switchboard server", 1 ); +		msn_sb_destroy( sb ); +		return FALSE; +	} +	 +	/* Prepare the callback */ +	sb->handler = g_new0( struct msn_handler_data, 1 ); +	sb->handler->fd = sb->fd; +	sb->handler->rxq = g_new0( char, 1 ); +	sb->handler->data = sb; +	sb->handler->exec_command = msn_sb_command; +	sb->handler->exec_message = msn_sb_message; +	 +	if( sb->session == MSN_SB_NEW ) +		g_snprintf( buf, sizeof( buf ), "USR %d %s %s\r\n", ++sb->trId, ic->acc->user, sb->key ); +	else +		g_snprintf( buf, sizeof( buf ), "ANS %d %s %s %d\r\n", ++sb->trId, ic->acc->user, sb->key, sb->session ); +	 +	if( msn_sb_write( sb, "%s", buf ) ) +		sb->inp = b_input_add( sb->fd, B_EV_IO_READ, msn_sb_callback, sb ); +	else +		debug( "Error %d while connecting to switchboard server", 2 ); +	 +	return FALSE; +} + +static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond ) +{ +	struct msn_switchboard *sb = data; +	struct im_connection *ic = sb->ic; +	struct msn_data *md = ic->proto_data; +	 +	if( msn_handler( sb->handler ) != -1 ) +		return TRUE; +	 +	if( sb->msgq != NULL ) +	{ +		time_t now = time( NULL ); +		 +		if( now - md->first_sb_failure > 600 ) +		{ +			/* It's not really the first one, but the start of this "series". +			   With this, the warning below will be shown only if this happens +			   at least three times in ten minutes. This algorithm isn't +			   perfect, but for this purpose it will do. */ +			md->first_sb_failure = now; +			md->sb_failures = 0; +		} +		 +		debug( "Error: Switchboard died" ); +		if( ++ md->sb_failures >= 3 ) +			imcb_log( ic, "Warning: Many switchboard failures on MSN connection. " +			              "There might be problems delivering your messages." ); +		 +		if( md->msgq == NULL ) +		{ +			md->msgq = sb->msgq; +		} +		else +		{ +			GSList *l; +			 +			for( l = md->msgq; l->next; l = l->next ); +			l->next = sb->msgq; +		} +		sb->msgq = NULL; +		 +		debug( "Moved queued messages back to the main queue, " +		       "creating a new switchboard to retry." ); +		if( !msn_ns_write( ic, -1, "XFR %d SB\r\n", ++md->trId ) ) +			return FALSE; +	} +	 +	msn_sb_destroy( sb ); +	return FALSE; +} + +static int msn_sb_command( struct msn_handler_data *handler, char **cmd, int num_parts ) +{ +	struct msn_switchboard *sb = handler->data; +	struct im_connection *ic = sb->ic; +	 +	if( !num_parts ) +	{ +		/* Hrrm... Empty command...? Ignore? */ +		return( 1 ); +	} +	 +	if( strcmp( cmd[0], "XFR" ) == 0 ) +	{ +		imcb_error( ic, "Received an XFR from a switchboard server, unable to comply! This is likely to be a bug, please report it!" ); +		imc_logout( ic, TRUE ); +		return( 0 ); +	} +	else if( strcmp( cmd[0], "USR" ) == 0 ) +	{ +		if( num_parts < 5 ) +		{ +			msn_sb_destroy( sb ); +			return( 0 ); +		} +		 +		if( strcmp( cmd[2], "OK" ) != 0 ) +		{ +			msn_sb_destroy( sb ); +			return( 0 ); +		} +		 +		if( sb->who ) +			return msn_sb_write( sb, "CAL %d %s\r\n", ++sb->trId, sb->who ); +		else +			debug( "Just created a switchboard, but I don't know what to do with it." ); +	} +	else if( strcmp( cmd[0], "IRO" ) == 0 ) +	{ +		int num, tot; +		 +		if( num_parts < 6 ) +		{ +			msn_sb_destroy( sb ); +			return( 0 ); +		} +		 +		num = atoi( cmd[2] ); +		tot = atoi( cmd[3] ); +		 +		if( tot <= 0 ) +		{ +			msn_sb_destroy( sb ); +			return( 0 ); +		} +		else if( tot > 1 ) +		{ +			char buf[1024]; +			 +			if( num == 1 ) +			{ +				g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session ); +				sb->chat = imcb_chat_new( ic, buf ); +				 +				g_free( sb->who ); +				sb->who = NULL; +			} +			 +			imcb_chat_add_buddy( sb->chat, cmd[4] ); +			 +			if( num == tot ) +			{ +				imcb_chat_add_buddy( sb->chat, ic->acc->user ); +			} +		} +	} +	else if( strcmp( cmd[0], "ANS" ) == 0 ) +	{ +		if( num_parts < 3 ) +		{ +			msn_sb_destroy( sb ); +			return( 0 ); +		} +		 +		if( strcmp( cmd[2], "OK" ) != 0 ) +		{ +			debug( "Switchboard server sent a negative ANS reply" ); +			msn_sb_destroy( sb ); +			return( 0 ); +		} +		 +		sb->ready = 1; +		 +		msn_sb_start_keepalives( sb, FALSE ); +	} +	else if( strcmp( cmd[0], "CAL" ) == 0 ) +	{ +		if( num_parts < 4 || !isdigit( cmd[3][0] ) ) +		{ +			msn_sb_destroy( sb ); +			return( 0 ); +		} +		 +		sb->session = atoi( cmd[3] ); +	} +	else if( strcmp( cmd[0], "JOI" ) == 0 ) +	{ +		if( num_parts < 3 ) +		{ +			msn_sb_destroy( sb ); +			return( 0 ); +		} +		 +		if( sb->who && g_strcasecmp( cmd[1], sb->who ) == 0 ) +		{ +			/* The user we wanted to talk to is finally there, let's send the queued messages then. */ +			struct msn_message *m; +			GSList *l; +			int st = 1; +			 +			debug( "%s arrived in the switchboard session, now sending queued message(s)", cmd[1] ); +			 +			/* Without this, sendmessage() will put everything back on the queue... */ +			sb->ready = 1; +			 +			while( ( l = sb->msgq ) ) +			{ +				m = l->data; +				if( st ) +				{ +					/* This hack is meant to convert a regular new chat into a groupchat */ +					if( strcmp( m->text, GROUPCHAT_SWITCHBOARD_MESSAGE ) == 0 ) +						msn_sb_to_chat( sb ); +					else +						st = msn_sb_sendmessage( sb, m->text ); +				} +				g_free( m->text ); +				g_free( m->who ); +				g_free( m ); +				 +				sb->msgq = g_slist_remove( sb->msgq, m ); +			} +			 +			msn_sb_start_keepalives( sb, FALSE ); +			 +			return( st ); +		} +		else if( sb->who ) +		{ +			debug( "Converting chat with %s to a groupchat because %s joined the session.", sb->who, cmd[1] ); +			 +			/* This SB is a one-to-one chat right now, but someone else is joining. */ +			msn_sb_to_chat( sb ); +			 +			imcb_chat_add_buddy( sb->chat, cmd[1] ); +		} +		else if( sb->chat ) +		{ +			imcb_chat_add_buddy( sb->chat, cmd[1] ); +			sb->ready = 1; +		} +		else +		{ +			/* PANIC! */ +		} +	} +	else if( strcmp( cmd[0], "MSG" ) == 0 ) +	{ +		if( num_parts < 4 ) +		{ +			msn_sb_destroy( sb ); +			return( 0 ); +		} +		 +		sb->handler->msglen = atoi( cmd[3] ); +		 +		if( sb->handler->msglen <= 0 ) +		{ +			debug( "Received a corrupted message on the switchboard, the switchboard will be closed" ); +			msn_sb_destroy( sb ); +			return( 0 ); +		} +	} +	else if( strcmp( cmd[0], "NAK" ) == 0 ) +	{ +		if( sb->who ) +		{ +			imcb_log( ic, "The MSN servers could not deliver one of your messages to %s.", sb->who ); +		} +		else +		{ +			imcb_log( ic, "The MSN servers could not deliver one of your groupchat messages to all participants." ); +		} +	} +	else if( strcmp( cmd[0], "BYE" ) == 0 ) +	{ +		if( num_parts < 2 ) +		{ +			msn_sb_destroy( sb ); +			return( 0 ); +		} +		 +		/* if( cmd[2] && *cmd[2] == '1' ) -=> Chat is being cleaned up because of idleness */ +		 +		if( sb->who ) +		{ +			msn_sb_stop_keepalives( sb ); +			 +			/* This is a single-person chat, and the other person is leaving. */ +			g_free( sb->who ); +			sb->who = NULL; +			sb->ready = 0; +			 +			debug( "Person %s left the one-to-one switchboard connection. Keeping it around as a spare...", cmd[1] ); +			 +			/* We could clean up the switchboard now, but keeping it around +			   as a spare for a next conversation sounds more sane to me. +			   The server will clean it up when it's idle for too long. */ +		} +		else if( sb->chat ) +		{ +			imcb_chat_remove_buddy( sb->chat, cmd[1], "" ); +		} +		else +		{ +			/* PANIC! */ +		} +	} +	else if( isdigit( cmd[0][0] ) ) +	{ +		int num = atoi( cmd[0] ); +		const struct msn_status_code *err = msn_status_by_number( num ); +		 +		/* If the person is offline, send an offline message instead, +		   and don't report an error. */ +		if( num == 217 ) +			msn_soap_oim_send_queue( ic, &sb->msgq ); +		else +			imcb_error( ic, "Error reported by switchboard server: %s", err->text ); +		 +		if( err->flags & STATUS_SB_FATAL ) +		{ +			msn_sb_destroy( sb ); +			return 0; +		} +		else if( err->flags & STATUS_FATAL ) +		{ +			imc_logout( ic, TRUE ); +			return 0; +		} +		else if( err->flags & STATUS_SB_IM_SPARE ) +		{ +			if( sb->who ) +			{ +				/* Apparently some invitation failed. We might want to use this +				   board later, so keep it as a spare. */ +				g_free( sb->who ); +				sb->who = NULL; +				 +				/* Also clear the msgq, otherwise someone else might get them. */ +				msn_msgq_purge( ic, &sb->msgq ); +			} +			 +			/* Do NOT return 0 here, we want to keep this sb. */ +		} +	} +	else +	{ +		/* debug( "Received unknown command from switchboard server: %s", cmd[0] ); */ +	} +	 +	return( 1 ); +} + +static int msn_sb_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts ) +{ +	struct msn_switchboard *sb = handler->data; +	struct im_connection *ic = sb->ic; +	char *body; +	int blen = 0; +	 +	if( !num_parts ) +		return( 1 ); +	 +	if( ( body = strstr( msg, "\r\n\r\n" ) ) ) +	{ +		body += 4; +		blen = msglen - ( body - msg ); +	} +	 +	if( strcmp( cmd[0], "MSG" ) == 0 ) +	{ +		char *ct = msn_findheader( msg, "Content-Type:", msglen ); +		 +		if( !ct ) +			return( 1 ); +		 +		if( g_strncasecmp( ct, "text/plain", 10 ) == 0 ) +		{ +			g_free( ct ); +			 +			if( !body ) +				return( 1 ); +			 +			if( sb->who ) +			{ +				imcb_buddy_msg( ic, cmd[1], body, 0, 0 ); +			} +			else if( sb->chat ) +			{ +				imcb_chat_msg( sb->chat, cmd[1], body, 0, 0 ); +			} +			else +			{ +				/* PANIC! */ +			} +		} +#if 0 +		// Disable MSN ft support for now. +		else if( g_strncasecmp( ct, "text/x-msmsgsinvite", 19 ) == 0 ) +		{ +			char *command = msn_findheader( body, "Invitation-Command:", blen ); +			char *cookie = msn_findheader( body, "Invitation-Cookie:", blen ); +			unsigned int icookie; +			 +			g_free( ct ); +			 +			/* Every invite should have both a Command and Cookie header */ +			if( !command || !cookie ) { +				g_free( command ); +				g_free( cookie ); +				imcb_log( ic, "Warning: No command or cookie from %s", sb->who ); +				return 1; +			} +			 +			icookie = strtoul( cookie, NULL, 10 ); +			g_free( cookie ); +			 +			if( g_strncasecmp( command, "INVITE", 6 ) == 0 ) { +				msn_invitation_invite( sb, cmd[1], icookie, body, blen ); +			} else if( g_strncasecmp( command, "ACCEPT", 6 ) == 0 ) { +				msn_invitation_accept( sb, cmd[1], icookie, body, blen ); +			} else if( g_strncasecmp( command, "CANCEL", 6 ) == 0 ) { +				msn_invitation_cancel( sb, cmd[1], icookie, body, blen ); +			} else { +				imcb_log( ic, "Warning: Received invalid invitation with " +						"command %s from %s", command, sb->who ); +			} +			 +			g_free( command ); +		} +#endif +		else if( g_strncasecmp( ct, "application/x-msnmsgrp2p", 24 ) == 0 )  +		{ +			/* Not currently implemented. Don't warn about it since +			   this seems to be used for avatars now. */ +			g_free( ct ); +		} +		else if( g_strncasecmp( ct, "text/x-msmsgscontrol", 20 ) == 0 ) +		{ +			char *who = msn_findheader( msg, "TypingUser:", msglen ); +			 +			if( who ) +			{ +				imcb_buddy_typing( ic, who, OPT_TYPING ); +				g_free( who ); +			} +			 +			g_free( ct ); +		} +		else +		{ +			g_free( ct ); +		} +	} +	 +	return( 1 ); +} + +static gboolean msn_sb_keepalive( gpointer data, gint source, b_input_condition cond ) +{ +	struct msn_switchboard *sb = data; +	return sb->ready && msn_sb_sendmessage( sb, SB_KEEPALIVE_MESSAGE ); +} + +void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial ) +{ +	bee_user_t *bu; +	 +	if( sb && sb->who && sb->keepalive == 0 && +	    ( bu = bee_user_by_handle( sb->ic->bee, sb->ic, sb->who ) ) && +	    !( bu->flags & BEE_USER_ONLINE ) && +	    set_getbool( &sb->ic->acc->set, "switchboard_keepalives" ) ) +	{ +		if( initial ) +			msn_sb_keepalive( sb, 0, 0 ); +		 +		sb->keepalive = b_timeout_add( 20000, msn_sb_keepalive, sb ); +	} +} + +void msn_sb_stop_keepalives( struct msn_switchboard *sb ) +{ +	if( sb && sb->keepalive > 0 ) +	{ +		b_event_remove( sb->keepalive ); +		sb->keepalive = 0; +	} +} | 
