bitlbee-3.2.1/0000755000175000017500000000000012245477444012520 5ustar wilmerwilmerbitlbee-3.2.1/irc_im.c0000644000175000017500000006616512245474076014142 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* Some glue to put the IRC and the IM stuff together. */ /* 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 "dcc.h" /* IM->IRC callbacks: Simple IM/buddy-related stuff. */ static const struct irc_user_funcs irc_user_im_funcs; static void bee_irc_imc_connected( struct im_connection *ic ) { irc_t *irc = (irc_t*) ic->bee->ui_data; irc_channel_auto_joins( irc, ic->acc ); } static void bee_irc_imc_disconnected( struct im_connection *ic ) { /* Maybe try to send /QUITs here instead of later on. */ } static gboolean bee_irc_user_new( bee_t *bee, bee_user_t *bu ) { irc_user_t *iu; irc_t *irc = (irc_t*) bee->ui_data; char nick[MAX_NICK_LENGTH+1], *s; memset( nick, 0, MAX_NICK_LENGTH + 1 ); strcpy( nick, nick_get( bu ) ); bu->ui_data = iu = irc_user_new( irc, nick ); iu->bu = bu; if( set_getbool( &irc->b->set, "private" ) ) iu->last_channel = NULL; else iu->last_channel = irc_channel_with_user( irc, iu ); if( ( s = strchr( bu->handle, '@' ) ) ) { iu->host = g_strdup( s + 1 ); iu->user = g_strndup( bu->handle, s - bu->handle ); } else { iu->user = g_strdup( bu->handle ); if( bu->ic->acc->server ) iu->host = g_strdup( bu->ic->acc->server ); else { char *s; for( s = bu->ic->acc->tag; isalnum( *s ); s ++ ); /* Only use the tag if we got to the end of the string. (So allow alphanumerics only. Hopefully not too restrictive.) */ if( *s ) iu->host = g_strdup( bu->ic->acc->prpl->name ); else iu->host = g_strdup( bu->ic->acc->tag ); } } while( ( s = strchr( iu->user, ' ' ) ) ) *s = '_'; if( bu->flags & BEE_USER_LOCAL ) { char *s = set_getstr( &bee->set, "handle_unknown" ); if( strcmp( s, "add_private" ) == 0 ) iu->last_channel = NULL; else if( strcmp( s, "add_channel" ) == 0 ) iu->last_channel = irc->default_channel; } iu->f = &irc_user_im_funcs; return TRUE; } static gboolean bee_irc_user_free( bee_t *bee, bee_user_t *bu ) { return irc_user_free( bee->ui_data, (irc_user_t *) bu->ui_data ); } static gboolean bee_irc_user_status( bee_t *bee, bee_user_t *bu, bee_user_t *old ) { irc_t *irc = bee->ui_data; irc_user_t *iu = bu->ui_data; /* Do this outside the if below since away state can change without the online state changing. */ iu->flags &= ~IRC_USER_AWAY; if( bu->flags & BEE_USER_AWAY || !( bu->flags & BEE_USER_ONLINE ) ) iu->flags |= IRC_USER_AWAY; if( ( bu->flags & BEE_USER_ONLINE ) != ( old->flags & BEE_USER_ONLINE ) ) { if( bu->flags & BEE_USER_ONLINE ) { if( g_hash_table_lookup( irc->watches, iu->key ) ) irc_send_num( irc, 600, "%s %s %s %d :%s", iu->nick, iu->user, iu->host, (int) time( NULL ), "logged online" ); } else { if( g_hash_table_lookup( irc->watches, iu->key ) ) irc_send_num( irc, 601, "%s %s %s %d :%s", iu->nick, iu->user, iu->host, (int) time( NULL ), "logged offline" ); /* Send a QUIT since those will also show up in any query windows the user may have, plus it's only one QUIT instead of possibly many (in case of multiple control chans). If there's a channel that shows offline people, a JOIN will follow. */ if( set_getbool( &bee->set, "offline_user_quits" ) ) irc_user_quit( iu, "Leaving..." ); } } /* Reset this one since the info may have changed. */ iu->away_reply_timeout = 0; bee_irc_channel_update( irc, NULL, iu ); return TRUE; } void bee_irc_channel_update( irc_t *irc, irc_channel_t *ic, irc_user_t *iu ) { GSList *l; if( ic == NULL ) { for( l = irc->channels; l; l = l->next ) { ic = l->data; /* TODO: Just add a type flag or so.. */ if( ic->f == irc->default_channel->f && ( ic->flags & IRC_CHANNEL_JOINED ) ) bee_irc_channel_update( irc, ic, iu ); } return; } if( iu == NULL ) { for( l = irc->users; l; l = l->next ) { iu = l->data; if( iu->bu ) bee_irc_channel_update( irc, ic, l->data ); } return; } if( !irc_channel_wants_user( ic, iu ) ) { irc_channel_del_user( ic, iu, IRC_CDU_PART, NULL ); } else { struct irc_control_channel *icc = ic->data; int mode = 0; if( !( iu->bu->flags & BEE_USER_ONLINE ) ) mode = icc->modes[0]; else if( iu->bu->flags & BEE_USER_AWAY ) mode = icc->modes[1]; else mode = icc->modes[2]; if( !mode ) irc_channel_del_user( ic, iu, IRC_CDU_PART, NULL ); else { irc_channel_add_user( ic, iu ); irc_channel_user_set_mode( ic, iu, mode ); } } } static gboolean bee_irc_user_msg( bee_t *bee, bee_user_t *bu, const char *msg_, time_t sent_at ) { irc_t *irc = bee->ui_data; irc_user_t *iu = (irc_user_t *) bu->ui_data; const char *dst; char *prefix = NULL; char *wrapped, *ts = NULL; char *msg = g_strdup( msg_ ); GSList *l; if( sent_at > 0 && set_getbool( &irc->b->set, "display_timestamps" ) ) ts = irc_format_timestamp( irc, sent_at ); dst = irc_user_msgdest( iu ); if( dst != irc->user->nick ) { /* if not messaging directly, call user by name */ prefix = g_strdup_printf( "%s%s%s", irc->user->nick, set_getstr( &bee->set, "to_char" ), ts ? : "" ); } else { prefix = ts; ts = NULL; /* don't double-free */ } for( l = irc_plugins; l; l = l->next ) { irc_plugin_t *p = l->data; if( p->filter_msg_in ) { char *s = p->filter_msg_in( iu, msg, 0 ); if( s ) { if( s != msg ) g_free( msg ); msg = s; } else { /* Modules can swallow messages. */ return TRUE; } } } if( ( g_strcasecmp( set_getstr( &bee->set, "strip_html" ), "always" ) == 0 ) || ( ( bu->ic->flags & OPT_DOES_HTML ) && set_getbool( &bee->set, "strip_html" ) ) ) { char *s = g_strdup( msg ); strip_html( s ); g_free( msg ); msg = s; } wrapped = word_wrap( msg, 425 ); irc_send_msg( iu, "PRIVMSG", dst, wrapped, prefix ); g_free( wrapped ); g_free( prefix ); g_free( msg ); g_free( ts ); return TRUE; } static gboolean bee_irc_user_typing( bee_t *bee, bee_user_t *bu, uint32_t flags ) { irc_t *irc = (irc_t *) bee->ui_data; if( set_getbool( &bee->set, "typing_notice" ) ) irc_send_msg_f( (irc_user_t *) bu->ui_data, "PRIVMSG", irc->user->nick, "\001TYPING %d\001", ( flags >> 8 ) & 3 ); else return FALSE; return TRUE; } static gboolean bee_irc_user_action_response( bee_t *bee, bee_user_t *bu, const char *action, char * const args[], void *data ) { irc_t *irc = (irc_t *) bee->ui_data; GString *msg = g_string_new( "\001" ); g_string_append( msg, action ); while( *args ) { if( strchr( *args, ' ' ) ) g_string_append_printf( msg, " \"%s\"", *args ); else g_string_append_printf( msg, " %s", *args ); args ++; } g_string_append_c( msg, '\001' ); irc_send_msg( (irc_user_t *) bu->ui_data, "NOTICE", irc->user->nick, msg->str, NULL ); return TRUE; } static gboolean bee_irc_user_nick_update( irc_user_t *iu ); static gboolean bee_irc_user_fullname( bee_t *bee, bee_user_t *bu ) { irc_user_t *iu = (irc_user_t *) bu->ui_data; char *s; if( iu->fullname != iu->nick ) g_free( iu->fullname ); iu->fullname = g_strdup( bu->fullname ); /* Strip newlines (unlikely, but IRC-unfriendly so they must go) TODO(wilmer): Do the same with away msgs again! */ for( s = iu->fullname; *s; s ++ ) if( isspace( *s ) ) *s = ' '; if( ( bu->ic->flags & OPT_LOGGED_IN ) && set_getbool( &bee->set, "display_namechanges" ) ) { /* People don't like this /NOTICE. Meh, let's go back to the old one. char *msg = g_strdup_printf( "<< \002BitlBee\002 - Changed name to `%s' >>", iu->fullname ); irc_send_msg( iu, "NOTICE", irc->user->nick, msg, NULL ); */ imcb_log( bu->ic, "User `%s' changed name to `%s'", iu->nick, iu->fullname ); } bee_irc_user_nick_update( iu ); return TRUE; } static gboolean bee_irc_user_nick_hint( bee_t *bee, bee_user_t *bu, const char *hint ) { bee_irc_user_nick_update( (irc_user_t*) bu->ui_data ); return TRUE; } static gboolean bee_irc_user_group( bee_t *bee, bee_user_t *bu ) { irc_user_t *iu = (irc_user_t *) bu->ui_data; irc_t *irc = (irc_t *) bee->ui_data; bee_user_flags_t online; /* Take the user offline temporarily so we can change the nick (if necessary). */ if( ( online = bu->flags & BEE_USER_ONLINE ) ) bu->flags &= ~BEE_USER_ONLINE; bee_irc_channel_update( irc, NULL, iu ); bee_irc_user_nick_update( iu ); if( online ) { bu->flags |= online; bee_irc_channel_update( irc, NULL, iu ); } return TRUE; } static gboolean bee_irc_user_nick_update( irc_user_t *iu ) { bee_user_t *bu = iu->bu; char *newnick; if( bu->flags & BEE_USER_ONLINE ) /* Ignore if the user is visible already. */ return TRUE; if( nick_saved( bu ) ) /* The user already assigned a nickname to this person. */ return TRUE; newnick = nick_get( bu ); if( strcmp( iu->nick, newnick ) != 0 ) { nick_dedupe( bu, newnick ); irc_user_set_nick( iu, newnick ); } return TRUE; } void bee_irc_user_nick_reset( irc_user_t *iu ) { bee_user_t *bu = iu->bu; bee_user_flags_t online; if( bu == FALSE ) return; /* In this case, pretend the user is offline. */ if( ( online = bu->flags & BEE_USER_ONLINE ) ) bu->flags &= ~BEE_USER_ONLINE; nick_del( bu ); bee_irc_user_nick_update( iu ); bu->flags |= online; } /* IRC->IM calls */ static gboolean bee_irc_user_privmsg_cb( gpointer data, gint fd, b_input_condition cond ); static gboolean bee_irc_user_privmsg( irc_user_t *iu, const char *msg ) { const char *away; if( iu->bu == NULL ) return FALSE; if( ( away = irc_user_get_away( iu ) ) && time( NULL ) >= iu->away_reply_timeout ) { irc_send_num( iu->irc, 301, "%s :%s", iu->nick, away ); iu->away_reply_timeout = time( NULL ) + set_getint( &iu->irc->b->set, "away_reply_timeout" ); } if( iu->pastebuf == NULL ) iu->pastebuf = g_string_new( msg ); else { b_event_remove( iu->pastebuf_timer ); g_string_append_printf( iu->pastebuf, "\n%s", msg ); } if( set_getbool( &iu->irc->b->set, "paste_buffer" ) ) { int delay; if( ( delay = set_getint( &iu->irc->b->set, "paste_buffer_delay" ) ) <= 5 ) delay *= 1000; iu->pastebuf_timer = b_timeout_add( delay, bee_irc_user_privmsg_cb, iu ); return TRUE; } else { bee_irc_user_privmsg_cb( iu, 0, 0 ); return TRUE; } } static gboolean bee_irc_user_privmsg_cb( gpointer data, gint fd, b_input_condition cond ) { irc_user_t *iu = data; char *msg; GSList *l; msg = g_string_free( iu->pastebuf, FALSE ); iu->pastebuf = NULL; iu->pastebuf_timer = 0; for( l = irc_plugins; l; l = l->next ) { irc_plugin_t *p = l->data; if( p->filter_msg_out ) { char *s = p->filter_msg_out( iu, msg, 0 ); if( s ) { if( s != msg ) g_free( msg ); msg = s; } else { /* Modules can swallow messages. */ iu->pastebuf = NULL; g_free( msg ); return FALSE; } } } bee_user_msg( iu->irc->b, iu->bu, msg, 0 ); g_free( msg ); return FALSE; } static gboolean bee_irc_user_ctcp( irc_user_t *iu, char *const *ctcp ) { if( ctcp[1] && g_strcasecmp( ctcp[0], "DCC" ) == 0 && g_strcasecmp( ctcp[1], "SEND" ) == 0 ) { if( iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->transfer_request ) { file_transfer_t *ft = dcc_request( iu->bu->ic, ctcp ); if ( ft ) iu->bu->ic->acc->prpl->transfer_request( iu->bu->ic, ft, iu->bu->handle ); return TRUE; } } else if( g_strcasecmp( ctcp[0], "TYPING" ) == 0 ) { if( iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->send_typing && ctcp[1] ) { int st = ctcp[1][0]; if( st >= '0' && st <= '2' ) { st <<= 8; iu->bu->ic->acc->prpl->send_typing( iu->bu->ic, iu->bu->handle, st ); } return TRUE; } } else if( g_strcasecmp( ctcp[0], "HELP" ) == 0 && iu->bu ) { GString *supp = g_string_new( "Supported CTCPs:" ); GList *l; if( iu->bu->ic && iu->bu->ic->acc->prpl->transfer_request ) g_string_append( supp, " DCC SEND," ); if( iu->bu->ic && iu->bu->ic->acc->prpl->send_typing ) g_string_append( supp, " TYPING," ); if( iu->bu->ic->acc->prpl->buddy_action_list ) for( l = iu->bu->ic->acc->prpl->buddy_action_list( iu->bu ); l; l = l->next ) { struct buddy_action *ba = l->data; g_string_append_printf( supp, " %s (%s),", ba->name, ba->description ); } g_string_truncate( supp, supp->len - 1 ); irc_send_msg_f( iu, "NOTICE", iu->irc->user->nick, "\001HELP %s\001", supp->str ); g_string_free( supp, TRUE ); } else if( iu->bu && iu->bu->ic && iu->bu->ic->acc->prpl->buddy_action ) { iu->bu->ic->acc->prpl->buddy_action( iu->bu, ctcp[0], ctcp + 1, NULL ); } return FALSE; } static const struct irc_user_funcs irc_user_im_funcs = { bee_irc_user_privmsg, bee_irc_user_ctcp, }; /* IM->IRC: Groupchats */ const struct irc_channel_funcs irc_channel_im_chat_funcs; static gboolean bee_irc_chat_new( bee_t *bee, struct groupchat *c ) { irc_t *irc = bee->ui_data; irc_channel_t *ic; char *topic; GSList *l; int i; /* Try to find a channel that expects to receive a groupchat. This flag is set earlier in our current call trace. */ for( l = irc->channels; l; l = l->next ) { ic = l->data; if( ic->flags & IRC_CHANNEL_CHAT_PICKME ) break; } /* If we found none, just generate some stupid name. */ if( l == NULL ) for( i = 0; i <= 999; i ++ ) { char name[16]; sprintf( name, "#chat_%03d", i ); if( ( ic = irc_channel_new( irc, name ) ) ) break; } if( ic == NULL ) return FALSE; c->ui_data = ic; ic->data = c; topic = g_strdup_printf( "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->title ); irc_channel_set_topic( ic, topic, irc->root ); g_free( topic ); return TRUE; } static gboolean bee_irc_chat_free( bee_t *bee, struct groupchat *c ) { irc_channel_t *ic = c->ui_data; if( ic == NULL ) return FALSE; if( ic->flags & IRC_CHANNEL_JOINED ) irc_channel_printf( ic, "Cleaning up channel, bye!" ); ic->data = NULL; c->ui_data = NULL; irc_channel_del_user( ic, ic->irc->user, IRC_CDU_KICK, "Chatroom closed by server" ); return TRUE; } static gboolean bee_irc_chat_log( bee_t *bee, struct groupchat *c, const char *text ) { irc_channel_t *ic = c->ui_data; if( ic == NULL ) return FALSE; irc_channel_printf( ic, "%s", text ); return TRUE; } static gboolean bee_irc_chat_msg( bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *msg, time_t sent_at ) { irc_t *irc = bee->ui_data; irc_user_t *iu = bu->ui_data; irc_channel_t *ic = c->ui_data; char *ts = NULL; if( ic == NULL ) return FALSE; if( sent_at > 0 && set_getbool( &bee->set, "display_timestamps" ) ) ts = irc_format_timestamp( irc, sent_at ); irc_send_msg( iu, "PRIVMSG", ic->name, msg, ts ); g_free( ts ); return TRUE; } static gboolean bee_irc_chat_add_user( bee_t *bee, struct groupchat *c, bee_user_t *bu ) { irc_t *irc = bee->ui_data; irc_channel_t *ic = c->ui_data; if( ic == NULL ) return FALSE; irc_channel_add_user( ic, bu == bee->user ? irc->user : bu->ui_data ); return TRUE; } static gboolean bee_irc_chat_remove_user( bee_t *bee, struct groupchat *c, bee_user_t *bu ) { irc_t *irc = bee->ui_data; irc_channel_t *ic = c->ui_data; if( ic == NULL || bu == NULL ) return FALSE; /* TODO: Possible bug here: If a module removes $user here instead of just using imcb_chat_free() and the channel was IRC_CHANNEL_TEMP, we get into a broken state around here. */ irc_channel_del_user( ic, bu == bee->user ? irc->user : bu->ui_data, IRC_CDU_PART, NULL ); return TRUE; } static gboolean bee_irc_chat_topic( bee_t *bee, struct groupchat *c, const char *new, bee_user_t *bu ) { irc_channel_t *ic = c->ui_data; irc_t *irc = bee->ui_data; irc_user_t *iu; if( ic == NULL ) return FALSE; if( bu == NULL ) iu = irc->root; else if( bu == bee->user ) iu = irc->user; else iu = bu->ui_data; irc_channel_set_topic( ic, new, iu ); return TRUE; } static gboolean bee_irc_chat_name_hint( bee_t *bee, struct groupchat *c, const char *name ) { irc_t *irc = bee->ui_data; irc_channel_t *ic = c->ui_data, *oic; char stripped[MAX_NICK_LENGTH+1], *full_name; if( ic == NULL ) return FALSE; /* Don't rename a channel if the user's in it already. */ if( ic->flags & IRC_CHANNEL_JOINED ) return FALSE; strncpy( stripped, name, MAX_NICK_LENGTH ); stripped[MAX_NICK_LENGTH] = '\0'; irc_channel_name_strip( stripped ); if( set_getbool( &bee->set, "lcnicks" ) ) nick_lc( irc, stripped ); if( stripped[0] == '\0' ) return FALSE; full_name = g_strdup_printf( "#%s", stripped ); if( ( oic = irc_channel_by_name( irc, full_name ) ) ) { char *type, *chat_type; type = set_getstr( &oic->set, "type" ); chat_type = set_getstr( &oic->set, "chat_type" ); if( type && chat_type && oic->data == FALSE && strcmp( type, "chat" ) == 0 && strcmp( chat_type, "groupchat" ) == 0 ) { /* There's a channel with this name already, but it looks like it's not in use yet. Most likely the IRC client rejoined the channel after a reconnect. Remove it so we can reuse its name. */ irc_channel_free( oic ); } else { g_free( full_name ); return FALSE; } } g_free( ic->name ); ic->name = full_name; return TRUE; } static gboolean bee_irc_chat_invite( bee_t *bee, bee_user_t *bu, const char *name, const char *msg ) { char *channel, *s; irc_t *irc = bee->ui_data; irc_user_t *iu = bu->ui_data; irc_channel_t *chan; if( strchr( CTYPES, name[0] ) ) channel = g_strdup( name ); else channel = g_strdup_printf( "#%s", name ); if( ( s = strchr( channel, '@' ) ) ) *s = '\0'; if( strlen( channel ) > MAX_NICK_LENGTH ) { /* If the channel name is very long (like those insane GTalk UUID names), try if we can use the inviter's nick. */ s = g_strdup_printf( "#%s", iu->nick ); if( irc_channel_by_name( irc, s ) == NULL ) { g_free( channel ); channel = s; } } if( ( chan = irc_channel_new( irc, channel ) ) && set_setstr( &chan->set, "type", "chat" ) && set_setstr( &chan->set, "chat_type", "room" ) && set_setstr( &chan->set, "account", bu->ic->acc->tag ) && set_setstr( &chan->set, "room", (char*) name ) ) { /* I'm assuming that if the user didn't "chat add" the room himself but got invited, it's temporary, so make this a temporary mapping that is removed as soon as we /PART. */ chan->flags |= IRC_CHANNEL_TEMP; } else { irc_channel_free( chan ); chan = NULL; } g_free( channel ); irc_send_msg_f( iu, "PRIVMSG", irc->user->nick, "<< \002BitlBee\002 - Invitation to chatroom %s >>", name ); if( msg ) irc_send_msg( iu, "PRIVMSG", irc->user->nick, msg, NULL ); if( chan ) { irc_send_msg_f( iu, "PRIVMSG", irc->user->nick, "To join the room, just /join %s", chan->name ); irc_send_invite( iu, chan ); } return TRUE; } /* IRC->IM */ static gboolean bee_irc_channel_chat_privmsg_cb( gpointer data, gint fd, b_input_condition cond ); static gboolean bee_irc_channel_chat_privmsg( irc_channel_t *ic, const char *msg ) { struct groupchat *c = ic->data; char *trans = NULL, *s; if( c == NULL ) return FALSE; if( set_getbool( &ic->set, "translate_to_nicks" ) ) { char nick[MAX_NICK_LENGTH+1]; irc_user_t *iu; strncpy( nick, msg, MAX_NICK_LENGTH ); nick[MAX_NICK_LENGTH] = '\0'; if( ( s = strchr( nick, ':' ) ) || ( s = strchr( nick, ',' ) ) ) { *s = '\0'; if( ( iu = irc_user_by_name( ic->irc, nick ) ) && iu->bu && iu->bu->nick && irc_channel_has_user( ic, iu ) ) { trans = g_strconcat( iu->bu->nick, msg + ( s - nick ), NULL ); msg = trans; } } } if( set_getbool( &ic->irc->b->set, "paste_buffer" ) ) { int delay; if( ic->pastebuf == NULL ) ic->pastebuf = g_string_new( msg ); else { b_event_remove( ic->pastebuf_timer ); g_string_append_printf( ic->pastebuf, "\n%s", msg ); } if( ( delay = set_getint( &ic->irc->b->set, "paste_buffer_delay" ) ) <= 5 ) delay *= 1000; ic->pastebuf_timer = b_timeout_add( delay, bee_irc_channel_chat_privmsg_cb, ic ); g_free( trans ); return TRUE; } else bee_chat_msg( ic->irc->b, c, msg, 0 ); g_free( trans ); return TRUE; } static gboolean bee_irc_channel_chat_privmsg_cb( gpointer data, gint fd, b_input_condition cond ) { irc_channel_t *ic = data; if( ic->data ) bee_chat_msg( ic->irc->b, ic->data, ic->pastebuf->str, 0 ); g_string_free( ic->pastebuf, TRUE ); ic->pastebuf = 0; ic->pastebuf_timer = 0; return FALSE; } static gboolean bee_irc_channel_chat_join( irc_channel_t *ic ) { char *acc_s, *room; account_t *acc; if( strcmp( set_getstr( &ic->set, "chat_type" ), "room" ) != 0 ) return TRUE; if( ( acc_s = set_getstr( &ic->set, "account" ) ) && ( room = set_getstr( &ic->set, "room" ) ) && ( acc = account_get( ic->irc->b, acc_s ) ) && acc->ic && acc->prpl->chat_join ) { char *nick; if( !( nick = set_getstr( &ic->set, "nick" ) ) ) nick = ic->irc->user->nick; ic->flags |= IRC_CHANNEL_CHAT_PICKME; acc->prpl->chat_join( acc->ic, room, nick, NULL, &ic->set ); ic->flags &= ~IRC_CHANNEL_CHAT_PICKME; return FALSE; } else { irc_send_num( ic->irc, 403, "%s :Can't join channel, account offline?", ic->name ); return FALSE; } } static gboolean bee_irc_channel_chat_part( irc_channel_t *ic, const char *msg ) { struct groupchat *c = ic->data; if( c && c->ic->acc->prpl->chat_leave ) c->ic->acc->prpl->chat_leave( c ); /* Remove references in both directions now. We don't need each other anymore. */ ic->data = NULL; if( c ) c->ui_data = NULL; return TRUE; } static gboolean bee_irc_channel_chat_topic( irc_channel_t *ic, const char *new ) { struct groupchat *c = ic->data; if( c == NULL ) return FALSE; if( c->ic->acc->prpl->chat_topic == NULL ) irc_send_num( ic->irc, 482, "%s :IM network does not support channel topics", ic->name ); else { /* TODO: Need more const goodness here, sigh */ char *topic = g_strdup( new ); c->ic->acc->prpl->chat_topic( c, topic ); g_free( topic ); } /* Whatever happened, the IM module should ack the topic change. */ return FALSE; } static gboolean bee_irc_channel_chat_invite( irc_channel_t *ic, irc_user_t *iu ) { struct groupchat *c = ic->data; bee_user_t *bu = iu->bu; if( bu == NULL ) return FALSE; if( c ) { if( iu->bu->ic != c->ic ) irc_send_num( ic->irc, 482, "%s :Can't mix different IM networks in one groupchat", ic->name ); else if( c->ic->acc->prpl->chat_invite ) c->ic->acc->prpl->chat_invite( c, iu->bu->handle, NULL ); else irc_send_num( ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name ); } else if( bu->ic->acc->prpl->chat_with && strcmp( set_getstr( &ic->set, "chat_type" ), "groupchat" ) == 0 ) { ic->flags |= IRC_CHANNEL_CHAT_PICKME; iu->bu->ic->acc->prpl->chat_with( bu->ic, bu->handle ); ic->flags &= ~IRC_CHANNEL_CHAT_PICKME; } else { irc_send_num( ic->irc, 482, "%s :IM protocol does not support room invitations", ic->name ); } return TRUE; } static char *set_eval_room_account( set_t *set, char *value ); static char *set_eval_chat_type( set_t *set, char *value ); static gboolean bee_irc_channel_init( irc_channel_t *ic ) { set_t *s; set_add( &ic->set, "account", NULL, set_eval_room_account, ic ); set_add( &ic->set, "chat_type", "groupchat", set_eval_chat_type, ic ); s = set_add( &ic->set, "nick", NULL, NULL, ic ); s->flags |= SET_NULL_OK; set_add( &ic->set, "room", NULL, NULL, ic ); set_add( &ic->set, "translate_to_nicks", "true", set_eval_bool, ic ); /* chat_type == groupchat */ ic->flags |= IRC_CHANNEL_TEMP; return TRUE; } static char *set_eval_room_account( set_t *set, char *value ) { struct irc_channel *ic = set->data; account_t *acc, *oa; if( !( acc = account_get( ic->irc->b, value ) ) ) return SET_INVALID; else if( !acc->prpl->chat_join ) { irc_rootmsg( ic->irc, "Named chatrooms not supported on that account." ); return SET_INVALID; } if( set->value && ( oa = account_get( ic->irc->b, set->value ) ) && oa->prpl->chat_free_settings ) oa->prpl->chat_free_settings( oa, &ic->set ); if( acc->prpl->chat_add_settings ) acc->prpl->chat_add_settings( acc, &ic->set ); return g_strdup( acc->tag ); } static char *set_eval_chat_type( set_t *set, char *value ) { struct irc_channel *ic = set->data; if( strcmp( value, "groupchat" ) == 0 ) ic->flags |= IRC_CHANNEL_TEMP; else if( strcmp( value, "room" ) == 0 ) ic->flags &= ~IRC_CHANNEL_TEMP; else return NULL; return value; } static gboolean bee_irc_channel_free( irc_channel_t *ic ) { struct groupchat *c = ic->data; set_del( &ic->set, "account" ); set_del( &ic->set, "chat_type" ); set_del( &ic->set, "nick" ); set_del( &ic->set, "room" ); set_del( &ic->set, "translate_to_nicks" ); ic->flags &= ~IRC_CHANNEL_TEMP; /* That one still points at this channel. Don't. */ if( c ) c->ui_data = NULL; return TRUE; } const struct irc_channel_funcs irc_channel_im_chat_funcs = { bee_irc_channel_chat_privmsg, bee_irc_channel_chat_join, bee_irc_channel_chat_part, bee_irc_channel_chat_topic, bee_irc_channel_chat_invite, bee_irc_channel_init, bee_irc_channel_free, }; /* IM->IRC: File transfers */ static file_transfer_t *bee_irc_ft_in_start( bee_t *bee, bee_user_t *bu, const char *file_name, size_t file_size ) { return dccs_send_start( bu->ic, (irc_user_t *) bu->ui_data, file_name, file_size ); } static gboolean bee_irc_ft_out_start( struct im_connection *ic, file_transfer_t *ft ) { return dccs_recv_start( ft ); } static void bee_irc_ft_close( struct im_connection *ic, file_transfer_t *ft ) { return dcc_close( ft ); } static void bee_irc_ft_finished( struct im_connection *ic, file_transfer_t *file ) { dcc_file_transfer_t *df = file->priv; if( file->bytes_transferred >= file->file_size ) dcc_finish( file ); else df->proto_finished = TRUE; } const struct bee_ui_funcs irc_ui_funcs = { bee_irc_imc_connected, bee_irc_imc_disconnected, bee_irc_user_new, bee_irc_user_free, bee_irc_user_fullname, bee_irc_user_nick_hint, bee_irc_user_group, bee_irc_user_status, bee_irc_user_msg, bee_irc_user_typing, bee_irc_user_action_response, bee_irc_chat_new, bee_irc_chat_free, bee_irc_chat_log, bee_irc_chat_msg, bee_irc_chat_add_user, bee_irc_chat_remove_user, bee_irc_chat_topic, bee_irc_chat_name_hint, bee_irc_chat_invite, bee_irc_ft_in_start, bee_irc_ft_out_start, bee_irc_ft_close, bee_irc_ft_finished, }; bitlbee-3.2.1/query.c0000644000175000017500000001024712245474076014033 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ /* Questions to the user (mainly authorization requests from IM) */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" static void query_display( irc_t *irc, query_t *q ); static query_t *query_default( irc_t *irc ); query_t *query_add( irc_t *irc, struct im_connection *ic, char *question, query_callback yes, query_callback no, query_callback free, void *data ) { query_t *q = g_new0( query_t, 1 ); q->ic = ic; q->question = g_strdup( question ); q->yes = yes; q->no = no; q->free = free; q->data = data; if( strchr( irc->umode, 'b' ) != NULL ) { char *s; /* At least for the machine-parseable version, get rid of newlines to make "parsing" easier. */ for( s = q->question; *s; s ++ ) if( *s == '\r' || *s == '\n' ) *s = ' '; } if( irc->queries ) { query_t *l = irc->queries; while( l->next ) l = l->next; l->next = q; } else { irc->queries = q; } if( g_strcasecmp( set_getstr( &irc->b->set, "query_order" ), "lifo" ) == 0 || irc->queries == q ) query_display( irc, q ); return( q ); } void query_del( irc_t *irc, query_t *q ) { query_t *l; if( irc->queries == q ) { irc->queries = q->next; } else { for( l = irc->queries; l; l = l->next ) { if( l->next == q ) { l->next = q->next; break; } } if( !l ) return; /* Hrmmm... */ } g_free( q->question ); if( q->free && q->data ) q->free( q->data ); g_free( q ); } void query_del_by_conn( irc_t *irc, struct im_connection *ic ) { query_t *q, *n, *def; int count = 0; if( !ic ) return; q = irc->queries; def = query_default( irc ); while( q ) { if( q->ic == ic ) { n = q->next; query_del( irc, q ); q = n; count ++; } else { q = q->next; } } if( count > 0 ) imcb_log( ic, "Flushed %d unanswered question(s) for this connection.", count ); q = query_default( irc ); if( q && q != def ) query_display( irc, q ); } void query_answer( irc_t *irc, query_t *q, int ans ) { int disp = 0; if( !q ) { q = query_default( irc ); disp = 1; } if( ans ) { if( q->ic ) imcb_log( q->ic, "Accepted: %s", q->question ); else irc_rootmsg( irc, "Accepted: %s", q->question ); if( q->yes ) q->yes( q->data ); } else { if( q->ic ) imcb_log( q->ic, "Rejected: %s", q->question ); else irc_rootmsg( irc, "Rejected: %s", q->question ); if( q->no ) q->no( q->data ); } q->data = NULL; query_del( irc, q ); if( disp && ( q = query_default( irc ) ) ) query_display( irc, q ); } static void query_display( irc_t *irc, query_t *q ) { if( q->ic ) { imcb_log( q->ic, "New request: %s\nYou can use the \2yes\2/\2no\2 commands to accept/reject this request.", q->question ); } else { irc_rootmsg( irc, "New request: %s\nYou can use the \2yes\2/\2no\2 commands to accept/reject this request.", q->question ); } } static query_t *query_default( irc_t *irc ) { query_t *q; if( g_strcasecmp( set_getstr( &irc->b->set, "query_order" ), "fifo" ) == 0 ) q = irc->queries; else for( q = irc->queries; q && q->next; q = q->next ); return( q ); } bitlbee-3.2.1/storage.h0000644000175000017500000000472112245474076014337 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ /* Layer for retrieving and storing buddy information */ /* 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 __STORAGE_H__ #define __STORAGE_H__ typedef enum { STORAGE_OK = 0, STORAGE_NO_SUCH_USER, STORAGE_INVALID_PASSWORD, STORAGE_ALREADY_EXISTS, STORAGE_OTHER_ERROR /* Error that isn't caused by user input, such as a database that is unreachable. log() will be used for the exact error message */ } storage_status_t; typedef struct { const char *name; /* May be set to NULL if not required */ void (*init) (void); storage_status_t (*check_pass) (const char *nick, const char *password); storage_status_t (*load) (irc_t *irc, const char *password); storage_status_t (*save) (irc_t *irc, int overwrite); storage_status_t (*remove) (const char *nick, const char *password); /* May be NULL if not supported by backend */ storage_status_t (*rename) (const char *onick, const char *nnick, const char *password); } storage_t; storage_status_t storage_check_pass (const char *nick, const char *password); storage_status_t storage_load (irc_t * irc, const char *password); storage_status_t storage_save (irc_t *irc, char *password, int overwrite); storage_status_t storage_remove (const char *nick, const char *password); void register_storage_backend(storage_t *); G_GNUC_MALLOC GList *storage_init(const char *primary, char **migrate); #endif /* __STORAGE_H__ */ bitlbee-3.2.1/Makefile0000644000175000017500000001430712245474076014163 0ustar wilmerwilmer########################### ## Makefile for BitlBee ## ## ## ## Copyright 2002 Lintux ## ########################### ### DEFINITIONS -include Makefile.settings # Program variables objects = bitlbee.o dcc.o help.o ipc.o irc.o irc_im.o irc_channel.o irc_commands.o irc_send.o irc_user.o irc_util.o nick.o $(OTR_BI) query.o root_commands.o set.o storage.o $(STORAGE_OBJS) headers = bitlbee.h commands.h conf.h config.h help.h ipc.h irc.h log.h nick.h query.h set.h sock.h storage.h lib/events.h lib/ftutil.h lib/http_client.h lib/ini.h lib/json.h lib/json_util.h lib/md5.h lib/misc.h lib/proxy.h lib/sha1.h lib/ssl_client.h lib/url.h protocols/account.h protocols/bee.h protocols/ft.h protocols/nogaim.h subdirs = lib protocols ifeq ($(TARGET),i586-mingw32msvc) objects += win32.o LFLAGS+=-lws2_32 EFLAGS+=-lsecur32 OUTFILE=bitlbee.exe else objects += unix.o conf.o log.o OUTFILE=bitlbee endif # Expansion of variables subdirobjs = $(foreach dir,$(subdirs),$(dir)/$(dir).o) all: $(OUTFILE) $(OTR_PI) $(SKYPE_PI) doc systemd ifdef SKYPE_PI $(MAKE) -C protocols/skype doc endif doc: $(MAKE) -C doc uninstall: uninstall-bin uninstall-doc @echo -e '\nmake uninstall does not remove files in '$(DESTDIR)$(ETCDIR)', you can use make uninstall-etc to do that.\n' install: install-bin install-doc install-plugins install-systemd @if ! [ -d $(DESTDIR)$(CONFIG) ]; then echo -e '\nThe configuration directory $(DESTDIR)$(CONFIG) does not exist yet, don'\''t forget to create it!'; fi @if ! [ -e $(DESTDIR)$(ETCDIR)/bitlbee.conf ]; then echo -e '\nNo files are installed in '$(DESTDIR)$(ETCDIR)' by make install. Run make install-etc to do that.'; fi @echo .PHONY: install install-bin install-etc install-doc install-plugins install-systemd \ uninstall uninstall-bin uninstall-etc uninstall-doc \ all clean distclean tar $(subdirs) doc Makefile.settings: @echo @echo Run ./configure to create Makefile.settings, then rerun make @echo clean: $(subdirs) rm -f *.o $(OUTFILE) core utils/bitlbeed init/bitlbee*.service $(MAKE) -C tests clean ifdef SKYPE_PI $(MAKE) -C protocols/skype clean endif distclean: clean $(subdirs) rm -rf .depend rm -f Makefile.settings config.h bitlbee.pc find . -name 'DEADJOE' -o -name '*.orig' -o -name '*.rej' -o -name '*~' -exec rm -f {} \; @# May still be present in dirs of disabled protocols. -find . -name .depend -exec rm -rf {} \; $(MAKE) -C tests distclean check: all $(MAKE) -C tests gcov: check gcov *.c lcov: check lcov --directory . --capture --output-file bitlbee.info genhtml -o coverage bitlbee.info install-doc: $(MAKE) -C doc install ifdef SKYPE_PI $(MAKE) -C protocols/skype install-doc endif uninstall-doc: $(MAKE) -C doc uninstall ifdef SKYPE_PI $(MAKE) -C protocols/skype uninstall-doc endif install-bin: mkdir -p $(DESTDIR)$(SBINDIR) install -m 0755 $(OUTFILE) $(DESTDIR)$(SBINDIR)/$(OUTFILE) uninstall-bin: rm -f $(DESTDIR)$(SBINDIR)/$(OUTFILE) install-dev: mkdir -p $(DESTDIR)$(INCLUDEDIR) install -m 0644 config.h $(DESTDIR)$(INCLUDEDIR) for i in $(headers); do install -m 0644 $(_SRCDIR_)$$i $(DESTDIR)$(INCLUDEDIR); done mkdir -p $(DESTDIR)$(PCDIR) install -m 0644 bitlbee.pc $(DESTDIR)$(PCDIR) uninstall-dev: rm -f $(foreach hdr,$(headers),$(DESTDIR)$(INCLUDEDIR)/$(hdr)) -rmdir $(DESTDIR)$(INCLUDEDIR) rm -f $(DESTDIR)$(PCDIR)/bitlbee.pc install-etc: mkdir -p $(DESTDIR)$(ETCDIR) install -m 0644 $(_SRCDIR_)motd.txt $(DESTDIR)$(ETCDIR)/motd.txt install -m 0644 $(_SRCDIR_)bitlbee.conf $(DESTDIR)$(ETCDIR)/bitlbee.conf uninstall-etc: rm -f $(DESTDIR)$(ETCDIR)/motd.txt rm -f $(DESTDIR)$(ETCDIR)/bitlbee.conf -rmdir $(DESTDIR)$(ETCDIR) install-plugins: install-plugin-otr install-plugin-skype install-plugin-otr: ifdef OTR_PI mkdir -p $(DESTDIR)$(PLUGINDIR) install -m 0755 otr.so $(DESTDIR)$(PLUGINDIR) endif install-plugin-skype: ifdef SKYPE_PI mkdir -p $(DESTDIR)$(PLUGINDIR) install -m 0755 skype.so $(DESTDIR)$(PLUGINDIR) mkdir -p $(DESTDIR)$(ETCDIR)/../skyped $(DESTDIR)$(BINDIR) install -m 0644 $(_SRCDIR_)protocols/skype/skyped.cnf $(DESTDIR)$(ETCDIR)/../skyped/skyped.cnf install -m 0644 $(_SRCDIR_)protocols/skype/skyped.conf.dist $(DESTDIR)$(ETCDIR)/../skyped/skyped.conf install -m 0755 $(_SRCDIR_)protocols/skype/skyped.py $(DESTDIR)$(BINDIR)/skyped make -C protocols/skype install-doc endif systemd: ifdef SYSTEMDSYSTEMUNITDIR sed 's|@sbindir@|$(SBINDIR)|' init/bitlbee.service.in > init/bitlbee.service sed 's|@sbindir@|$(SBINDIR)|' init/bitlbee@.service.in > init/bitlbee@.service endif install-systemd: ifdef SYSTEMDSYSTEMUNITDIR ifeq ($(shell id -u),0) mkdir -p $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) install -m 0644 init/bitlbee.service $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) install -m 0644 init/bitlbee@.service $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) install -m 0644 init/bitlbee.socket $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) else @echo Not root, so not installing systemd files. endif endif tar: fakeroot debian/rules clean || make distclean x=$$(basename $$(pwd)); \ cd ..; \ tar czf $$x.tar.gz --exclude=debian --exclude=.bzr* --exclude=.depend $$x $(subdirs): @$(MAKE) -C $@ $(MAKECMDGOALS) $(OTR_PI): %.so: $(_SRCDIR_)%.c @echo '*' Building plugin $@ @$(CC) $(CFLAGS) -fPIC -shared $(LDFLAGS) $< -o $@ $(OTRFLAGS) $(SKYPE_PI): $(_SRCDIR_)protocols/skype/skype.c @echo '*' Building plugin skype @$(CC) $(CFLAGS) $(SKYPEFLAGS) $< -o $@ $(objects): %.o: $(_SRCDIR_)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $(CFLAGS_BITLBEE) $< -o $@ $(objects): Makefile Makefile.settings config.h $(OUTFILE): $(objects) $(subdirs) @echo '*' Linking $(OUTFILE) @$(CC) $(objects) $(subdirobjs) -o $(OUTFILE) $(LDFLAGS_BITLBEE) $(LFLAGS) $(EFLAGS) ifndef DEBUG @echo '*' Stripping $(OUTFILE) @-$(STRIP) $(OUTFILE) endif ctags: ctags `find . -name "*.c"` `find . -name "*.h"` # Using this as a bogus Make target to test if a GNU-compatible version of # make is available. helloworld: @echo Hello World # Check if we can load the helpfile. (This fails if some article is >1KB.) # If print returns a NULL pointer, the file is unusable. testhelp: doc gdb --eval-command='b main' --eval-command='r' \ --eval-command='print help_init(&global->helpfile, "doc/user-guide/help.txt")' \ $(OUTFILE) < /dev/null -include .depend/*.d # DO NOT DELETE bitlbee-3.2.1/ipc.c0000644000175000017500000006073612245474076013451 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* IPC - communication between BitlBee processes */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" #include "ipc.h" #include "commands.h" #ifndef _WIN32 #include #include #endif GSList *child_list = NULL; static int ipc_child_recv_fd = -1; static void ipc_master_takeover_fail( struct bitlbee_child *child, gboolean both ); static gboolean ipc_send_fd( int fd, int send_fd ); /* On Solaris and possibly other systems passing FDs between processes is * not possible (or at least not using the method used in this file. * Just disable that code, the functionality is not that important. */ #if defined(NO_FD_PASSING) && !defined(CMSG_SPACE) #define CMSG_SPACE(len) 1 #endif static void ipc_master_cmd_client( irc_t *data, char **cmd ) { /* Normally data points at an irc_t block, but for the IPC master this is different. We think this scary cast is better than creating a new command_t structure, just to make the compiler happy. */ struct bitlbee_child *child = (void*) data; if( child && cmd[1] ) { child->host = g_strdup( cmd[1] ); child->nick = g_strdup( cmd[2] ); child->realname = g_strdup( cmd[3] ); } /* CLIENT == On initial connects, HELLO is after /RESTARTs. */ if( g_strcasecmp( cmd[0], "CLIENT" ) == 0 ) ipc_to_children_str( "OPERMSG :Client connecting (PID=%d): %s@%s (%s)\r\n", (int) ( child ? child->pid : -1 ), cmd[2], cmd[1], cmd[3] ); } static void ipc_master_cmd_nick( irc_t *data, char **cmd ) { struct bitlbee_child *child = (void*) data; if( child && cmd[1] ) { g_free( child->nick ); child->nick = g_strdup( cmd[1] ); } } static void ipc_master_cmd_die( irc_t *data, char **cmd ) { if( global.conf->runmode == RUNMODE_FORKDAEMON ) ipc_to_children_str( "DIE\r\n" ); bitlbee_shutdown( NULL, -1, 0 ); } static void ipc_master_cmd_deaf( irc_t *data, char **cmd ) { if( global.conf->runmode == RUNMODE_DAEMON ) { b_event_remove( global.listen_watch_source_id ); close( global.listen_socket ); global.listen_socket = global.listen_watch_source_id = -1; ipc_to_children_str( "OPERMSG :Closed listening socket, waiting " "for all users to disconnect." ); } else { ipc_to_children_str( "OPERMSG :The DEAF command only works in " "normal daemon mode. Try DIE instead." ); } } void ipc_master_cmd_rehash( irc_t *data, char **cmd ) { runmode_t oldmode; oldmode = global.conf->runmode; g_free( global.conf ); global.conf = conf_load( 0, NULL ); if( global.conf->runmode != oldmode ) { log_message( LOGLVL_WARNING, "Can't change RunMode setting at runtime, restoring original setting" ); global.conf->runmode = oldmode; } if( global.conf->runmode == RUNMODE_FORKDAEMON ) ipc_to_children( cmd ); } void ipc_master_cmd_restart( irc_t *data, char **cmd ) { if( global.conf->runmode != RUNMODE_FORKDAEMON ) { /* Tell child that this is unsupported. */ return; } global.restart = -1; bitlbee_shutdown( NULL, -1, 0 ); } void ipc_master_cmd_identify( irc_t *data, char **cmd ) { struct bitlbee_child *child = (void*) data, *old = NULL; char *resp; GSList *l; if( !child || !child->nick || strcmp( child->nick, cmd[1] ) != 0 ) return; g_free( child->password ); child->password = g_strdup( cmd[2] ); for( l = child_list; l; l = l->next ) { old = l->data; if( child != old && old->nick && nick_cmp( NULL, old->nick, child->nick ) == 0 && old->password && strcmp( old->password, child->password ) == 0 ) break; } if( l && !child->to_child && !old->to_child ) { resp = "TAKEOVER INIT\r\n"; child->to_child = old; old->to_child = child; } else { /* Won't need the fd since we can't send it anywhere. */ closesocket( child->to_fd ); child->to_fd = -1; resp = "TAKEOVER NO\r\n"; } if( write( child->ipc_fd, resp, strlen( resp ) ) != strlen( resp ) ) ipc_master_free_one( child ); } void ipc_master_cmd_takeover( irc_t *data, char **cmd ) { struct bitlbee_child *child = (void*) data; char *fwd = NULL; /* Normal daemon mode doesn't keep these and has simplified code for takeovers. */ if( child == NULL ) return; if( child->to_child == NULL || g_slist_find( child_list, child->to_child ) == NULL ) return ipc_master_takeover_fail( child, FALSE ); if( strcmp( cmd[1], "AUTH" ) == 0 ) { /* New connection -> Master */ if( child->to_child && child->nick && child->to_child->nick && cmd[2] && child->password && child->to_child->password && cmd[3] && strcmp( child->nick, child->to_child->nick ) == 0 && strcmp( child->nick, cmd[2] ) == 0 && strcmp( child->password, child->to_child->password ) == 0 && strcmp( child->password, cmd[3] ) == 0 ) { ipc_send_fd( child->to_child->ipc_fd, child->to_fd ); fwd = irc_build_line( cmd ); if( write( child->to_child->ipc_fd, fwd, strlen( fwd ) ) != strlen( fwd ) ) ipc_master_free_one( child ); g_free( fwd ); } else return ipc_master_takeover_fail( child, TRUE ); } else if( strcmp( cmd[1], "DONE" ) == 0 || strcmp( cmd[1], "FAIL" ) == 0 ) { /* Old connection -> Master */ int fd; /* The copy was successful (or not), we don't need it anymore. */ closesocket( child->to_fd ); child->to_fd = -1; /* Pass it through to the other party, and flush all state. */ fwd = irc_build_line( cmd ); fd = child->to_child->ipc_fd; child->to_child->to_child = NULL; child->to_child = NULL; if( write( fd, fwd, strlen( fwd ) ) != strlen( fwd ) ) ipc_master_free_one( child ); g_free( fwd ); } } static const command_t ipc_master_commands[] = { { "client", 3, ipc_master_cmd_client, 0 }, { "hello", 0, ipc_master_cmd_client, 0 }, { "nick", 1, ipc_master_cmd_nick, 0 }, { "die", 0, ipc_master_cmd_die, 0 }, { "deaf", 0, ipc_master_cmd_deaf, 0 }, { "wallops", 1, NULL, IPC_CMD_TO_CHILDREN }, { "wall", 1, NULL, IPC_CMD_TO_CHILDREN }, { "opermsg", 1, NULL, IPC_CMD_TO_CHILDREN }, { "rehash", 0, ipc_master_cmd_rehash, 0 }, { "kill", 2, NULL, IPC_CMD_TO_CHILDREN }, { "restart", 0, ipc_master_cmd_restart, 0 }, { "identify", 2, ipc_master_cmd_identify, 0 }, { "takeover", 1, ipc_master_cmd_takeover, 0 }, { NULL } }; static void ipc_child_cmd_die( irc_t *irc, char **cmd ) { irc_abort( irc, 0, "Shutdown requested by operator" ); } static void ipc_child_cmd_wallops( irc_t *irc, char **cmd ) { if( !( irc->status & USTATUS_LOGGED_IN ) ) return; if( strchr( irc->umode, 'w' ) ) irc_write( irc, ":%s WALLOPS :%s", irc->root->host, cmd[1] ); } static void ipc_child_cmd_wall( irc_t *irc, char **cmd ) { if( !( irc->status & USTATUS_LOGGED_IN ) ) return; if( strchr( irc->umode, 's' ) ) irc_write( irc, ":%s NOTICE %s :%s", irc->root->host, irc->user->nick, cmd[1] ); } static void ipc_child_cmd_opermsg( irc_t *irc, char **cmd ) { if( !( irc->status & USTATUS_LOGGED_IN ) ) return; if( strchr( irc->umode, 'o' ) ) irc_write( irc, ":%s NOTICE %s :*** OperMsg *** %s", irc->root->host, irc->user->nick, cmd[1] ); } static void ipc_child_cmd_rehash( irc_t *irc, char **cmd ) { runmode_t oldmode; oldmode = global.conf->runmode; g_free( global.conf ); global.conf = conf_load( 0, NULL ); global.conf->runmode = oldmode; } static void ipc_child_cmd_kill( irc_t *irc, char **cmd ) { if( !( irc->status & USTATUS_LOGGED_IN ) ) return; if( nick_cmp( NULL, cmd[1], irc->user->nick ) != 0 ) return; /* It's not for us. */ irc_write( irc, ":%s!%s@%s KILL %s :%s", irc->root->nick, irc->root->nick, irc->root->host, irc->user->nick, cmd[2] ); irc_abort( irc, 0, "Killed by operator: %s", cmd[2] ); } static void ipc_child_cmd_hello( irc_t *irc, char **cmd ) { if( !( irc->status & USTATUS_LOGGED_IN ) ) ipc_to_master_str( "HELLO\r\n" ); else ipc_to_master_str( "HELLO %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname ); } static void ipc_child_cmd_takeover_yes( void *data ); static void ipc_child_cmd_takeover_no( void *data ); static void ipc_child_cmd_takeover( irc_t *irc, char **cmd ) { if( strcmp( cmd[1], "NO" ) == 0 ) { /* Master->New connection */ /* No takeover, finish the login. */ } else if( strcmp( cmd[1], "INIT" ) == 0 ) { /* Master->New connection */ if( !set_getbool( &irc->b->set, "allow_takeover" ) ) { ipc_child_cmd_takeover_no( irc ); return; } /* Offer to take over the old session, unless for some reason we're already logging into IM connections. */ if( irc->login_source_id != -1 ) query_add( irc, NULL, "You're already connected to this server. " "Would you like to take over this session?", ipc_child_cmd_takeover_yes, ipc_child_cmd_takeover_no, NULL, irc ); /* This one's going to connect to accounts, avoid that. */ b_event_remove( irc->login_source_id ); irc->login_source_id = -1; } else if( strcmp( cmd[1], "AUTH" ) == 0 ) { /* Master->Old connection */ if( irc->password && cmd[2] && cmd[3] && ipc_child_recv_fd != -1 && strcmp( irc->user->nick, cmd[2] ) == 0 && strcmp( irc->password, cmd[3] ) == 0 && set_getbool( &irc->b->set, "allow_takeover" ) ) { irc_switch_fd( irc, ipc_child_recv_fd ); irc_sync( irc ); irc_rootmsg( irc, "You've successfully taken over your old session" ); ipc_child_recv_fd = -1; ipc_to_master_str( "TAKEOVER DONE\r\n" ); } else { ipc_to_master_str( "TAKEOVER FAIL\r\n" ); } } else if( strcmp( cmd[1], "DONE" ) == 0 ) { /* Master->New connection (now taken over by old process) */ irc_free( irc ); } else if( strcmp( cmd[1], "FAIL" ) == 0 ) { /* Master->New connection */ irc_rootmsg( irc, "Could not take over old session" ); } } static void ipc_child_cmd_takeover_yes( void *data ) { irc_t *irc = data, *old = NULL; char *to_auth[] = { "TAKEOVER", "AUTH", irc->user->nick, irc->password, NULL }; /* Master->New connection */ ipc_to_master_str( "TAKEOVER AUTH %s :%s\r\n", irc->user->nick, irc->password ); if( global.conf->runmode == RUNMODE_DAEMON ) { GSList *l; for( l = irc_connection_list; l; l = l->next ) { old = l->data; if( irc != old && irc->user->nick && old->user->nick && irc->password && old->password && strcmp( irc->user->nick, old->user->nick ) == 0 && strcmp( irc->password, old->password ) == 0 ) break; } if( l == NULL ) { to_auth[1] = "FAIL"; ipc_child_cmd_takeover( irc, to_auth ); return; } } /* Drop credentials, we'll shut down soon and shouldn't overwrite any settings. */ irc_rootmsg( irc, "Trying to take over existing session" ); irc_desync( irc ); if( old ) { ipc_child_recv_fd = dup( irc->fd ); ipc_child_cmd_takeover( old, to_auth ); } /* TODO: irc_setpass() should do all of this. */ irc_setpass( irc, NULL ); irc->status &= ~USTATUS_IDENTIFIED; irc_umode_set( irc, "-R", 1 ); if( old ) irc_abort( irc, FALSE, NULL ); } static void ipc_child_cmd_takeover_no( void *data ) { ipc_to_master_str( "TAKEOVER NO\r\n" ); cmd_identify_finish( data, 0, 0 ); } static const command_t ipc_child_commands[] = { { "die", 0, ipc_child_cmd_die, 0 }, { "wallops", 1, ipc_child_cmd_wallops, 0 }, { "wall", 1, ipc_child_cmd_wall, 0 }, { "opermsg", 1, ipc_child_cmd_opermsg, 0 }, { "rehash", 0, ipc_child_cmd_rehash, 0 }, { "kill", 2, ipc_child_cmd_kill, 0 }, { "hello", 0, ipc_child_cmd_hello, 0 }, { "takeover", 1, ipc_child_cmd_takeover, 0 }, { NULL } }; gboolean ipc_child_identify( irc_t *irc ) { if( global.conf->runmode == RUNMODE_FORKDAEMON ) { #ifndef NO_FD_PASSING if( !ipc_send_fd( global.listen_socket, irc->fd ) ) ipc_child_disable(); ipc_to_master_str( "IDENTIFY %s :%s\r\n", irc->user->nick, irc->password ); #endif return TRUE; } else if( global.conf->runmode == RUNMODE_DAEMON ) { GSList *l; irc_t *old; char *to_init[] = { "TAKEOVER", "INIT", NULL }; for( l = irc_connection_list; l; l = l->next ) { old = l->data; if( irc != old && irc->user->nick && old->user->nick && irc->password && old->password && strcmp( irc->user->nick, old->user->nick ) == 0 && strcmp( irc->password, old->password ) == 0 ) break; } if( l == NULL || !set_getbool( &irc->b->set, "allow_takeover" ) || !set_getbool( &old->b->set, "allow_takeover" ) ) return FALSE; ipc_child_cmd_takeover( irc, to_init ); return TRUE; } else return FALSE; } static void ipc_master_takeover_fail( struct bitlbee_child *child, gboolean both ) { if( child == NULL || g_slist_find( child_list, child ) == NULL ) return; if( both && child->to_child != NULL ) ipc_master_takeover_fail( child->to_child, FALSE ); if( child->to_fd > -1 ) { /* Send this error only to the new connection, which can be recognised by to_fd being set. */ if( write( child->ipc_fd, "TAKEOVER FAIL\r\n", 15 ) != 15 ) { ipc_master_free_one( child ); return; } close( child->to_fd ); child->to_fd = -1; } child->to_child = NULL; } static void ipc_command_exec( void *data, char **cmd, const command_t *commands ) { int i, j; if( !cmd[0] ) return; for( i = 0; commands[i].command; i ++ ) if( g_strcasecmp( commands[i].command, cmd[0] ) == 0 ) { /* There is no typo in this line: */ for( j = 1; cmd[j]; j ++ ); j --; if( j < commands[i].required_parameters ) break; if( commands[i].flags & IPC_CMD_TO_CHILDREN ) ipc_to_children( cmd ); else commands[i].execute( data, cmd ); break; } } /* Return just one line. Returns NULL if something broke, an empty string on temporary "errors" (EAGAIN and friends). */ static char *ipc_readline( int fd, int *recv_fd ) { struct msghdr msg; struct iovec iov; char ccmsg[CMSG_SPACE(sizeof(recv_fd))]; struct cmsghdr *cmsg; char buf[513], *eol; int size; /* Because this is internal communication, it should be pretty safe to just peek at the message, find its length (by searching for the end-of-line) and then just read that message. With internal sockets and limites message length, messages should always be complete. Saves us quite a lot of code and buffering. */ size = recv( fd, buf, sizeof( buf ) - 1, MSG_PEEK ); if( size == 0 || ( size < 0 && !sockerr_again() ) ) return NULL; else if( size < 0 ) /* && sockerr_again() */ return( g_strdup( "" ) ); else buf[size] = 0; if( ( eol = strstr( buf, "\r\n" ) ) == NULL ) return NULL; else size = eol - buf + 2; iov.iov_base = buf; iov.iov_len = size; memset( &msg, 0, sizeof( msg ) ); msg.msg_iov = &iov; msg.msg_iovlen = 1; #ifndef NO_FD_PASSING msg.msg_control = ccmsg; msg.msg_controllen = sizeof( ccmsg ); #endif if( recvmsg( fd, &msg, 0 ) != size ) return NULL; #ifndef NO_FD_PASSING if( recv_fd ) for( cmsg = CMSG_FIRSTHDR( &msg ); cmsg; cmsg = CMSG_NXTHDR( &msg, cmsg ) ) if( cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS ) { /* Getting more than one shouldn't happen but if it does, make sure we don't leave them around. */ if( *recv_fd != -1 ) close( *recv_fd ); *recv_fd = *(int*) CMSG_DATA( cmsg ); /* fprintf( stderr, "pid %d received fd %d\n", (int) getpid(), *recv_fd ); */ } #endif /* fprintf( stderr, "pid %d received: %s", (int) getpid(), buf ); */ return g_strndup( buf, size - 2 ); } gboolean ipc_master_read( gpointer data, gint source, b_input_condition cond ) { struct bitlbee_child *child = data; char *buf, **cmd; if( ( buf = ipc_readline( source, &child->to_fd ) ) ) { cmd = irc_parse_line( buf ); if( cmd ) { ipc_command_exec( child, cmd, ipc_master_commands ); g_free( cmd ); } g_free( buf ); } else { ipc_master_free_fd( source ); } return TRUE; } gboolean ipc_child_read( gpointer data, gint source, b_input_condition cond ) { char *buf, **cmd; if( ( buf = ipc_readline( source, &ipc_child_recv_fd ) ) ) { cmd = irc_parse_line( buf ); if( cmd ) { ipc_command_exec( data, cmd, ipc_child_commands ); g_free( cmd ); } g_free( buf ); } else { ipc_child_disable(); } return TRUE; } void ipc_to_master( char **cmd ) { if( global.conf->runmode == RUNMODE_FORKDAEMON ) { char *s = irc_build_line( cmd ); ipc_to_master_str( "%s", s ); g_free( s ); } else if( global.conf->runmode == RUNMODE_DAEMON ) { ipc_command_exec( NULL, cmd, ipc_master_commands ); } } void ipc_to_master_str( char *format, ... ) { char *msg_buf; va_list params; va_start( params, format ); msg_buf = g_strdup_vprintf( format, params ); va_end( params ); if( strlen( msg_buf ) > 512 ) { /* Don't send it, it's too long... */ } else if( global.conf->runmode == RUNMODE_FORKDAEMON ) { if( global.listen_socket >= 0 ) if( write( global.listen_socket, msg_buf, strlen( msg_buf ) ) <= 0 ) ipc_child_disable(); } else if( global.conf->runmode == RUNMODE_DAEMON ) { char **cmd, *s; if( ( s = strchr( msg_buf, '\r' ) ) ) *s = 0; cmd = irc_parse_line( msg_buf ); ipc_command_exec( NULL, cmd, ipc_master_commands ); g_free( cmd ); } g_free( msg_buf ); } void ipc_to_children( char **cmd ) { if( global.conf->runmode == RUNMODE_FORKDAEMON ) { char *msg_buf = irc_build_line( cmd ); ipc_to_children_str( "%s", msg_buf ); g_free( msg_buf ); } else if( global.conf->runmode == RUNMODE_DAEMON ) { GSList *l; for( l = irc_connection_list; l; l = l->next ) ipc_command_exec( l->data, cmd, ipc_child_commands ); } } void ipc_to_children_str( char *format, ... ) { char *msg_buf; va_list params; va_start( params, format ); msg_buf = g_strdup_vprintf( format, params ); va_end( params ); if( strlen( msg_buf ) > 512 ) { /* Don't send it, it's too long... */ } else if( global.conf->runmode == RUNMODE_FORKDAEMON ) { int msg_len = strlen( msg_buf ); GSList *l, *next; for( l = child_list; l; l = next ) { struct bitlbee_child *c = l->data; next = l->next; if( write( c->ipc_fd, msg_buf, msg_len ) <= 0 ) ipc_master_free_one( c ); } } else if( global.conf->runmode == RUNMODE_DAEMON ) { char **cmd, *s; if( ( s = strchr( msg_buf, '\r' ) ) ) *s = 0; cmd = irc_parse_line( msg_buf ); ipc_to_children( cmd ); g_free( cmd ); } g_free( msg_buf ); } static gboolean ipc_send_fd( int fd, int send_fd ) { struct msghdr msg; struct iovec iov; char ccmsg[CMSG_SPACE(sizeof(fd))]; struct cmsghdr *cmsg; memset( &msg, 0, sizeof( msg ) ); iov.iov_base = "0x90\r\n"; /* Ja, noppes */ iov.iov_len = 6; msg.msg_iov = &iov; msg.msg_iovlen = 1; #ifndef NO_FD_PASSING msg.msg_control = ccmsg; msg.msg_controllen = sizeof( ccmsg ); cmsg = CMSG_FIRSTHDR( &msg ); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN( sizeof( send_fd ) ); *(int*)CMSG_DATA( cmsg ) = send_fd; msg.msg_controllen = cmsg->cmsg_len; #endif return sendmsg( fd, &msg, 0 ) == 6; } void ipc_master_free_one( struct bitlbee_child *c ) { GSList *l; b_event_remove( c->ipc_inpa ); closesocket( c->ipc_fd ); if( c->to_fd != -1 ) close( c->to_fd ); g_free( c->host ); g_free( c->nick ); g_free( c->realname ); g_free( c->password ); g_free( c ); child_list = g_slist_remove( child_list, c ); /* Also, if any child has a reference to this one, remove it. */ for( l = child_list; l; l = l->next ) { struct bitlbee_child *oc = l->data; if( oc->to_child == c ) ipc_master_takeover_fail( oc, FALSE ); } } void ipc_master_free_fd( int fd ) { GSList *l; struct bitlbee_child *c; for( l = child_list; l; l = l->next ) { c = l->data; if( c->ipc_fd == fd ) { ipc_master_free_one( c ); break; } } } void ipc_master_free_all() { while( child_list ) ipc_master_free_one( child_list->data ); } void ipc_child_disable() { b_event_remove( global.listen_watch_source_id ); close( global.listen_socket ); global.listen_socket = -1; } #ifndef _WIN32 char *ipc_master_save_state() { char *fn = g_strdup( "/tmp/bee-restart.XXXXXX" ); int fd = mkstemp( fn ); GSList *l; FILE *fp; int i; if( fd == -1 ) { log_message( LOGLVL_ERROR, "Could not create temporary file: %s", strerror( errno ) ); g_free( fn ); return NULL; } /* This is more convenient now. */ fp = fdopen( fd, "w" ); for( l = child_list, i = 0; l; l = l->next ) i ++; /* Number of client processes. */ fprintf( fp, "%d\n", i ); for( l = child_list; l; l = l->next ) fprintf( fp, "%d %d\n", (int) ((struct bitlbee_child*)l->data)->pid, ((struct bitlbee_child*)l->data)->ipc_fd ); if( fclose( fp ) == 0 ) { return fn; } else { unlink( fn ); g_free( fn ); return NULL; } } static gboolean new_ipc_client( gpointer data, gint serversock, b_input_condition cond ) { struct bitlbee_child *child = g_new0( struct bitlbee_child, 1 ); child->to_fd = -1; child->ipc_fd = accept( serversock, NULL, 0 ); if( child->ipc_fd == -1 ) { log_message( LOGLVL_WARNING, "Unable to accept connection on UNIX domain socket: %s", strerror(errno) ); return TRUE; } child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child ); child_list = g_slist_prepend( child_list, child ); return TRUE; } int ipc_master_listen_socket() { struct sockaddr_un un_addr; int serversock; if (!IPCSOCKET || !*IPCSOCKET) return 1; /* Clean up old socket files that were hanging around.. */ if (unlink(IPCSOCKET) == -1 && errno != ENOENT) { log_message( LOGLVL_ERROR, "Could not remove old IPC socket at %s: %s", IPCSOCKET, strerror(errno) ); return 0; } un_addr.sun_family = AF_UNIX; strcpy(un_addr.sun_path, IPCSOCKET); serversock = socket(AF_UNIX, SOCK_STREAM, PF_UNIX); if (serversock == -1) { log_message( LOGLVL_WARNING, "Unable to create UNIX socket: %s", strerror(errno) ); return 0; } if (bind(serversock, (struct sockaddr *)&un_addr, sizeof(un_addr)) == -1) { log_message( LOGLVL_WARNING, "Unable to bind UNIX socket to %s: %s", IPCSOCKET, strerror(errno) ); return 0; } if (listen(serversock, 5) == -1) { log_message( LOGLVL_WARNING, "Unable to listen on UNIX socket: %s", strerror(errno) ); return 0; } b_input_add( serversock, B_EV_IO_READ, new_ipc_client, NULL ); return 1; } #else int ipc_master_listen_socket() { /* FIXME: Open named pipe \\.\BITLBEE */ return 0; } #endif int ipc_master_load_state( char *statefile ) { struct bitlbee_child *child; FILE *fp; int i, n; if( statefile == NULL ) return 0; fp = fopen( statefile, "r" ); unlink( statefile ); /* Why do it later? :-) */ if( fp == NULL ) return 0; if( fscanf( fp, "%d", &n ) != 1 ) { log_message( LOGLVL_WARNING, "Could not import state information for child processes." ); fclose( fp ); return 0; } log_message( LOGLVL_INFO, "Importing information for %d child processes.", n ); for( i = 0; i < n; i ++ ) { child = g_new0( struct bitlbee_child, 1 ); if( fscanf( fp, "%d %d", (int *) &child->pid, &child->ipc_fd ) != 2 ) { log_message( LOGLVL_WARNING, "Unexpected end of file: Only processed %d clients.", i ); g_free( child ); fclose( fp ); return 0; } child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child ); child->to_fd = -1; child_list = g_slist_prepend( child_list, child ); } ipc_to_children_str( "HELLO\r\n" ); ipc_to_children_str( "OPERMSG :New %s master process started (version %s)\r\n", PACKAGE, BITLBEE_VERSION ); fclose( fp ); return 1; } bitlbee-3.2.1/help.c0000644000175000017500000001100212245474076013604 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2010 Wilmer van der Gaast and others * \********************************************************************/ /* Help file control */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" #include "help.h" #undef read #undef write #define BUFSIZE 1100 help_t *help_init( help_t **help, const char *helpfile ) { int i, buflen = 0; help_t *h; char *s, *t; time_t mtime; struct stat stat[1]; *help = h = g_new0 ( help_t, 1 ); h->fd = open( helpfile, O_RDONLY #ifdef _WIN32 | O_BINARY #endif ); if( h->fd == -1 ) { g_free( h ); return( *help = NULL ); } if( fstat( h->fd, stat ) != 0 ) { g_free( h ); return( *help = NULL ); } mtime = stat->st_mtime; s = g_new (char, BUFSIZE + 1 ); s[BUFSIZE] = 0; while( ( ( i = read( h->fd, s + buflen, BUFSIZE - buflen ) ) > 0 ) || ( i == 0 && strstr( s, "\n%\n" ) ) ) { buflen += i; memset( s + buflen, 0, BUFSIZE - buflen ); if( !( t = strstr( s, "\n%\n" ) ) || s[0] != '?' ) { /* FIXME: Clean up */ help_free( help ); g_free( s ); return NULL; } i = strchr( s, '\n' ) - s; if( h->title ) { h = h->next = g_new0( help_t, 1 ); } h->title = g_new ( char, i ); strncpy( h->title, s + 1, i - 1 ); h->title[i-1] = 0; h->fd = (*help)->fd; h->offset.file_offset = lseek( h->fd, 0, SEEK_CUR ) - buflen + i + 1; h->length = t - s - i - 1; h->mtime = mtime; buflen -= ( t + 3 - s ); t = g_strdup( t + 3 ); g_free( s ); s = g_renew( char, t, BUFSIZE + 1 ); s[BUFSIZE] = 0; } g_free( s ); return( *help ); } void help_free( help_t **help ) { help_t *h, *oh; int last_fd = -1; /* Weak de-dupe */ if( help == NULL || *help == NULL ) return; h = *help; while( h ) { if( h->fd != last_fd ) { close( h->fd ); last_fd = h->fd; } g_free( h->title ); h = (oh=h)->next; g_free( oh ); } *help = NULL; } char *help_get( help_t **help, char *title ) { time_t mtime; struct stat stat[1]; help_t *h; for( h = *help; h; h = h->next ) { if( h->title != NULL && g_strcasecmp( h->title, title ) == 0 ) break; } if( h && h->length > 0 ) { char *s = g_new( char, h->length + 1 ); s[h->length] = 0; if( h->fd >= 0 ) { if( fstat( h->fd, stat ) != 0 ) { g_free( s ); return NULL; } mtime = stat->st_mtime; if( mtime > h->mtime ) { g_free( s ); return NULL; } if( lseek( h->fd, h->offset.file_offset, SEEK_SET ) == -1 || read( h->fd, s, h->length ) != h->length ) return NULL; } else { strncpy( s, h->offset.mem_offset, h->length ); } return s; } return NULL; } int help_add_mem( help_t **help, const char *title, const char *content ) { help_t *h, *l = NULL; for( h = *help; h; h = h->next ) { if( g_strcasecmp( h->title, title ) == 0 ) return 0; l = h; } if( l ) h = l->next = g_new0( struct help, 1 ); else *help = h = g_new0( struct help, 1 ); h->fd = -1; h->title = g_strdup( title ); h->length = strlen( content ); h->offset.mem_offset = g_strdup( content ); return 1; } char *help_get_whatsnew( help_t **help, int old ) { GString *ret = NULL; help_t *h; int v; for( h = *help; h; h = h->next ) if( h->title != NULL && strncmp( h->title, "whatsnew", 8 ) == 0 && sscanf( h->title + 8, "%x", &v ) == 1 && v > old ) { char *s = help_get( &h, h->title ); if( ret == NULL ) ret = g_string_new( s ); else g_string_append_printf( ret, "\n\n%s", s ); g_free( s ); } return ret ? g_string_free( ret, FALSE ) : NULL; } bitlbee-3.2.1/bitlbee.c0000644000175000017500000002234512245474076014276 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ /* Main file */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" #include "commands.h" #include "protocols/nogaim.h" #include "help.h" #include "ipc.h" #include #include #include static gboolean bitlbee_io_new_client( gpointer data, gint fd, b_input_condition condition ); static gboolean try_listen( struct addrinfo *res ) { int i; global.listen_socket = socket( res->ai_family, res->ai_socktype, res->ai_protocol ); if( global.listen_socket < 0 ) { log_error( "socket" ); return FALSE; } #ifdef IPV6_V6ONLY if( res->ai_family == AF_INET6 ) { i = 0; setsockopt( global.listen_socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &i, sizeof( i ) ); } #endif /* TIME_WAIT (?) sucks.. */ i = 1; setsockopt( global.listen_socket, SOL_SOCKET, SO_REUSEADDR, &i, sizeof( i ) ); i = bind( global.listen_socket, res->ai_addr, res->ai_addrlen ); if( i == -1 ) { closesocket( global.listen_socket ); global.listen_socket = -1; log_error( "bind" ); return FALSE; } return TRUE; } int bitlbee_daemon_init() { struct addrinfo *res, hints, *addrinfo_bind; int i; FILE *fp; log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE ); log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE ); memset( &hints, 0, sizeof( hints ) ); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE #ifdef AI_ADDRCONFIG /* Disabled as it may be doing more harm than good: this flag ignores IPv6 addresses on lo (which seems reasonable), but the result is that some clients (including irssi) try to connect to ::1 and fail. | AI_ADDRCONFIG */ #endif ; i = getaddrinfo( global.conf->iface_in, global.conf->port, &hints, &addrinfo_bind ); if( i ) { log_message( LOGLVL_ERROR, "Couldn't parse address `%s': %s", global.conf->iface_in, gai_strerror(i) ); return -1; } global.listen_socket = -1; /* Try IPv6 first (which will become an IPv6+IPv4 socket). */ for( res = addrinfo_bind; res; res = res->ai_next ) if( res->ai_family == AF_INET6 && try_listen( res ) ) break; /* The rest (so IPv4, I guess). */ if( res == NULL ) for( res = addrinfo_bind; res; res = res->ai_next ) if( res->ai_family != AF_INET6 && try_listen( res ) ) break; freeaddrinfo( addrinfo_bind ); i = listen( global.listen_socket, 10 ); if( i == -1 ) { log_error( "listen" ); return( -1 ); } global.listen_watch_source_id = b_input_add( global.listen_socket, B_EV_IO_READ, bitlbee_io_new_client, NULL ); #ifndef _WIN32 if( !global.conf->nofork ) { i = fork(); if( i == -1 ) { log_error( "fork" ); return( -1 ); } else if( i != 0 ) exit( 0 ); setsid(); i = chdir( "/" ); /* Don't use i, just make gcc happy. :-/ */ if( getenv( "_BITLBEE_RESTART_STATE" ) == NULL ) for( i = 0; i < 3; i ++ ) if( close( i ) == 0 ) { /* Keep something bogus on those fd's just in case. */ open( "/dev/null", O_WRONLY ); } } #endif if( global.conf->runmode == RUNMODE_FORKDAEMON ) ipc_master_load_state( getenv( "_BITLBEE_RESTART_STATE" ) ); if( global.conf->runmode == RUNMODE_DAEMON || global.conf->runmode == RUNMODE_FORKDAEMON ) ipc_master_listen_socket(); #ifndef _WIN32 if( ( fp = fopen( global.conf->pidfile, "w" ) ) ) { fprintf( fp, "%d\n", (int) getpid() ); fclose( fp ); } else { log_message( LOGLVL_WARNING, "Warning: Couldn't write PID to `%s'", global.conf->pidfile ); } #endif if( !global.conf->nofork ) { log_link( LOGLVL_ERROR, LOGOUTPUT_SYSLOG ); log_link( LOGLVL_WARNING, LOGOUTPUT_SYSLOG ); } return( 0 ); } int bitlbee_inetd_init() { if( !irc_new( 0 ) ) return( 1 ); return( 0 ); } gboolean bitlbee_io_current_client_read( gpointer data, gint fd, b_input_condition cond ) { irc_t *irc = data; char line[513]; int st; st = read( irc->fd, line, sizeof( line ) - 1 ); if( st == 0 ) { irc_abort( irc, 1, "Connection reset by peer" ); return FALSE; } else if( st < 0 ) { if( sockerr_again() ) { return TRUE; } else { irc_abort( irc, 1, "Read error: %s", strerror( errno ) ); return FALSE; } } line[st] = '\0'; if( irc->readbuffer == NULL ) { irc->readbuffer = g_strdup( line ); } else { irc->readbuffer = g_renew( char, irc->readbuffer, strlen( irc->readbuffer ) + strlen ( line ) + 1 ); strcpy( ( irc->readbuffer + strlen( irc->readbuffer ) ), line ); } irc_process( irc ); /* Normally, irc_process() shouldn't call irc_free() but irc_abort(). Just in case: */ if( !g_slist_find( irc_connection_list, irc ) ) { log_message( LOGLVL_WARNING, "Abnormal termination of connection with fd %d.", fd ); return FALSE; } /* Very naughty, go read the RFCs! >:) */ if( irc->readbuffer && ( strlen( irc->readbuffer ) > 1024 ) ) { irc_abort( irc, 0, "Maximum line length exceeded" ); return FALSE; } return TRUE; } gboolean bitlbee_io_current_client_write( gpointer data, gint fd, b_input_condition cond ) { irc_t *irc = data; int st, size; char *temp; if( irc->sendbuffer == NULL ) return FALSE; size = strlen( irc->sendbuffer ); st = write( irc->fd, irc->sendbuffer, size ); if( st == 0 || ( st < 0 && !sockerr_again() ) ) { irc_abort( irc, 1, "Write error: %s", strerror( errno ) ); return FALSE; } else if( st < 0 ) /* && sockerr_again() */ { return TRUE; } if( st == size ) { g_free( irc->sendbuffer ); irc->sendbuffer = NULL; irc->w_watch_source_id = 0; return FALSE; } else { temp = g_strdup( irc->sendbuffer + st ); g_free( irc->sendbuffer ); irc->sendbuffer = temp; return TRUE; } } static gboolean bitlbee_io_new_client( gpointer data, gint fd, b_input_condition condition ) { socklen_t size = sizeof( struct sockaddr_in ); struct sockaddr_in conn_info; int new_socket = accept( global.listen_socket, (struct sockaddr *) &conn_info, &size ); if( new_socket == -1 ) { log_message( LOGLVL_WARNING, "Could not accept new connection: %s", strerror( errno ) ); return TRUE; } #ifndef _WIN32 if( global.conf->runmode == RUNMODE_FORKDAEMON ) { pid_t client_pid = 0; int fds[2]; if( socketpair( AF_UNIX, SOCK_STREAM, 0, fds ) == -1 ) { log_message( LOGLVL_WARNING, "Could not create IPC socket for client: %s", strerror( errno ) ); fds[0] = fds[1] = -1; } sock_make_nonblocking( fds[0] ); sock_make_nonblocking( fds[1] ); client_pid = fork(); if( client_pid > 0 && fds[0] != -1 ) { struct bitlbee_child *child; /* TODO: Stuff like this belongs in ipc.c. */ child = g_new0( struct bitlbee_child, 1 ); child->pid = client_pid; child->ipc_fd = fds[0]; child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child ); child->to_fd = -1; child_list = g_slist_append( child_list, child ); log_message( LOGLVL_INFO, "Creating new subprocess with pid %d.", (int) client_pid ); /* Close some things we don't need in the parent process. */ close( new_socket ); close( fds[1] ); } else if( client_pid == 0 ) { irc_t *irc; /* Since we're fork()ing here, let's make sure we won't get the same random numbers as the parent/siblings. */ srand( time( NULL ) ^ getpid() ); b_main_init(); /* Close the listening socket, we're a client. */ close( global.listen_socket ); b_event_remove( global.listen_watch_source_id ); /* Make the connection. */ irc = irc_new( new_socket ); /* We can store the IPC fd there now. */ global.listen_socket = fds[1]; global.listen_watch_source_id = b_input_add( fds[1], B_EV_IO_READ, ipc_child_read, irc ); close( fds[0] ); ipc_master_free_all(); } } else #endif { log_message( LOGLVL_INFO, "Creating new connection with fd %d.", new_socket ); irc_new( new_socket ); } return TRUE; } gboolean bitlbee_shutdown( gpointer data, gint fd, b_input_condition cond ) { /* Try to save data for all active connections (if desired). */ while( irc_connection_list != NULL ) irc_abort( irc_connection_list->data, TRUE, "BitlBee server shutting down" ); /* We'll only reach this point when not running in inetd mode: */ b_main_quit(); return FALSE; } bitlbee-3.2.1/motd.txt0000644000175000017500000000100312245474076014214 0ustar wilmerwilmerWelcome to the BitlBee server at %h. This server is running BitlBee version %v. The newest version can be found on http://www.bitlbee.org/ You are getting this message because the server administrator has not yet had the time (or need) to change it. For those who don't know it yet, this is not quite a regular Internet Relay Chat server. Please see the site mentioned above for more information. The developers of the Bee hope you have a buzzing time. -- BitlBee development team. ... Buzzing, haha, get it? bitlbee-3.2.1/.gdbinit0000644000175000017500000000003512245474076014135 0ustar wilmerwilmerset disable-randomization on bitlbee-3.2.1/configure0000755000175000017500000004472012245474076014434 0ustar wilmerwilmer#!/bin/sh ############################## ## Configurer for BitlBee ## ## ## ## Copyright 2004 Lintux ## ## Copyright 2002 Lucumo ## ############################## prefix='/usr/local/' bindir='$prefix/bin/' sbindir='$prefix/sbin/' etcdir='$prefix/etc/bitlbee/' mandir='$prefix/share/man/' datadir='$prefix/share/bitlbee/' config='/var/lib/bitlbee/' plugindir='$prefix/lib/bitlbee/' includedir='$prefix/include/bitlbee/' systemdsystemunitdir='' libevent='/usr/' pidfile='/var/run/bitlbee.pid' ipcsocket='' pcdir='$prefix/lib/pkgconfig' systemlibdirs="/lib64 /usr/lib64 /usr/local/lib64 /lib /usr/lib /usr/local/lib" msn=1 jabber=1 oscar=1 yahoo=1 twitter=1 purple=0 debug=0 strip=1 gcov=0 plugins=1 otr=0 skype=0 events=glib ssl=auto pie=1 arch=`uname -s` cpu=`uname -m` GLIB_MIN_VERSION=2.14 echo BitlBee configure while [ -n "$1" ]; do e="`expr "X$1" : 'X--\(.*=.*\)'`" if [ -z "$e" ]; then cat<Makefile.settings ## BitlBee settings, generated by configure PREFIX=$prefix BINDIR=$bindir SBINDIR=$sbindir ETCDIR=$etcdir MANDIR=$mandir DATADIR=$datadir PLUGINDIR=$plugindir CONFIG=$config INCLUDEDIR=$includedir PCDIR=$pcdir TARGET=$target ARCH=$arch CPU=$cpu INSTALL=install -p DESTDIR= LFLAGS= EFLAGS= EOF srcdir=$(cd $(dirname $0);pwd) currdir=$(pwd) if [ "$srcdir" != "$currdir" ]; then echo echo "configure script run from a different directory. Will create some symlinks..." if [ ! -e Makefile -o -L Makefile ]; then COPYDIRS="doc lib protocols tests utils" mkdir -p $(cd "$srcdir"; find $COPYDIRS -type d) find . -name Makefile -type l -print0 | xargs -0 rm 2> /dev/null dst="$PWD" cd "$srcdir" for i in $(find . -name Makefile -type f); do ln -s "$PWD${i#.}" "$dst/$i"; done cd "$dst" rm -rf .bzr fi echo "_SRCDIR_=$srcdir/" >> Makefile.settings CFLAGS="$CFLAGS -I${dst}" else srcdir=$PWD fi cat<config.h /* BitlBee settings, generated by configure Do *NOT* use any of these defines in your code without thinking twice, most of them can/will be overridden at run-time */ #define CONFIG "$config" #define ETCDIR "$etcdir" #define VARDIR "$datadir" #define PLUGINDIR "$plugindir" #define PIDFILE "$pidfile" #define IPCSOCKET "$ipcsocket" #define ARCH "$arch" #define CPU "$cpu" EOF if [ -n "$target" ]; then PKG_CONFIG_LIBDIR=/usr/$target/lib/pkgconfig export PKG_CONFIG_LIBDIR PATH=/usr/$target/bin:$PATH CC=$target-cc LD=$target-ld systemlibdirs="/usr/$target/lib" fi if [ "$debug" = "1" ]; then [ -z "$CFLAGS" ] && CFLAGS=-g echo 'DEBUG=1' >> Makefile.settings CFLAGS="$CFLAGS -DDEBUG" else [ -z "$CFLAGS" ] && CFLAGS="-O2 -fno-strict-aliasing" fi if [ "$pie" = "1" ]; then echo 'CFLAGS_BITLBEE=-fPIE' >> Makefile.settings echo 'LDFLAGS_BITLBEE=-pie' >> Makefile.settings fi echo CFLAGS=$CFLAGS $CPPFLAGS >> Makefile.settings echo CFLAGS+=-I${srcdir} -I${srcdir}/lib -I${srcdir}/protocols -I. >> Makefile.settings echo CFLAGS+=-DHAVE_CONFIG_H -D_GNU_SOURCE >> Makefile.settings if [ -n "$CC" ]; then CC=$CC elif type gcc > /dev/null 2> /dev/null; then CC=gcc elif type cc > /dev/null 2> /dev/null; then CC=cc else echo 'Cannot find a C compiler, aborting.' exit 1; fi echo "CC=$CC" >> Makefile.settings; if echo $CC | grep -qw gcc; then # Apparently -Wall is gcc-specific? echo CFLAGS+=-Wall >> Makefile.settings fi if [ -z "$LD" ]; then if type ld > /dev/null 2> /dev/null; then LD=ld else echo 'Cannot find ld, aborting.' exit 1; fi fi echo "LD=$LD" >> Makefile.settings if [ -z "$PKG_CONFIG" ]; then PKG_CONFIG=pkg-config fi if $PKG_CONFIG --version > /dev/null 2>/dev/null && $PKG_CONFIG glib-2.0; then if $PKG_CONFIG glib-2.0 --atleast-version=$GLIB_MIN_VERSION; then cat<>Makefile.settings EFLAGS+=`$PKG_CONFIG --libs glib-2.0 gmodule-2.0` CFLAGS+=`$PKG_CONFIG --cflags glib-2.0 gmodule-2.0` EOF else echo echo 'Found glib2 '`$PKG_CONFIG glib-2.0 --modversion`', but version '$GLIB_MIN_VERSION' or newer is required.' exit 1 fi else echo echo 'Cannot find glib2 development libraries, aborting. (Install libglib2-dev?)' exit 1 fi if [ "$events" = "libevent" ]; then if ! [ -f "${libevent}include/event.h" ]; then echo echo 'Warning: Could not find event.h, you might have to install it and/or specify' echo 'its location using the --libevent= argument. (Example: If event.h is in' echo '/usr/local/include and binaries are in /usr/local/lib: --libevent=/usr/local)' fi echo '#define EVENTS_LIBEVENT' >> config.h cat <>Makefile.settings EFLAGS+=-levent -L${libevent}lib CFLAGS+=-I${libevent}include EOF elif [ "$events" = "glib" ]; then ## We already use glib anyway, so this is all we need (and in fact not even this, but just to be sure...): echo '#define EVENTS_GLIB' >> config.h else echo echo 'ERROR: Unknown event handler specified.' exit 1 fi echo 'EVENT_HANDLER=events_'$events'.o' >> Makefile.settings detect_gnutls() { if $PKG_CONFIG --exists gnutls; then cat <>Makefile.settings EFLAGS+=`$PKG_CONFIG --libs gnutls` `libgcrypt-config --libs` CFLAGS+=`$PKG_CONFIG --cflags gnutls` `libgcrypt-config --cflags` EOF ssl=gnutls if ! pkg-config gnutls --atleast-version=2.8; then echo echo 'Warning: With GnuTLS versions <2.8, certificate expire dates are not verified.' fi ret=1 elif libgnutls-config --version > /dev/null 2> /dev/null; then cat <>Makefile.settings EFLAGS+=`libgnutls-config --libs` `libgcrypt-config --libs` CFLAGS+=`libgnutls-config --cflags` `libgcrypt-config --cflags` EOF ssl=gnutls ret=1; else ret=0; fi; } detect_nss() { if $PKG_CONFIG --version > /dev/null 2>/dev/null && $PKG_CONFIG nss; then cat<>Makefile.settings EFLAGS+=`$PKG_CONFIG --libs nss` CFLAGS+=`$PKG_CONFIG --cflags nss` EOF ssl=nss ret=1; else ret=0; fi; } RESOLV_TESTCODE=' #include #include #include #include int main() { ns_initparse( NULL, 0, NULL ); ns_parserr( NULL, ns_s_an, 0, NULL ); } ' detect_resolv_dynamic() { case "$arch" in FreeBSD ) # In FreeBSD res_* routines are present in libc.so LIBRESOLV=;; * ) LIBRESOLV=-lresolv;; esac TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX) ret=1 echo "$RESOLV_TESTCODE" | $CC -o $TMPFILE -x c - $LIBRESOLV >/dev/null 2>/dev/null if [ "$?" = "0" ]; then echo "EFLAGS+=$LIBRESOLV" >> Makefile.settings ret=0 fi rm -f $TMPFILE return $ret } detect_resolv_static() { TMPFILE=$(mktemp /tmp/bitlbee-configure.XXXXXX) ret=1 for i in $systemlibdirs; do if [ -f $i/libresolv.a ]; then echo "$RESOLV_TESTCODE" | $CC -o $TMPFILE -x c - -Wl,$i/libresolv.a >/dev/null 2>/dev/null if [ "$?" = "0" ]; then echo 'EFLAGS+='$i'/libresolv.a' >> Makefile.settings ret=0 fi fi done rm -f $TMPFILE return $ret } if [ "$ssl" = "auto" ]; then detect_gnutls if [ "$ret" = "0" ]; then # Disable NSS for now as it's known to not work very well ATM. #detect_nss : fi elif [ "$ssl" = "gnutls" ]; then detect_gnutls elif [ "$ssl" = "nss" ]; then detect_nss elif [ "$ssl" = "sspi" ]; then echo elif [ "$ssl" = "openssl" ]; then echo echo 'No detection code exists for OpenSSL. Make sure that you have a complete' echo 'installation of OpenSSL (including devel/header files) before reporting' echo 'compilation problems.' echo echo 'Also, keep in mind that the OpenSSL is, according to some people, not' echo 'completely GPL-compatible. Using GnuTLS is recommended and better supported' echo 'by us. However, on many BSD machines, OpenSSL can be considered part of the' echo 'operating system, which makes it GPL-compatible.' echo echo 'For more info, see: http://www.openssl.org/support/faq.html#LEGAL2' echo ' http://www.gnome.org/~markmc/openssl-and-the-gpl.html' echo echo 'Please note that distributing a BitlBee binary which links to OpenSSL is' echo 'probably illegal. If you want to create and distribute a binary BitlBee' echo 'package, you really should use GnuTLS instead.' echo echo 'Also, the OpenSSL license requires us to say this:' echo ' * "This product includes software developed by the OpenSSL Project' echo ' * for use in the OpenSSL Toolkit. (http://www.openssl.org/)"' echo 'EFLAGS+=-lssl -lcrypto' >> Makefile.settings ret=1 else echo echo 'ERROR: Unknown SSL library specified.' exit 1 fi if [ "$ret" = "0" ]; then echo echo 'ERROR: Could not find a suitable SSL library (GnuTLS, libnss or OpenSSL).' echo ' Please note that this script doesn'\''t have detection code for OpenSSL,' echo ' so if you want to use that, you have to select it by hand.' exit 1 fi; echo 'SSL_CLIENT=ssl_'$ssl'.o' >> Makefile.settings if detect_resolv_dynamic || detect_resolv_static; then echo '#define HAVE_RESOLV_A' >> config.h fi STORAGES="xml" for i in $STORAGES; do STORAGE_OBJS="$STORAGE_OBJS storage_$i.o" done echo "STORAGE_OBJS="$STORAGE_OBJS >> Makefile.settings if [ "$strip" = 0 ]; then echo "STRIP=\# skip strip" >> Makefile.settings; else if [ "$debug" = 1 ]; then echo echo 'Stripping binaries does not make sense when debugging. Stripping disabled.' echo 'STRIP=\# skip strip' >> Makefile.settings strip=0; elif [ -n "$STRIP" ]; then echo "STRIP=$STRIP" >> Makefile.settings; elif type strip > /dev/null 2> /dev/null; then echo "STRIP=strip" >> Makefile.settings; else echo echo 'No strip utility found, cannot remove unnecessary parts from executable.' echo 'STRIP=\# skip strip' >> Makefile.settings strip=0; fi; fi if [ -z "$systemdsystemunitdir" ]; then if $PKG_CONFIG --exists systemd; then systemdsystemunitdir=`$PKG_CONFIG --variable=systemdsystemunitdir systemd` fi fi if [ -n "$systemdsystemunitdir" ]; then if [ "$systemdsystemunitdir" != "no" ]; then echo "SYSTEMDSYSTEMUNITDIR=$systemdsystemunitdir" >> Makefile.settings fi fi if [ "$gcov" = "1" ]; then echo "CFLAGS+=--coverage" >> Makefile.settings echo "EFLAGS+=--coverage" >> Makefile.settings fi if [ "$plugins" = 0 ]; then echo '#undef WITH_PLUGINS' >> config.h else echo '#define WITH_PLUGINS' >> config.h fi otrprefix="" for i in / /usr /usr/local; do if [ -f ${i}/lib/libotr.a ]; then otrprefix=${i} break fi done if [ "$otr" = "auto" ]; then if [ -n "$otrprefix" ]; then otr=1 else otr=0 fi fi if [ "$otr" = 1 ]; then # BI == built-in echo '#define OTR_BI' >> config.h echo "EFLAGS+=-L${otrprefix}/lib -lotr" >> Makefile.settings echo "CFLAGS+=-I${otrprefix}/include" >> Makefile.settings echo 'OTR_BI=otr.o' >> Makefile.settings elif [ "$otr" = "plugin" ]; then echo '#define OTR_PI' >> config.h echo "OTRFLAGS=-L${otrprefix}/lib -lotr" >> Makefile.settings echo "CFLAGS+=-I${otrprefix}/include" >> Makefile.settings echo 'OTR_PI=otr.so' >> Makefile.settings fi if [ "$skype" = "1" -o "$skype" = "plugin" ]; then if [ "$arch" = "Darwin" ]; then echo "SKYPEFLAGS=-dynamiclib -undefined dynamic_lookup" >> Makefile.settings else echo "SKYPEFLAGS=-fPIC -shared" >> Makefile.settings fi echo 'SKYPE_PI=skype.so' >> Makefile.settings protocols_mods="$protocol_mods skype(plugin)" fi if [ ! -e doc/user-guide/help.txt ] && ! type xmlto > /dev/null 2> /dev/null; then echo echo 'WARNING: Building from an unreleased source tree without prebuilt helpfile.' echo 'Install xmlto if you want online help to work.' fi echo if [ -z "$BITLBEE_VERSION" -a -d .bzr ] && type bzr > /dev/null 2> /dev/null; then nick=`bzr nick` if [ -n "$nick" -a "$nick" != "bitlbee" ]; then nick="-$nick" else nick="" fi rev=`bzr revno` echo 'Using bzr revision #'$rev' as version number' BITLBEE_VERSION=\"bzr$nick-$rev\" fi if [ -n "$BITLBEE_VERSION" ]; then echo 'Spoofing version number: '$BITLBEE_VERSION echo '#undef BITLBEE_VERSION' >> config.h echo '#define BITLBEE_VERSION '$BITLBEE_VERSION >> config.h echo fi if ! make helloworld > /dev/null 2>&1; then echo "WARNING: Your version of make (BSD make?) does not support BitlBee's makefiles." echo "BitlBee needs GNU make to build properly. On most systems GNU make is available" echo "under the name 'gmake'." echo if gmake helloworld > /dev/null 2>&1; then echo "gmake seems to be available on your machine, great." echo else echo "gmake is not installed (or not working). Please try to install it." echo fi fi cat <bitlbee.pc prefix=$prefix includedir=$includedir Name: bitlbee Description: IRC to IM gateway Requires: glib-2.0 Version: $BITLBEE_VERSION Libs: Cflags: -I\${includedir} EOF protocols='' protoobjs='' if [ "$purple" = 0 ]; then echo '#undef WITH_PURPLE' >> config.h else if ! $PKG_CONFIG purple; then echo echo 'Cannot find libpurple development libraries, aborting. (Install libpurple-dev?)' exit 1 fi echo '#define WITH_PURPLE' >> config.h cat<>Makefile.settings EFLAGS += $($PKG_CONFIG purple --libs) PURPLE_CFLAGS += $($PKG_CONFIG purple --cflags) EOF protocols=$protocols'purple ' protoobjs=$protoobjs'purple_mod.o ' # Having both libpurple and native IM modules in one binary may # do strange things. Let's not do that. msn=0 jabber=0 oscar=0 yahoo=0 echo '#undef PACKAGE' >> config.h echo '#define PACKAGE "BitlBee-LIBPURPLE"' >> config.h if [ "$events" = "libevent" ]; then echo 'Warning: Some libpurple modules (including msn-pecan) do their event handling' echo 'outside libpurple, talking to GLib directly. At least for now the combination' echo 'libpurple + libevent is *not* recommended!' echo fi fi case "$CC" in *gcc* ) echo CFLAGS+=-MMD -MF .depend/\$@.d >> Makefile.settings for i in . lib tests protocols protocols/*/; do mkdir -p $i/.depend done esac if [ "$msn" = 0 ]; then echo '#undef WITH_MSN' >> config.h else echo '#define WITH_MSN' >> config.h protocols=$protocols'msn ' protoobjs=$protoobjs'msn_mod.o ' fi if [ "$jabber" = 0 ]; then echo '#undef WITH_JABBER' >> config.h else echo '#define WITH_JABBER' >> config.h protocols=$protocols'jabber ' protoobjs=$protoobjs'jabber_mod.o ' fi if [ "$oscar" = 0 ]; then echo '#undef WITH_OSCAR' >> config.h else echo '#define WITH_OSCAR' >> config.h protocols=$protocols'oscar ' protoobjs=$protoobjs'oscar_mod.o ' fi if [ "$yahoo" = 0 ]; then echo '#undef WITH_YAHOO' >> config.h else echo '#define WITH_YAHOO' >> config.h protocols=$protocols'yahoo ' protoobjs=$protoobjs'yahoo_mod.o ' fi if [ "$twitter" = 0 ]; then echo '#undef WITH_TWITTER' >> config.h else echo '#define WITH_TWITTER' >> config.h protocols=$protocols'twitter ' protoobjs=$protoobjs'twitter_mod.o ' fi if [ "$protocols" = "PROTOCOLS = " ]; then echo "Warning: You haven't selected any communication protocol to compile!" echo " BitlBee will run, but you will be unable to connect to IM servers!" fi echo "PROTOCOLS = $protocols" >> Makefile.settings echo "PROTOOBJS = $protoobjs" >> Makefile.settings echo Architecture: $arch case "$arch" in Linux ) ;; GNU/* ) ;; *BSD ) ;; Darwin ) echo 'STRIP=\# skip strip' >> Makefile.settings ;; IRIX ) ;; SunOS ) echo 'EFLAGS+=-lresolv -lnsl -lsocket' >> Makefile.settings echo 'STRIP=\# skip strip' >> Makefile.settings echo '#define NO_FD_PASSING' >> config.h ;; AIX ) echo 'EFLAGS+=-Wl,-brtl' >> Makefile.settings ;; CYGWIN* ) echo 'Cygwin is not officially supported.' ;; Windows ) ;; * ) echo 'We haven'\''t tested BitlBee on many platforms yet, yours is untested. YMMV.' echo 'Please report any problems at http://bugs.bitlbee.org/.' ;; esac if [ -n "$target" ]; then echo "Cross-compiling for: $target" fi echo echo 'Configuration done:' if [ "$debug" = "1" ]; then echo ' Debugging enabled.' else echo ' Debugging disabled.' fi if [ "$pie" = "1" ]; then echo ' Building PIE executable' else echo ' Building non-PIE executable' fi if [ "$strip" = "1" ]; then echo ' Binary stripping enabled.' else echo ' Binary stripping disabled.' fi if [ "$otr" = "1" ]; then echo ' Off-the-Record (OTR) Messaging enabled.' elif [ "$otr" = "plugin" ]; then echo ' Off-the-Record (OTR) Messaging enabled (as a plugin).' else echo ' Off-the-Record (OTR) Messaging disabled.' fi if [ -n "$systemdsystemunitdir" ]; then echo ' systemd enabled.' else echo ' systemd disabled.' fi echo ' Using event handler: '$events echo ' Using SSL library: '$ssl #echo ' Building with these storage backends: '$STORAGES if [ -n "$protocols" ]; then echo ' Building with these protocols:' $protocols$protocols_mods case "$protocols" in *purple*) echo " Note that BitlBee-libpurple is supported on a best-effort basis. It's" echo " not *fully* compatible with normal BitlBee. Don't use it unless you" echo " absolutely need it (i.e. support for a certain protocol or feature)." esac else echo ' Building without IM-protocol support. We wish you a lot of fun...' fi bitlbee-3.2.1/win32.c0000644000175000017500000001766612245474076013644 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ /* Main file (Windows specific part) */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" #include "commands.h" #include "protocols/nogaim.h" #include "help.h" #include #include global_t global; /* Against global namespace pollution */ static void WINAPI service_ctrl (DWORD dwControl) { switch (dwControl) { case SERVICE_CONTROL_STOP: /* FIXME */ break; case SERVICE_CONTROL_INTERROGATE: break; default: break; } } static void bitlbee_init(int argc, char **argv) { int i = -1; memset( &global, 0, sizeof( global_t ) ); b_main_init(); global.conf = conf_load( argc, argv ); if( global.conf == NULL ) return; if( global.conf->runmode == RUNMODE_INETD ) { i = bitlbee_inetd_init(); log_message( LOGLVL_INFO, "Bitlbee %s starting in inetd mode.", BITLBEE_VERSION ); } else if( global.conf->runmode == RUNMODE_DAEMON ) { i = bitlbee_daemon_init(); log_message( LOGLVL_INFO, "Bitlbee %s starting in daemon mode.", BITLBEE_VERSION ); } else { log_message( LOGLVL_INFO, "No bitlbee mode specified..."); } if( i != 0 ) return; if( access( global.conf->configdir, F_OK ) != 0 ) log_message( LOGLVL_WARNING, "The configuration directory %s does not exist. Configuration won't be saved.", global.conf->configdir ); else if( access( global.conf->configdir, 06 ) != 0 ) log_message( LOGLVL_WARNING, "Permission problem: Can't read/write from/to %s.", global.conf->configdir ); if( help_init( &(global.help), HELP_FILE ) == NULL ) log_message( LOGLVL_WARNING, "Error opening helpfile %s.", global.helpfile ); } void service_main (DWORD argc, LPTSTR *argv) { SERVICE_STATUS_HANDLE handle; SERVICE_STATUS status; handle = RegisterServiceCtrlHandler("bitlbee", service_ctrl); if (!handle) return; status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; status.dwServiceSpecificExitCode = 0; bitlbee_init(argc, argv); SetServiceStatus(handle, &status); b_main_run( ); } SERVICE_TABLE_ENTRY dispatch_table[] = { { TEXT("bitlbee"), (LPSERVICE_MAIN_FUNCTION)service_main }, { NULL, NULL } }; static int debug = 0; static void usage() { printf("Options:\n"); printf("-h Show this help message\n"); printf("-d Debug mode (simple console program)\n"); } int main( int argc, char **argv) { int i; WSADATA WSAData; nogaim_init( ); for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "-d")) debug = 1; if (!strcmp(argv[i], "-h")) { usage(); return 0; } } WSAStartup(MAKEWORD(1,1), &WSAData); if (!debug) { if (!StartServiceCtrlDispatcher(dispatch_table)) log_message( LOGLVL_ERROR, "StartServiceCtrlDispatcher failed."); } else { bitlbee_init(argc, argv); b_main_run(); } return 0; } double gettime() { return (GetTickCount() / 1000); } void conf_get_string(HKEY section, const char *name, const char *def, char **dest) { char buf[4096]; long x; if (RegQueryValue(section, name, buf, &x) == ERROR_SUCCESS) { *dest = g_strdup(buf); } else if (!def) { *dest = NULL; } else { *dest = g_strdup(def); } } void conf_get_int(HKEY section, const char *name, int def, int *dest) { char buf[20]; long x; DWORD y; if (RegQueryValue(section, name, buf, &x) == ERROR_SUCCESS) { memcpy(&y, buf, sizeof(DWORD)); *dest = y; } else { *dest = def; } } conf_t *conf_load( int argc, char *argv[] ) { conf_t *conf; HKEY key, key_main, key_proxy; char *tmp; RegOpenKey(HKEY_CURRENT_USER, "SOFTWARE\\Bitlbee", &key); RegOpenKey(key, "main", &key_main); RegOpenKey(key, "proxy", &key_proxy); memset( &global, 0, sizeof( global_t ) ); b_main_init(); conf = g_new0( conf_t,1 ); global.conf = conf; conf_get_string(key_main, "interface_in", "0.0.0.0", &global.conf->iface_in); conf_get_string(key_main, "interface_out", "0.0.0.0", &global.conf->iface_out); conf_get_string(key_main, "port", "6667", &global.conf->port); conf_get_int(key_main, "verbose", 0, &global.conf->verbose); conf_get_string(key_main, "auth_pass", "", &global.conf->auth_pass); conf_get_string(key_main, "oper_pass", "", &global.conf->oper_pass); conf_get_int(key_main, "ping_interval_timeout", 60, &global.conf->ping_interval); conf_get_string(key_main, "hostname", "localhost", &global.conf->hostname); conf_get_string(key_main, "configdir", NULL, &global.conf->configdir); conf_get_string(key_main, "motdfile", NULL, &global.conf->motdfile); conf_get_string(key_main, "helpfile", NULL, &global.helpfile); global.conf->runmode = RUNMODE_DAEMON; conf_get_int(key_main, "AuthMode", AUTHMODE_OPEN, (int *)&global.conf->authmode); conf_get_string(key_proxy, "host", "", &tmp); strcpy(proxyhost, tmp); conf_get_string(key_proxy, "user", "", &tmp); strcpy(proxyuser, tmp); conf_get_string(key_proxy, "password", "", &tmp); strcpy(proxypass, tmp); conf_get_int(key_proxy, "type", PROXY_NONE, &proxytype); conf_get_int(key_proxy, "port", 3128, &proxyport); RegCloseKey(key); RegCloseKey(key_main); RegCloseKey(key_proxy); return conf; } void conf_loaddefaults( irc_t *irc ) { HKEY key_defaults; int i; char name[4096], data[4096]; DWORD namelen = sizeof(name), datalen = sizeof(data); DWORD type; if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\Bitlbee\\defaults", &key_defaults) != ERROR_SUCCESS) { return; } for (i = 0; RegEnumValue(key_defaults, i, name, &namelen, NULL, &type, data, &datalen) == ERROR_SUCCESS; i++) { set_t *s = set_find( &irc->set, name ); if( s ) { if( s->def ) g_free( s->def ); s->def = g_strdup( data ); } namelen = sizeof(name); datalen = sizeof(data); } RegCloseKey(key_defaults); } #ifndef INADDR_NONE #define INADDR_NONE 0xffffffff #endif int inet_aton(const char *cp, struct in_addr *addr) { addr->s_addr = inet_addr(cp); return (addr->s_addr == INADDR_NONE) ? 0 : 1; } void log_error(char *msg) { log_message(LOGLVL_ERROR, "%s", msg); } void log_message(int level, char *message, ...) { HANDLE hEventSource; LPTSTR lpszStrings[2]; WORD elevel; va_list ap; va_start(ap, message); if (debug) { vprintf(message, ap); putchar('\n'); va_end(ap); return; } hEventSource = RegisterEventSource(NULL, TEXT("bitlbee")); lpszStrings[0] = TEXT("bitlbee"); lpszStrings[1] = g_strdup_vprintf(message, ap); va_end(ap); switch (level) { case LOGLVL_ERROR: elevel = EVENTLOG_ERROR_TYPE; break; case LOGLVL_WARNING: elevel = EVENTLOG_WARNING_TYPE; break; case LOGLVL_INFO: elevel = EVENTLOG_INFORMATION_TYPE; break; #ifdef DEBUG case LOGLVL_DEBUG: elevel = EVENTLOG_AUDIT_SUCCESS; break; #endif } if (hEventSource != NULL) { ReportEvent(hEventSource, elevel, 0, 0, NULL, 2, 0, lpszStrings, NULL); DeregisterEventSource(hEventSource); } g_free(lpszStrings[1]); } void log_link(int level, int output) { /* FIXME */ } struct tm * gmtime_r (const time_t *timer, struct tm *result) { struct tm *local_result; local_result = gmtime (timer); if (local_result == NULL || result == NULL) return NULL; memcpy (result, local_result, sizeof (result)); return result; } bitlbee-3.2.1/irc.c0000644000175000017500000006430312245474076013445 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* The IRC-based UI (for now the only one) */ /* 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 "ipc.h" #include "dcc.h" GSList *irc_connection_list; GSList *irc_plugins; static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond ); static char *set_eval_charset( set_t *set, char *value ); static char *set_eval_password( set_t *set, char *value ); static char *set_eval_bw_compat( set_t *set, char *value ); static char *set_eval_utf8_nicks( set_t *set, char *value ); irc_t *irc_new( int fd ) { irc_t *irc; struct sockaddr_storage sock; socklen_t socklen = sizeof( sock ); char *host = NULL, *myhost = NULL; irc_user_t *iu; GSList *l; set_t *s; bee_t *b; irc = g_new0( irc_t, 1 ); irc->fd = fd; sock_make_nonblocking( irc->fd ); irc->r_watch_source_id = b_input_add( irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc ); irc->status = USTATUS_OFFLINE; irc->last_pong = gettime(); irc->nick_user_hash = g_hash_table_new( g_str_hash, g_str_equal ); irc->watches = g_hash_table_new( g_str_hash, g_str_equal ); irc->iconv = (GIConv) -1; irc->oconv = (GIConv) -1; if( global.conf->hostname ) { myhost = g_strdup( global.conf->hostname ); } else if( getsockname( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 ) { char buf[NI_MAXHOST+1]; if( getnameinfo( (struct sockaddr *) &sock, socklen, buf, NI_MAXHOST, NULL, 0, 0 ) == 0 ) { myhost = g_strdup( ipv6_unwrap( buf ) ); } } if( getpeername( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 ) { char buf[NI_MAXHOST+1]; if( getnameinfo( (struct sockaddr *)&sock, socklen, buf, NI_MAXHOST, NULL, 0, 0 ) == 0 ) { host = g_strdup( ipv6_unwrap( buf ) ); } } if( host == NULL ) host = g_strdup( "localhost.localdomain" ); if( myhost == NULL ) myhost = g_strdup( "localhost.localdomain" ); if( global.conf->ping_interval > 0 && global.conf->ping_timeout > 0 ) irc->ping_source_id = b_timeout_add( global.conf->ping_interval * 1000, irc_userping, irc ); irc_connection_list = g_slist_append( irc_connection_list, irc ); b = irc->b = bee_new(); b->ui_data = irc; b->ui = &irc_ui_funcs; s = set_add( &b->set, "allow_takeover", "true", set_eval_bool, irc ); s = set_add( &b->set, "away_devoice", "true", set_eval_bw_compat, irc ); s->flags |= SET_HIDDEN; s = set_add( &b->set, "away_reply_timeout", "3600", set_eval_int, irc ); s = set_add( &b->set, "charset", "utf-8", set_eval_charset, irc ); s = set_add( &b->set, "default_target", "root", NULL, irc ); s = set_add( &b->set, "display_namechanges", "false", set_eval_bool, irc ); s = set_add( &b->set, "display_timestamps", "true", set_eval_bool, irc ); s = set_add( &b->set, "handle_unknown", "add_channel", NULL, irc ); s = set_add( &b->set, "last_version", "0", NULL, irc ); s->flags |= SET_HIDDEN; s = set_add( &b->set, "lcnicks", "true", set_eval_bool, irc ); s = set_add( &b->set, "nick_format", "%-@nick", NULL, irc ); s = set_add( &b->set, "offline_user_quits", "true", set_eval_bool, irc ); s = set_add( &b->set, "ops", "both", set_eval_irc_channel_ops, irc ); s = set_add( &b->set, "paste_buffer", "false", set_eval_bool, irc ); s->old_key = g_strdup( "buddy_sendbuffer" ); s = set_add( &b->set, "paste_buffer_delay", "200", set_eval_int, irc ); s->old_key = g_strdup( "buddy_sendbuffer_delay" ); s = set_add( &b->set, "password", NULL, set_eval_password, irc ); s->flags |= SET_NULL_OK | SET_PASSWORD; s = set_add( &b->set, "private", "true", set_eval_bool, irc ); s = set_add( &b->set, "query_order", "lifo", NULL, irc ); s = set_add( &b->set, "root_nick", ROOT_NICK, set_eval_root_nick, irc ); s->flags |= SET_HIDDEN; s = set_add( &b->set, "show_offline", "false", set_eval_bw_compat, irc ); s->flags |= SET_HIDDEN; s = set_add( &b->set, "simulate_netsplit", "true", set_eval_bool, irc ); s = set_add( &b->set, "timezone", "local", set_eval_timezone, irc ); s = set_add( &b->set, "to_char", ": ", set_eval_to_char, irc ); s = set_add( &b->set, "typing_notice", "false", set_eval_bool, irc ); s = set_add( &b->set, "utf8_nicks", "false", set_eval_utf8_nicks, irc ); irc->root = iu = irc_user_new( irc, ROOT_NICK ); iu->host = g_strdup( myhost ); iu->fullname = g_strdup( ROOT_FN ); iu->f = &irc_user_root_funcs; iu = irc_user_new( irc, NS_NICK ); iu->host = g_strdup( myhost ); iu->fullname = g_strdup( ROOT_FN ); iu->f = &irc_user_root_funcs; irc->user = g_new0( irc_user_t, 1 ); irc->user->host = g_strdup( host ); conf_loaddefaults( irc ); /* Evaluator sets the iconv/oconv structures. */ set_eval_charset( set_find( &b->set, "charset" ), set_getstr( &b->set, "charset" ) ); irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host, "BitlBee-IRCd initialized, please go on" ); if( isatty( irc->fd ) ) irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host, "If you read this, you most likely accidentally " "started BitlBee in inetd mode on the command line. " "You probably want to run it in (Fork)Daemon mode. " "See doc/README for more information." ); g_free( myhost ); g_free( host ); /* libpurple doesn't like fork()s after initializing itself, so this is the right moment to initialize it. */ #ifdef WITH_PURPLE nogaim_init(); #endif for( l = irc_plugins; l; l = l->next ) { irc_plugin_t *p = l->data; if( p->irc_new ) p->irc_new( irc ); } return irc; } /* immed=1 makes this function pretty much equal to irc_free(), except that this one will "log". In case the connection is already broken and we shouldn't try to write to it. */ void irc_abort( irc_t *irc, int immed, char *format, ... ) { char *reason = NULL; if( format != NULL ) { va_list params; va_start( params, format ); reason = g_strdup_vprintf( format, params ); va_end( params ); } if( reason ) irc_write( irc, "ERROR :Closing link: %s", reason ); ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n", irc->user->nick ? irc->user->nick : "(NONE)", irc->user->host, reason ? : "" ); g_free( reason ); irc_flush( irc ); if( immed ) { irc_free( irc ); } else { b_event_remove( irc->ping_source_id ); irc->ping_source_id = b_timeout_add( 1, (b_event_handler) irc_free, irc ); } } static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data ); void irc_free( irc_t * irc ) { GSList *l; irc->status |= USTATUS_SHUTDOWN; log_message( LOGLVL_INFO, "Destroying connection with fd %d", irc->fd ); if( irc->status & USTATUS_IDENTIFIED && set_getbool( &irc->b->set, "save_on_quit" ) ) if( storage_save( irc, NULL, TRUE ) != STORAGE_OK ) log_message( LOGLVL_WARNING, "Error while saving settings for user %s", irc->user->nick ); for( l = irc_plugins; l; l = l->next ) { irc_plugin_t *p = l->data; if( p->irc_free ) p->irc_free( irc ); } irc_connection_list = g_slist_remove( irc_connection_list, irc ); while( irc->queries != NULL ) query_del( irc, irc->queries ); /* This is a little bit messy: bee_free() frees all b->users which calls us back to free the corresponding irc->users. So do this before we clear the remaining ones ourselves. */ bee_free( irc->b ); while( irc->users ) irc_user_free( irc, (irc_user_t *) irc->users->data ); while( irc->channels ) irc_channel_free( irc->channels->data ); if( irc->ping_source_id > 0 ) b_event_remove( irc->ping_source_id ); if( irc->r_watch_source_id > 0 ) b_event_remove( irc->r_watch_source_id ); if( irc->w_watch_source_id > 0 ) b_event_remove( irc->w_watch_source_id ); closesocket( irc->fd ); irc->fd = -1; g_hash_table_foreach_remove( irc->nick_user_hash, irc_free_hashkey, NULL ); g_hash_table_destroy( irc->nick_user_hash ); g_hash_table_foreach_remove( irc->watches, irc_free_hashkey, NULL ); g_hash_table_destroy( irc->watches ); if( irc->iconv != (GIConv) -1 ) g_iconv_close( irc->iconv ); if( irc->oconv != (GIConv) -1 ) g_iconv_close( irc->oconv ); g_free( irc->sendbuffer ); g_free( irc->readbuffer ); g_free( irc->password ); g_free( irc ); if( global.conf->runmode == RUNMODE_INETD || global.conf->runmode == RUNMODE_FORKDAEMON || ( global.conf->runmode == RUNMODE_DAEMON && global.listen_socket == -1 && irc_connection_list == NULL ) ) b_main_quit(); } static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data ) { g_free( key ); return( TRUE ); } /* USE WITH CAUTION! Sets pass without checking */ void irc_setpass (irc_t *irc, const char *pass) { g_free (irc->password); if (pass) { irc->password = g_strdup (pass); } else { irc->password = NULL; } } static char *set_eval_password( set_t *set, char *value ) { irc_t *irc = set->data; if( irc->status & USTATUS_IDENTIFIED && value ) { irc_setpass( irc, value ); return NULL; } else { return SET_INVALID; } } static char **irc_splitlines( char *buffer ); void irc_process( irc_t *irc ) { char **lines, *temp, **cmd; int i; if( irc->readbuffer != NULL ) { lines = irc_splitlines( irc->readbuffer ); for( i = 0; *lines[i] != '\0'; i ++ ) { char *conv = NULL; /* [WvG] If the last line isn't empty, it's an incomplete line and we should wait for the rest to come in before processing it. */ if( lines[i+1] == NULL ) { temp = g_strdup( lines[i] ); g_free( irc->readbuffer ); irc->readbuffer = temp; i ++; break; } if( irc->iconv != (GIConv) -1 ) { gsize bytes_read, bytes_written; conv = g_convert_with_iconv( lines[i], -1, irc->iconv, &bytes_read, &bytes_written, NULL ); if( conv == NULL || bytes_read != strlen( lines[i] ) ) { /* GLib can do strange things if things are not in the expected charset, so let's be a little bit paranoid here: */ if( irc->status & USTATUS_LOGGED_IN ) { irc_rootmsg( irc, "Error: Charset mismatch detected. The charset " "setting is currently set to %s, so please make " "sure your IRC client will send and accept text in " "that charset, or tell BitlBee which charset to " "expect by changing the charset setting. See " "`help set charset' for more information. Your " "message was ignored.", set_getstr( &irc->b->set, "charset" ) ); g_free( conv ); conv = NULL; } else { irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host, "Warning: invalid characters received at login time." ); conv = g_strdup( lines[i] ); for( temp = conv; *temp; temp ++ ) if( *temp & 0x80 ) *temp = '?'; } } lines[i] = conv; } if( lines[i] && ( cmd = irc_parse_line( lines[i] ) ) ) { irc_exec( irc, cmd ); g_free( cmd ); } g_free( conv ); /* Shouldn't really happen, but just in case... */ if( !g_slist_find( irc_connection_list, irc ) ) { g_free( lines ); return; } } if( lines[i] != NULL ) { g_free( irc->readbuffer ); irc->readbuffer = NULL; } g_free( lines ); } } /* Splits a long string into separate lines. The array is NULL-terminated and, unless the string contains an incomplete line at the end, ends with an empty string. Could use g_strsplit() but this one does it in-place. (So yes, it's destructive.) */ static char **irc_splitlines( char *buffer ) { int i, j, n = 3; char **lines; /* Allocate n+1 elements. */ lines = g_new( char *, n + 1 ); lines[0] = buffer; /* Split the buffer in several strings, and accept any kind of line endings, * knowing that ERC on Windows may send something interesting like \r\r\n, * and surely there must be clients that think just \n is enough... */ for( i = 0, j = 0; buffer[i] != '\0'; i ++ ) { if( buffer[i] == '\r' || buffer[i] == '\n' ) { while( buffer[i] == '\r' || buffer[i] == '\n' ) buffer[i++] = '\0'; lines[++j] = buffer + i; if( j >= n ) { n *= 2; lines = g_renew( char *, lines, n + 1 ); } if( buffer[i] == '\0' ) break; } } /* NULL terminate our list. */ lines[++j] = NULL; return lines; } /* Split an IRC-style line into little parts/arguments. */ char **irc_parse_line( char *line ) { int i, j; char **cmd; /* Move the line pointer to the start of the command, skipping spaces and the optional prefix. */ if( line[0] == ':' ) { for( i = 0; line[i] && line[i] != ' '; i ++ ); line = line + i; } for( i = 0; line[i] == ' '; i ++ ); line = line + i; /* If we're already at the end of the line, return. If not, we're going to need at least one element. */ if( line[0] == '\0') return NULL; /* Count the number of char **cmd elements we're going to need. */ j = 1; for( i = 0; line[i] != '\0'; i ++ ) { if( line[i] == ' ' ) { j ++; if( line[i+1] == ':' ) break; } } /* Allocate the space we need. */ cmd = g_new( char *, j + 1 ); cmd[j] = NULL; /* Do the actual line splitting, format is: * Input: "PRIVMSG #bitlbee :foo bar" * Output: cmd[0]=="PRIVMSG", cmd[1]=="#bitlbee", cmd[2]=="foo bar", cmd[3]==NULL */ cmd[0] = line; for( i = 0, j = 0; line[i] != '\0'; i ++ ) { if( line[i] == ' ' ) { line[i] = '\0'; cmd[++j] = line + i + 1; if( line[i+1] == ':' ) { cmd[j] ++; break; } } } return cmd; } /* Converts such an array back into a command string. Mainly used for the IPC code right now. */ char *irc_build_line( char **cmd ) { int i, len; char *s; if( cmd[0] == NULL ) return NULL; len = 1; for( i = 0; cmd[i]; i ++ ) len += strlen( cmd[i] ) + 1; if( strchr( cmd[i-1], ' ' ) != NULL ) len ++; s = g_new0( char, len + 1 ); for( i = 0; cmd[i]; i ++ ) { if( cmd[i+1] == NULL && strchr( cmd[i], ' ' ) != NULL ) strcat( s, ":" ); strcat( s, cmd[i] ); if( cmd[i+1] ) strcat( s, " " ); } strcat( s, "\r\n" ); return s; } void irc_write( irc_t *irc, char *format, ... ) { va_list params; va_start( params, format ); irc_vawrite( irc, format, params ); va_end( params ); return; } void irc_write_all( int now, char *format, ... ) { va_list params; GSList *temp; va_start( params, format ); temp = irc_connection_list; while( temp != NULL ) { irc_t *irc = temp->data; if( now ) { g_free( irc->sendbuffer ); irc->sendbuffer = g_strdup( "\r\n" ); } irc_vawrite( temp->data, format, params ); if( now ) { bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE ); } temp = temp->next; } va_end( params ); return; } void irc_vawrite( irc_t *irc, char *format, va_list params ) { int size; char line[IRC_MAX_LINE+1]; /* Don't try to write anything new anymore when shutting down. */ if( irc->status & USTATUS_SHUTDOWN ) return; memset( line, 0, sizeof( line ) ); g_vsnprintf( line, IRC_MAX_LINE - 2, format, params ); strip_newlines( line ); if( irc->oconv != (GIConv) -1 ) { gsize bytes_read, bytes_written; char *conv; conv = g_convert_with_iconv( line, -1, irc->oconv, &bytes_read, &bytes_written, NULL ); if( bytes_read == strlen( line ) ) strncpy( line, conv, IRC_MAX_LINE - 2 ); g_free( conv ); } g_strlcat( line, "\r\n", IRC_MAX_LINE + 1 ); if( irc->sendbuffer != NULL ) { size = strlen( irc->sendbuffer ) + strlen( line ); irc->sendbuffer = g_renew ( char, irc->sendbuffer, size + 1 ); strcpy( ( irc->sendbuffer + strlen( irc->sendbuffer ) ), line ); } else { irc->sendbuffer = g_strdup(line); } if( irc->w_watch_source_id == 0 ) { /* If the buffer is empty we can probably write, so call the write event handler immediately. If it returns TRUE, it should be called again, so add the event to the queue. If it's FALSE, we emptied the buffer and saved ourselves some work in the event queue. */ /* Really can't be done as long as the code doesn't do error checking very well: if( bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE ) ) */ /* So just always do it via the event handler. */ irc->w_watch_source_id = b_input_add( irc->fd, B_EV_IO_WRITE, bitlbee_io_current_client_write, irc ); } return; } /* Flush sendbuffer if you can. If it fails, fail silently and let some I/O event handler clean up. */ void irc_flush( irc_t *irc ) { ssize_t n; size_t len; if( irc->sendbuffer == NULL ) return; len = strlen( irc->sendbuffer ); if( ( n = send( irc->fd, irc->sendbuffer, len, 0 ) ) == len ) { g_free( irc->sendbuffer ); irc->sendbuffer = NULL; b_event_remove( irc->w_watch_source_id ); irc->w_watch_source_id = 0; } else if( n > 0 ) { char *s = g_strdup( irc->sendbuffer + n ); g_free( irc->sendbuffer ); irc->sendbuffer = s; } /* Otherwise something went wrong and we don't currently care what the error was. We may or may not succeed later, we were just trying to flush the buffer immediately. */ } /* Meant for takeover functionality. Transfer an IRC connection to a different socket. */ void irc_switch_fd( irc_t *irc, int fd ) { irc_write( irc, "ERROR :Transferring session to a new connection" ); irc_flush( irc ); /* Write it now or forget about it forever. */ if( irc->sendbuffer ) { b_event_remove( irc->w_watch_source_id ); irc->w_watch_source_id = 0; g_free( irc->sendbuffer ); irc->sendbuffer = NULL; } b_event_remove( irc->r_watch_source_id ); closesocket( irc->fd ); irc->fd = fd; irc->r_watch_source_id = b_input_add( irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc ); } void irc_sync( irc_t *irc ) { GSList *l; irc_write( irc, ":%s!%s@%s MODE %s :+%s", irc->user->nick, irc->user->user, irc->user->host, irc->user->nick, irc->umode ); for( l = irc->channels; l; l = l->next ) { irc_channel_t *ic = l->data; if( ic->flags & IRC_CHANNEL_JOINED ) irc_send_join( ic, irc->user ); } /* We may be waiting for a PONG from the previous client connection. */ irc->pinging = FALSE; } void irc_desync( irc_t *irc ) { GSList *l; for( l = irc->channels; l; l = l->next ) irc_channel_del_user( l->data, irc->user, IRC_CDU_KICK, "Switching to old session" ); irc_write( irc, ":%s!%s@%s MODE %s :-%s", irc->user->nick, irc->user->user, irc->user->host, irc->user->nick, irc->umode ); } int irc_check_login( irc_t *irc ) { if( irc->user->user && irc->user->nick ) { if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) ) { irc_send_num( irc, 464, ":This server is password-protected." ); return 0; } else { irc_channel_t *ic; irc_user_t *iu = irc->user; irc->user = irc_user_new( irc, iu->nick ); irc->user->user = iu->user; irc->user->host = iu->host; irc->user->fullname = iu->fullname; irc->user->f = &irc_user_self_funcs; g_free( iu->nick ); g_free( iu ); if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON ) ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname ); irc->status |= USTATUS_LOGGED_IN; irc_send_login( irc ); irc->umode[0] = '\0'; irc_umode_set( irc, "+" UMODE, TRUE ); ic = irc->default_channel = irc_channel_new( irc, ROOT_CHAN ); irc_channel_set_topic( ic, CONTROL_TOPIC, irc->root ); set_setstr( &ic->set, "auto_join", "true" ); irc_channel_auto_joins( irc, NULL ); irc->root->last_channel = irc->default_channel; irc_rootmsg( irc, "Welcome to the BitlBee gateway!\n\n" "If you've never used BitlBee before, please do read the help " "information using the \x02help\x02 command. Lots of FAQs are " "answered there.\n" "If you already have an account on this server, just use the " "\x02identify\x02 command to identify yourself." ); /* This is for bug #209 (use PASS to identify to NickServ). */ if( irc->password != NULL ) { char *send_cmd[] = { "identify", g_strdup( irc->password ), NULL }; irc_setpass( irc, NULL ); root_command( irc, send_cmd ); g_free( send_cmd[1] ); } return 1; } } else { /* More information needed. */ return 0; } } /* TODO: This is a mess, but this function is a bit too complicated to be converted to something more generic. */ void irc_umode_set( irc_t *irc, const char *s, gboolean allow_priv ) { /* allow_priv: Set to 0 if s contains user input, 1 if you want to set a "privileged" mode (+o, +R, etc). */ char m[128], st = 1; const char *t; int i; char changes[512], st2 = 2; char badflag = 0; memset( m, 0, sizeof( m ) ); /* Keep track of which modes are enabled in this array. */ for( t = irc->umode; *t; t ++ ) if( *t < sizeof( m ) ) m[(int)*t] = 1; i = 0; for( t = s; *t && i < sizeof( changes ) - 3; t ++ ) { if( *t == '+' || *t == '-' ) st = *t == '+'; else if( ( st == 0 && ( !strchr( UMODES_KEEP, *t ) || allow_priv ) ) || ( st == 1 && strchr( UMODES, *t ) ) || ( st == 1 && allow_priv && strchr( UMODES_PRIV, *t ) ) ) { if( m[(int)*t] != st) { /* If we're actually making a change, remember this for the response. */ if( st != st2 ) st2 = st, changes[i++] = st ? '+' : '-'; changes[i++] = *t; } m[(int)*t] = st; } else badflag = 1; } changes[i] = '\0'; /* Convert the m array back into an umode string. */ memset( irc->umode, 0, sizeof( irc->umode ) ); for( i = 'A'; i <= 'z' && strlen( irc->umode ) < ( sizeof( irc->umode ) - 1 ); i ++ ) if( m[i] ) irc->umode[strlen(irc->umode)] = i; if( badflag ) irc_send_num( irc, 501, ":Unknown MODE flag" ); if( *changes ) irc_write( irc, ":%s!%s@%s MODE %s :%s", irc->user->nick, irc->user->user, irc->user->host, irc->user->nick, changes ); } /* Returns 0 if everything seems to be okay, a number >0 when there was a timeout. The number returned is the number of seconds we received no pongs from the user. When not connected yet, we don't ping but drop the connection when the user fails to connect in IRC_LOGIN_TIMEOUT secs. */ static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond ) { double now = gettime(); irc_t *irc = _irc; int fail = 0; if( !( irc->status & USTATUS_LOGGED_IN ) ) { if( now > ( irc->last_pong + IRC_LOGIN_TIMEOUT ) ) fail = now - irc->last_pong; } else { if( now > ( irc->last_pong + global.conf->ping_timeout ) ) { fail = now - irc->last_pong; } else { irc_write( irc, "PING :%s", IRC_PING_STRING ); } } if( fail > 0 ) { irc_abort( irc, 0, "Ping Timeout: %d seconds", fail ); return FALSE; } return TRUE; } static char *set_eval_charset( set_t *set, char *value ) { irc_t *irc = (irc_t*) set->data; char *test; gsize test_bytes = 0; GIConv ic, oc; if( g_strcasecmp( value, "none" ) == 0 ) value = g_strdup( "utf-8" ); if( ( oc = g_iconv_open( value, "utf-8" ) ) == (GIConv) -1 ) { return NULL; } /* Do a test iconv to see if the user picked an IRC-compatible charset (for example utf-16 goes *horribly* wrong). */ if( ( test = g_convert_with_iconv( " ", 1, oc, NULL, &test_bytes, NULL ) ) == NULL || test_bytes > 1 ) { g_free( test ); g_iconv_close( oc ); irc_rootmsg( irc, "Unsupported character set: The IRC protocol " "only supports 8-bit character sets." ); return NULL; } g_free( test ); if( ( ic = g_iconv_open( "utf-8", value ) ) == (GIConv) -1 ) { g_iconv_close( oc ); return NULL; } if( irc->iconv != (GIConv) -1 ) g_iconv_close( irc->iconv ); if( irc->oconv != (GIConv) -1 ) g_iconv_close( irc->oconv ); irc->iconv = ic; irc->oconv = oc; return value; } /* Mostly meant for upgrades. If one of these is set to the non-default, set show_users of all channels to something with the same effect. */ static char *set_eval_bw_compat( set_t *set, char *value ) { irc_t *irc = set->data; char *val; GSList *l; irc_rootmsg( irc, "Setting `%s' is obsolete, use the `show_users' " "channel setting instead.", set->key ); if( strcmp( set->key, "away_devoice" ) == 0 && !bool2int( value ) ) val = "online,away"; else if( strcmp( set->key, "show_offline" ) == 0 && bool2int( value ) ) val = "online@,away+,offline"; else val = "online+,away"; for( l = irc->channels; l; l = l->next ) { irc_channel_t *ic = l->data; /* No need to check channel type, if the setting doesn't exist it will just be ignored. */ set_setstr( &ic->set, "show_users", val ); } return SET_INVALID; } static char *set_eval_utf8_nicks( set_t *set, char *value ) { irc_t *irc = set->data; gboolean val = bool2int( value ); /* Do *NOT* unset this flag in the middle of a session. There will be UTF-8 nicks around already so if we suddenly disable support for them, various functions might behave strangely. */ if( val ) irc->status |= IRC_UTF8_NICKS; else if( irc->status & IRC_UTF8_NICKS ) irc_rootmsg( irc, "You need to reconnect to BitlBee for this " "change to take effect." ); return set_eval_bool( set, value ); } void register_irc_plugin( const struct irc_plugin *p ) { irc_plugins = g_slist_prepend( irc_plugins, (gpointer) p ); } bitlbee-3.2.1/storage.c0000644000175000017500000001114512245474076014330 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ /* Support for multiple storage backends */ /* Copyright (C) 2005 Jelmer Vernooij */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" extern storage_t storage_text; extern storage_t storage_xml; static GList *storage_backends = NULL; void register_storage_backend(storage_t *backend) { storage_backends = g_list_append(storage_backends, backend); } static storage_t *storage_init_single(const char *name) { GList *gl; storage_t *st = NULL; for (gl = storage_backends; gl; gl = gl->next) { st = gl->data; if (strcmp(st->name, name) == 0) break; } if (gl == NULL) return NULL; if (st->init) st->init(); return st; } GList *storage_init(const char *primary, char **migrate) { GList *ret = NULL; int i; storage_t *storage; register_storage_backend(&storage_xml); storage = storage_init_single(primary); if (storage == NULL && storage->save == NULL) return NULL; ret = g_list_append(ret, storage); for (i = 0; migrate && migrate[i]; i++) { storage = storage_init_single(migrate[i]); if (storage) ret = g_list_append(ret, storage); } return ret; } storage_status_t storage_check_pass (const char *nick, const char *password) { GList *gl; /* Loop until we don't get NO_SUCH_USER */ for (gl = global.storage; gl; gl = gl->next) { storage_t *st = gl->data; storage_status_t status; status = st->check_pass(nick, password); if (status != STORAGE_NO_SUCH_USER) return status; } return STORAGE_NO_SUCH_USER; } storage_status_t storage_load (irc_t * irc, const char *password) { GList *gl; if (irc && irc->status & USTATUS_IDENTIFIED) return STORAGE_OTHER_ERROR; /* Loop until we don't get NO_SUCH_USER */ for (gl = global.storage; gl; gl = gl->next) { storage_t *st = gl->data; storage_status_t status; status = st->load(irc, password); if (status == STORAGE_OK) { GSList *l; for( l = irc_plugins; l; l = l->next ) { irc_plugin_t *p = l->data; if( p->storage_load ) p->storage_load( irc ); } return status; } if (status != STORAGE_NO_SUCH_USER) return status; } return STORAGE_NO_SUCH_USER; } storage_status_t storage_save (irc_t *irc, char *password, int overwrite) { storage_status_t st; GSList *l; if (password != NULL) { /* Should only use this in the "register" command. */ if (irc->password || overwrite) return STORAGE_OTHER_ERROR; irc_setpass(irc, password); } else if ((irc->status & USTATUS_IDENTIFIED) == 0) { return STORAGE_NO_SUCH_USER; } st = ((storage_t *)global.storage->data)->save(irc, overwrite); for( l = irc_plugins; l; l = l->next ) { irc_plugin_t *p = l->data; if( p->storage_save ) p->storage_save( irc ); } if (password != NULL) { irc_setpass(irc, NULL); } return st; } storage_status_t storage_remove (const char *nick, const char *password) { GList *gl; storage_status_t ret = STORAGE_OK; gboolean ok = FALSE; GSList *l; /* Remove this account from all storage backends. If this isn't * done, the account will still be usable, it'd just be * loaded from a different backend. */ for (gl = global.storage; gl; gl = gl->next) { storage_t *st = gl->data; storage_status_t status; status = st->remove(nick, password); ok |= status == STORAGE_OK; if (status != STORAGE_NO_SUCH_USER && status != STORAGE_OK) ret = status; } /* If at least one succeeded, remove plugin data. */ if( ok ) for( l = irc_plugins; l; l = l->next ) { irc_plugin_t *p = l->data; if( p->storage_remove ) p->storage_remove( nick ); } return ret; } bitlbee-3.2.1/sock.h0000644000175000017500000000164612245474076013635 0ustar wilmerwilmer#include #include #ifndef _WIN32 #include #include #include #include #include #define sock_make_nonblocking(fd) fcntl(fd, F_SETFL, O_NONBLOCK) #define sock_make_blocking(fd) fcntl(fd, F_SETFL, 0) #define sockerr_again() (errno == EINPROGRESS || errno == EINTR) void closesocket( int fd ); #else # include # include # if !defined(BITLBEE_CORE) && defined(_MSC_VER) # pragma comment(lib,"bitlbee.lib") # endif # include # define sock_make_nonblocking(fd) { int non_block = 1; ioctlsocket(fd, FIONBIO, &non_block); } # define sock_make_blocking(fd) { int non_block = 0; ioctlsocket(fd, FIONBIO, &non_block); } # define sockerr_again() (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEINPROGRESS || WSAGetLastError() == WSAEWOULDBLOCK) # define ETIMEDOUT WSAETIMEDOUT # define sleep(a) Sleep(a*1000) #endif bitlbee-3.2.1/tests/0000755000175000017500000000000012245477444013662 5ustar wilmerwilmerbitlbee-3.2.1/tests/check_irc.c0000644000175000017500000000303612245474076015740 0ustar wilmerwilmer#include #include #include #include #include #include #include "irc.h" #include "testsuite.h" START_TEST(test_connect) GIOChannel *ch1, *ch2; irc_t *irc; char *raw; fail_unless(g_io_channel_pair(&ch1, &ch2)); irc = irc_new(g_io_channel_unix_get_fd(ch1)); irc_free(irc); fail_unless(g_io_channel_read_to_end(ch2, &raw, NULL, NULL) == G_IO_STATUS_NORMAL); fail_if(strcmp(raw, "") != 0); g_free(raw); END_TEST START_TEST(test_login) GIOChannel *ch1, *ch2; irc_t *irc; GError *error = NULL; char *raw; fail_unless(g_io_channel_pair(&ch1, &ch2)); g_io_channel_set_flags(ch1, G_IO_FLAG_NONBLOCK, NULL); g_io_channel_set_flags(ch2, G_IO_FLAG_NONBLOCK, NULL); irc = irc_new(g_io_channel_unix_get_fd(ch1)); fail_unless(g_io_channel_write_chars(ch2, "NICK bla\r\r\n" "USER a a a a\n", -1, NULL, NULL) == G_IO_STATUS_NORMAL); fail_unless(g_io_channel_flush(ch2, NULL) == G_IO_STATUS_NORMAL); g_main_iteration(FALSE); irc_free(irc); fail_unless(g_io_channel_read_to_end(ch2, &raw, NULL, NULL) == G_IO_STATUS_NORMAL); fail_unless(strstr(raw, "001") != NULL); fail_unless(strstr(raw, "002") != NULL); fail_unless(strstr(raw, "003") != NULL); fail_unless(strstr(raw, "004") != NULL); fail_unless(strstr(raw, "005") != NULL); g_free(raw); END_TEST Suite *irc_suite (void) { Suite *s = suite_create("IRC"); TCase *tc_core = tcase_create("Core"); suite_add_tcase (s, tc_core); tcase_add_test (tc_core, test_connect); tcase_add_test (tc_core, test_login); return s; } bitlbee-3.2.1/tests/Makefile0000644000175000017500000000141512245474076015321 0ustar wilmerwilmer-include ../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)tests/ endif LFLAGS +=-lcheck all: check ./check $(CHECKFLAGS) clean: rm -f check *.o distclean: clean main_objs = bitlbee.o conf.o dcc.o help.o ipc.o irc.o irc_channel.o irc_commands.o irc_im.o irc_send.o irc_user.o irc_util.o irc_commands.o log.o nick.o query.o root_commands.o set.o storage.o storage_xml.o test_objs = check.o check_util.o check_nick.o check_md5.o check_arc.o check_irc.o check_help.o check_user.o check_set.o check_jabber_sasl.o check_jabber_util.o check: $(test_objs) $(addprefix ../, $(main_objs)) ../protocols/protocols.o ../lib/lib.o @echo '*' Linking $@ @$(CC) $(CFLAGS) -o $@ $^ $(LFLAGS) $(EFLAGS) %.o: $(_SRCDIR_)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ bitlbee-3.2.1/tests/testsuite.h0000644000175000017500000000031012245474076016054 0ustar wilmerwilmer#ifndef __BITLBEE_CHECK_H__ #define __BITLBEE_CHECK_H__ #include "irc.h" irc_t *torture_irc(void); gboolean g_io_channel_pair(GIOChannel **ch1, GIOChannel **ch2); #endif /* __BITLBEE_CHECK_H__ */ bitlbee-3.2.1/tests/check_jabber_sasl.c0000644000175000017500000000435212245474076017434 0ustar wilmerwilmer#include #include #include #include #include #include char *sasl_get_part( char *data, char *field ); #define challenge1 "nonce=\"1669585310\",qop=\"auth\",charset=utf-8,algorithm=md5-sess," \ "something=\"Not \\\"standardized\\\"\"" #define challenge2 "realm=\"quadpoint.org\", nonce=\"NPotlQpQf9RNYodOwierkQ==\", " \ "qop=\"auth, auth-int\", charset=utf-8, algorithm=md5-sess" #define challenge3 ", realm=\"localhost\", nonce=\"LlBV2txnO8RbB5hgs3KgiQ==\", " \ "qop=\"auth, auth-int, \", ,\n, charset=utf-8, algorithm=md5-sess," struct { char *challenge; char *key; char *value; } get_part_tests[] = { { challenge1, "nonce", "1669585310" }, { challenge1, "charset", "utf-8" }, { challenge1, "harset", NULL }, { challenge1, "something", "Not \"standardized\"" }, { challenge1, "something_else", NULL }, { challenge2, "realm", "quadpoint.org", }, { challenge2, "real", NULL }, { challenge2, "qop", "auth, auth-int" }, { challenge3, "realm", "localhost" }, { challenge3, "qop", "auth, auth-int, " }, { challenge3, "charset", "utf-8" }, { NULL, NULL, NULL } }; static void check_get_part(int l) { int i; for( i = 0; get_part_tests[i].key; i++ ) { tcase_fn_start( get_part_tests[i].key, __FILE__, i ); char *res; int len; res = sasl_get_part( get_part_tests[i].challenge, get_part_tests[i].key ); if( get_part_tests[i].value == NULL ) fail_if( res != NULL, "Found key %s in %s while it shouldn't be there!", get_part_tests[i].key, get_part_tests[i].challenge ); else if( res ) fail_unless( strcmp( res, get_part_tests[i].value ) == 0, "Incorrect value for key %s in %s: %s", get_part_tests[i].key, get_part_tests[i].challenge, res ); else fail( "Could not find key %s in %s", get_part_tests[i].key, get_part_tests[i].challenge ); g_free( res ); } } Suite *jabber_sasl_suite (void) { Suite *s = suite_create("jabber/sasl"); TCase *tc_core = tcase_create("Core"); suite_add_tcase (s, tc_core); tcase_add_test (tc_core, check_get_part); return s; } bitlbee-3.2.1/tests/check_jabber_util.c0000644000175000017500000001211012245474076017436 0ustar wilmerwilmer#include #include #include #include #include #include #include "jabber/jabber.h" static struct im_connection *ic; static void check_buddy_add(int l) { struct jabber_buddy *budw1, *budw2, *budw3, *budn, *bud; budw1 = jabber_buddy_add( ic, "wilmer@gaast.net/BitlBee" ); budw1->last_msg = time( NULL ) - 100; budw2 = jabber_buddy_add( ic, "WILMER@gaast.net/Telepathy" ); budw2->priority = 2; budw2->last_msg = time( NULL ); budw3 = jabber_buddy_add( ic, "wilmer@GAAST.NET/bitlbee" ); budw3->last_msg = time( NULL ) - 200; budw3->priority = 4; /* TODO(wilmer): Shouldn't this just return budw3? */ fail_if( jabber_buddy_add( ic, "wilmer@gaast.net/Telepathy" ) != NULL ); budn = jabber_buddy_add( ic, "nekkid@lamejab.net" ); /* Shouldn't be allowed if there's already a bare JID. */ fail_if( jabber_buddy_add( ic, "nekkid@lamejab.net/Illegal" ) ); /* Case sensitivity: Case only matters after the / */ fail_if( jabber_buddy_by_jid( ic, "wilmer@gaast.net/BitlBee", 0 ) == jabber_buddy_by_jid( ic, "wilmer@gaast.net/bitlbee", 0 ) ); fail_if( jabber_buddy_by_jid( ic, "wilmer@gaast.net/telepathy", 0 ) ); fail_unless( jabber_buddy_by_jid( ic, "wilmer@gaast.net/BitlBee", 0 ) == budw1 ); fail_unless( jabber_buddy_by_jid( ic, "WILMER@GAAST.NET/BitlBee", GET_BUDDY_EXACT ) == budw1 ); fail_unless( jabber_buddy_by_jid( ic, "wilmer@GAAST.NET/BitlBee", GET_BUDDY_CREAT ) == budw1 ); fail_if( jabber_buddy_by_jid( ic, "wilmer@gaast.net", GET_BUDDY_EXACT ) ); fail_unless( jabber_buddy_by_jid( ic, "WILMER@gaast.net", 0 ) == budw3 ); /* Check O_FIRST and see if it's indeed the first item from the list. */ fail_unless( ( bud = jabber_buddy_by_jid( ic, "wilmer@gaast.net", GET_BUDDY_FIRST ) ) == budw1 ); fail_unless( bud->next == budw2 && bud->next->next == budw3 && bud->next->next->next == NULL ); /* Change the resource_select setting, now we should get a different resource. */ set_setstr( &ic->acc->set, "resource_select", "activity" ); fail_unless( jabber_buddy_by_jid( ic, "wilmer@GAAST.NET", 0 ) == budw2 ); /* Some testing of bare JID handling (which is horrible). */ fail_if( jabber_buddy_by_jid( ic, "nekkid@lamejab.net/Illegal", 0 ) ); fail_if( jabber_buddy_by_jid( ic, "NEKKID@LAMEJAB.NET/Illegal", GET_BUDDY_CREAT ) ); fail_unless( jabber_buddy_by_jid( ic, "nekkid@lamejab.net", 0 ) == budn ); fail_unless( jabber_buddy_by_jid( ic, "NEKKID@lamejab.net", GET_BUDDY_EXACT ) == budn ); fail_unless( jabber_buddy_by_jid( ic, "nekkid@LAMEJAB.NET", GET_BUDDY_CREAT ) == budn ); /* More case sensitivity testing, and see if remove works properly. */ fail_if( jabber_buddy_remove( ic, "wilmer@gaast.net/telepathy" ) ); fail_if( jabber_buddy_by_jid( ic, "wilmer@GAAST.NET/telepathy", GET_BUDDY_CREAT ) == budw2 ); fail_unless( jabber_buddy_remove( ic, "wilmer@gaast.net/Telepathy" ) ); fail_unless( jabber_buddy_remove( ic, "wilmer@gaast.net/telepathy" ) ); /* Test activity_timeout and GET_BUDDY_BARE_OK. */ fail_unless( jabber_buddy_by_jid( ic, "wilmer@gaast.net", GET_BUDDY_BARE_OK ) == budw1 ); budw1->last_msg -= 50; fail_unless( ( bud = jabber_buddy_by_jid( ic, "wilmer@gaast.net", GET_BUDDY_BARE_OK ) ) != NULL ); fail_unless( strcmp( bud->full_jid, "wilmer@gaast.net" ) == 0 ); fail_if( jabber_buddy_remove( ic, "wilmer@gaast.net" ) ); fail_unless( jabber_buddy_by_jid( ic, "wilmer@gaast.net", 0 ) == budw1 ); fail_if( jabber_buddy_remove( ic, "wilmer@gaast.net" ) ); fail_unless( jabber_buddy_remove( ic, "wilmer@gaast.net/bitlbee" ) ); fail_unless( jabber_buddy_remove( ic, "wilmer@gaast.net/BitlBee" ) ); fail_if( jabber_buddy_by_jid( ic, "wilmer@gaast.net", GET_BUDDY_BARE_OK ) ); /* Check if remove_bare() indeed gets rid of all. */ /* disable this one for now. fail_unless( jabber_buddy_remove_bare( ic, "wilmer@gaast.net" ) ); fail_if( jabber_buddy_by_jid( ic, "wilmer@gaast.net", 0 ) ); */ fail_if( jabber_buddy_remove( ic, "nekkid@lamejab.net/Illegal" ) ); fail_unless( jabber_buddy_remove( ic, "nekkid@lamejab.net" ) ); fail_if( jabber_buddy_by_jid( ic, "nekkid@lamejab.net", 0 ) ); /* Fixing a bug in this branch that caused information to get lost when removing the first full JID from a list. */ jabber_buddy_add( ic, "bugtest@google.com/A" ); jabber_buddy_add( ic, "bugtest@google.com/B" ); jabber_buddy_add( ic, "bugtest@google.com/C" ); fail_unless( jabber_buddy_remove( ic, "bugtest@google.com/A" ) ); fail_unless( jabber_buddy_remove( ic, "bugtest@google.com/B" ) ); fail_unless( jabber_buddy_remove( ic, "bugtest@google.com/C" ) ); } Suite *jabber_util_suite (void) { Suite *s = suite_create("jabber/util"); TCase *tc_core = tcase_create("Buddy"); struct jabber_data *jd; ic = g_new0( struct im_connection, 1 ); ic->acc = g_new0( account_t, 1 ); ic->proto_data = jd = g_new0( struct jabber_data, 1 ); jd->buddies = g_hash_table_new( g_str_hash, g_str_equal ); set_add( &ic->acc->set, "resource_select", "priority", NULL, ic->acc ); set_add( &ic->acc->set, "activity_timeout", "120", NULL, ic->acc ); suite_add_tcase (s, tc_core); tcase_add_test (tc_core, check_buddy_add); return s; } bitlbee-3.2.1/tests/check_help.c0000644000175000017500000000126212245474076016112 0ustar wilmerwilmer#include #include #include #include #include #include #include "help.h" START_TEST(test_help_initfree) help_t *h, *r; r = help_init(&h, "/dev/null"); fail_if(r == NULL); fail_if(r != h); help_free(&h); fail_if(h != NULL); END_TEST START_TEST(test_help_nonexistent) help_t *h, *r; r = help_init(&h, "/dev/null"); fail_unless(help_get(&h, "nonexistent") == NULL); END_TEST Suite *help_suite (void) { Suite *s = suite_create("Help"); TCase *tc_core = tcase_create("Core"); suite_add_tcase (s, tc_core); tcase_add_test (tc_core, test_help_initfree); tcase_add_test (tc_core, test_help_nonexistent); return s; } bitlbee-3.2.1/tests/check_nick.c0000644000175000017500000000324112245474076016105 0ustar wilmerwilmer#include #include #include #include #include #include "irc.h" #include "set.h" #include "misc.h" START_TEST(test_nick_strip) { int i; const char *get[] = { "test:", "test", "test\n", "thisisaveryveryveryverylongnick", "thisisave:ryveryveryverylongnick", "t::::est", "test123", "123test", "123", NULL }; const char *expected[] = { "test", "test", "test", "thisisaveryveryveryveryl", "thisisaveryveryveryveryl", "test", "test123", "_123test", "_123", NULL }; for (i = 0; get[i]; i++) { char copy[60]; strcpy(copy, get[i]); nick_strip(copy); fail_unless (strcmp(copy, expected[i]) == 0, "(%d) nick_strip broken: %s -> %s (expected: %s)", i, get[i], copy, expected[i]); } } END_TEST START_TEST(test_nick_ok_ok) { const char *nicks[] = { "foo", "bar123", "bla[", "blie]", "BreEZaH", "\\od^~", "_123", "_123test", NULL }; int i; for (i = 0; nicks[i]; i++) { fail_unless (nick_ok(nicks[i]) == 1, "nick_ok() failed: %s", nicks[i]); } } END_TEST START_TEST(test_nick_ok_notok) { const char *nicks[] = { "thisisaveryveryveryveryveryveryverylongnick", "\nillegalchar", "", "nick%", "123test", NULL }; int i; for (i = 0; nicks[i]; i++) { fail_unless (nick_ok(nicks[i]) == 0, "nick_ok() succeeded for invalid: %s", nicks[i]); } } END_TEST Suite *nick_suite (void) { Suite *s = suite_create("Nick"); TCase *tc_core = tcase_create("Core"); suite_add_tcase (s, tc_core); tcase_add_test (tc_core, test_nick_ok_ok); tcase_add_test (tc_core, test_nick_ok_notok); tcase_add_test (tc_core, test_nick_strip); return s; } bitlbee-3.2.1/tests/check_md5.c0000644000175000017500000000340212245474076015645 0ustar wilmerwilmer#include #include #include #include #include #include #include "md5.h" /* From RFC 1321 */ struct md5_test { const char *str; md5_byte_t expected[16]; } tests[] = { { "", { 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e } }, { "a", { 0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61 } }, { "abc", { 0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1, 0x7f, 0x72 } }, { "message digest", { 0xf9, 0x6b, 0x69, 0x7d, 0x7c, 0xb7, 0x93, 0x8d, 0x52, 0x5a, 0x2f, 0x31, 0xaa, 0xf1, 0x61, 0xd0 } }, { "abcdefghijklmnopqrstuvwxyz", { 0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, 0x67, 0xe1, 0x3b } }, { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", { 0xd1, 0x74, 0xab, 0x98, 0xd2, 0x77, 0xd9, 0xf5, 0xa5, 0x61, 0x1c, 0x2c, 0x9f, 0x41, 0x9d, 0x9f } }, { "12345678901234567890123456789012345678901234567890123456789012345678901234567890", { 0x57, 0xed, 0xf4, 0xa2, 0x2b, 0xe3, 0xc9, 0x55, 0xac, 0x49, 0xda, 0x2e, 0x21, 0x07, 0xb6, 0x7a } }, { NULL }, }; static void check_sums(int l) { int i; for (i = 0; tests[i].str; i++) { md5_byte_t sum[16]; tcase_fn_start (tests[i].str, __FILE__, __LINE__); md5_state_t state; int di; md5_init(&state); md5_append(&state, (const md5_byte_t *)tests[i].str, strlen(tests[i].str)); md5_finish(&state, sum); fail_if(memcmp(tests[i].expected, sum, 16) != 0, "%s failed", tests[i].str); } } Suite *md5_suite (void) { Suite *s = suite_create("MD5"); TCase *tc_core = tcase_create("Core"); suite_add_tcase (s, tc_core); tcase_add_test (tc_core, check_sums); return s; } bitlbee-3.2.1/tests/check_user.c0000644000175000017500000000352112245474076016140 0ustar wilmerwilmer#include #include #include #include #include #include "bitlbee.h" #include "testsuite.h" #if 0 START_TEST(test_user_add) irc_t *irc = torture_irc(); user_t *user; user = user_add(irc, "foo"); fail_if(user == NULL); fail_if(strcmp(user->nick, "foo") != 0); fail_unless(user_find(irc, "foo") == user); END_TEST START_TEST(test_user_add_exists) irc_t *irc = torture_irc(); user_t *user; user = user_add(irc, "foo"); fail_if(user == NULL); user = user_add(irc, "foo"); fail_unless(user == NULL); END_TEST START_TEST(test_user_add_invalid) irc_t *irc = torture_irc(); user_t *user; user = user_add(irc, ":foo"); fail_unless(user == NULL); END_TEST START_TEST(test_user_del_invalid) irc_t *irc = torture_irc(); fail_unless(user_del(irc, ":foo") == 0); END_TEST START_TEST(test_user_del) irc_t *irc = torture_irc(); user_t *user; user = user_add(irc, "foo"); fail_unless(user_del(irc, "foo") == 1); fail_unless(user_find(irc, "foo") == NULL); END_TEST START_TEST(test_user_del_nonexistant) irc_t *irc = torture_irc(); fail_unless(user_del(irc, "foo") == 0); END_TEST START_TEST(test_user_rename) irc_t *irc = torture_irc(); user_t *user; user = user_add(irc, "foo"); user_rename(irc, "foo", "bar"); fail_unless(user_find(irc, "foo") == NULL); fail_if(user_find(irc, "bar") == NULL); END_TEST #endif Suite *user_suite (void) { Suite *s = suite_create("User"); TCase *tc_core = tcase_create("Core"); suite_add_tcase (s, tc_core); #if 0 tcase_add_test (tc_core, test_user_add); tcase_add_test (tc_core, test_user_add_invalid); tcase_add_test (tc_core, test_user_add_exists); tcase_add_test (tc_core, test_user_del_invalid); tcase_add_test (tc_core, test_user_del_nonexistant); tcase_add_test (tc_core, test_user_del); tcase_add_test (tc_core, test_user_rename); #endif return s; } bitlbee-3.2.1/tests/check_arc.c0000644000175000017500000000447712245474076015742 0ustar wilmerwilmer#include #include #include #include #include #include #include "arc.h" char *password = "ArcVier"; char *clear_tests[] = { "Wie dit leest is gek :-)", "ItllBeBitlBee", "One more boring password", "Hoi hoi", NULL }; static void check_codec(int l) { int i; for( i = 0; clear_tests[i]; i++ ) { tcase_fn_start (clear_tests[i], __FILE__, __LINE__); unsigned char *crypted; char *decrypted; int len; len = arc_encode( clear_tests[i], 0, &crypted, password, 12 ); len = arc_decode( crypted, len, &decrypted, password ); fail_if( strcmp( clear_tests[i], decrypted ) != 0, "%s didn't decrypt back properly", clear_tests[i] ); g_free( crypted ); g_free( decrypted ); } } struct { unsigned char crypted[30]; int len; char *decrypted; } decrypt_tests[] = { /* One block with padding. */ { { 0x3f, 0x79, 0xb0, 0xf5, 0x91, 0x56, 0xd2, 0x1b, 0xd1, 0x4b, 0x67, 0xac, 0xb1, 0x31, 0xc9, 0xdb, 0xf9, 0xaa }, 18, "short pass" }, /* Two blocks with padding. */ { { 0xf9, 0xa6, 0xec, 0x5d, 0xc7, 0x06, 0xb8, 0x6b, 0x63, 0x9f, 0x2d, 0xb5, 0x7d, 0xaa, 0x32, 0xbb, 0xd8, 0x08, 0xfd, 0x81, 0x2e, 0xca, 0xb4, 0xd7, 0x2f, 0x36, 0x9c, 0xac, 0xa0, 0xbc }, 30, "longer password" }, /* This string is exactly two "blocks" long, to make sure unpadded strings also decrypt properly. */ { { 0x95, 0x4d, 0xcf, 0x4d, 0x5e, 0x6c, 0xcf, 0xef, 0xb9, 0x80, 0x00, 0xef, 0x25, 0xe9, 0x17, 0xf6, 0x29, 0x6a, 0x82, 0x79, 0x1c, 0xca, 0x68, 0xb5, 0x4e, 0xd0, 0xc1, 0x41, 0x8e, 0xe6 }, 30, "OSCAR is really creepy.." }, { "", 0, NULL } }; static void check_decod(int l) { int i; for( i = 0; decrypt_tests[i].len; i++ ) { tcase_fn_start (decrypt_tests[i].decrypted, __FILE__, __LINE__); char *decrypted; int len; len = arc_decode( decrypt_tests[i].crypted, decrypt_tests[i].len, &decrypted, password ); fail_if( strcmp( decrypt_tests[i].decrypted, decrypted ) != 0, "`%s' didn't decrypt properly", decrypt_tests[i].decrypted ); g_free( decrypted ); } } Suite *arc_suite (void) { Suite *s = suite_create("ArcFour"); TCase *tc_core = tcase_create("Core"); suite_add_tcase (s, tc_core); tcase_add_test (tc_core, check_codec); tcase_add_test (tc_core, check_decod); return s; } bitlbee-3.2.1/tests/check_set.c0000644000175000017500000000633612245474076015764 0ustar wilmerwilmer#include #include #include #include #include #include "set.h" #include "testsuite.h" START_TEST(test_set_add) void *data = "data"; set_t *s = NULL, *t; t = set_add(&s, "name", "default", NULL, data); fail_unless(s == t); fail_unless(t->data == data); fail_unless(strcmp(t->def, "default") == 0); END_TEST START_TEST(test_set_add_existing) void *data = "data"; set_t *s = NULL, *t; t = set_add(&s, "name", "default", NULL, data); t = set_add(&s, "name", "newdefault", NULL, data); fail_unless(s == t); fail_unless(strcmp(t->def, "newdefault") == 0); END_TEST START_TEST(test_set_find_unknown) set_t *s = NULL, *t; fail_unless (set_find(&s, "foo") == NULL); END_TEST START_TEST(test_set_find) void *data = "data"; set_t *s = NULL, *t; t = set_add(&s, "name", "default", NULL, data); fail_unless(s == t); fail_unless(set_find(&s, "name") == t); END_TEST START_TEST(test_set_get_str_default) void *data = "data"; set_t *s = NULL, *t; t = set_add(&s, "name", "default", NULL, data); fail_unless(s == t); fail_unless(strcmp(set_getstr(&s, "name"), "default") == 0); END_TEST START_TEST(test_set_get_bool_default) void *data = "data"; set_t *s = NULL, *t; t = set_add(&s, "name", "true", NULL, data); fail_unless(s == t); fail_unless(set_getbool(&s, "name")); END_TEST START_TEST(test_set_get_bool_integer) void *data = "data"; set_t *s = NULL, *t; t = set_add(&s, "name", "3", NULL, data); fail_unless(s == t); fail_unless(set_getbool(&s, "name") == 3); END_TEST START_TEST(test_set_get_bool_unknown) set_t *s = NULL; fail_unless(set_getbool(&s, "name") == 0); END_TEST START_TEST(test_set_get_str_value) void *data = "data"; set_t *s = NULL, *t; t = set_add(&s, "name", "default", NULL, data); set_setstr(&s, "name", "foo"); fail_unless(strcmp(set_getstr(&s, "name"), "foo") == 0); END_TEST START_TEST(test_set_get_str_unknown) set_t *s = NULL; fail_unless(set_getstr(&s, "name") == NULL); END_TEST START_TEST(test_setint) void *data = "data"; set_t *s = NULL, *t; t = set_add(&s, "name", "10", NULL, data); set_setint(&s, "name", 3); fail_unless(set_getint(&s, "name") == 3); END_TEST START_TEST(test_setstr) void *data = "data"; set_t *s = NULL, *t; t = set_add(&s, "name", "foo", NULL, data); set_setstr(&s, "name", "bloe"); fail_unless(strcmp(set_getstr(&s, "name"), "bloe") == 0); END_TEST START_TEST(test_set_get_int_unknown) set_t *s = NULL; fail_unless(set_getint(&s, "foo") == 0); END_TEST Suite *set_suite (void) { Suite *s = suite_create("Set"); TCase *tc_core = tcase_create("Core"); suite_add_tcase (s, tc_core); tcase_add_test (tc_core, test_set_add); tcase_add_test (tc_core, test_set_add_existing); tcase_add_test (tc_core, test_set_find_unknown); tcase_add_test (tc_core, test_set_find); tcase_add_test (tc_core, test_set_get_str_default); tcase_add_test (tc_core, test_set_get_str_value); tcase_add_test (tc_core, test_set_get_str_unknown); tcase_add_test (tc_core, test_set_get_bool_default); tcase_add_test (tc_core, test_set_get_bool_integer); tcase_add_test (tc_core, test_set_get_bool_unknown); tcase_add_test (tc_core, test_set_get_int_unknown); tcase_add_test (tc_core, test_setint); tcase_add_test (tc_core, test_setstr); return s; } bitlbee-3.2.1/tests/check_util.c0000644000175000017500000001141612245474076016141 0ustar wilmerwilmer#include #include #include #include #include #include "irc.h" #include "set.h" #include "misc.h" #include "url.h" START_TEST(test_strip_linefeed) { int i; const char *get[] = { "Test", "Test\r", "Test\rX\r", NULL }; const char *expected[] = { "Test", "Test", "TestX", NULL }; for (i = 0; get[i]; i++) { char copy[20]; strcpy(copy, get[i]); strip_linefeed(copy); fail_unless (strcmp(copy, expected[i]) == 0, "(%d) strip_linefeed broken: %s -> %s (expected: %s)", i, get[i], copy, expected[i]); } } END_TEST START_TEST(test_strip_newlines) { int i; const char *get[] = { "Test", "Test\r\n", "Test\nX\n", NULL }; const char *expected[] = { "Test", "Test ", "Test X ", NULL }; for (i = 0; get[i]; i++) { char copy[20], *ret; strcpy(copy, get[i]); ret = strip_newlines(copy); fail_unless (strcmp(copy, expected[i]) == 0, "(%d) strip_newlines broken: %s -> %s (expected: %s)", i, get[i], copy, expected[i]); fail_unless (copy == ret, "Original string not returned"); } } END_TEST START_TEST(test_set_url_http) url_t url; fail_if (0 == url_set(&url, "http://host/")); fail_unless (!strcmp(url.host, "host")); fail_unless (!strcmp(url.file, "/")); fail_unless (!strcmp(url.user, "")); fail_unless (!strcmp(url.pass, "")); fail_unless (url.proto == PROTO_HTTP); fail_unless (url.port == 80); END_TEST START_TEST(test_set_url_https) url_t url; fail_if (0 == url_set(&url, "https://ahost/AimeeMann")); fail_unless (!strcmp(url.host, "ahost")); fail_unless (!strcmp(url.file, "/AimeeMann")); fail_unless (!strcmp(url.user, "")); fail_unless (!strcmp(url.pass, "")); fail_unless (url.proto == PROTO_HTTPS); fail_unless (url.port == 443); END_TEST START_TEST(test_set_url_port) url_t url; fail_if (0 == url_set(&url, "https://ahost:200/Lost/In/Space")); fail_unless (!strcmp(url.host, "ahost")); fail_unless (!strcmp(url.file, "/Lost/In/Space")); fail_unless (!strcmp(url.user, "")); fail_unless (!strcmp(url.pass, "")); fail_unless (url.proto == PROTO_HTTPS); fail_unless (url.port == 200); END_TEST START_TEST(test_set_url_username) url_t url; fail_if (0 == url_set(&url, "socks4://user@ahost/Space")); fail_unless (!strcmp(url.host, "ahost")); fail_unless (!strcmp(url.file, "/Space")); fail_unless (!strcmp(url.user, "user")); fail_unless (!strcmp(url.pass, "")); fail_unless (url.proto == PROTO_SOCKS4); fail_unless (url.port == 1080); END_TEST START_TEST(test_set_url_username_pwd) url_t url; fail_if (0 == url_set(&url, "socks5://user:pass@ahost/")); fail_unless (!strcmp(url.host, "ahost")); fail_unless (!strcmp(url.file, "/")); fail_unless (!strcmp(url.user, "user")); fail_unless (!strcmp(url.pass, "pass")); fail_unless (url.proto == PROTO_SOCKS5); fail_unless (url.port == 1080); END_TEST struct { char *orig; int line_len; char *wrapped; } word_wrap_tests[] = { { "Line-wrapping is not as easy as it seems?", 16, "Line-wrapping is\nnot as easy as\nit seems?" }, { "Line-wrapping is not as easy as it seems?", 8, "Line-\nwrapping\nis not\nas easy\nas it\nseems?" }, { "Line-wrapping is\nnot as easy as it seems?", 8, "Line-\nwrapping\nis\nnot as\neasy as\nit\nseems?" }, { "a aa aaa aaaa aaaaa aaaaaa aaaaaaa aaaaaaaa", 5, "a aa\naaa\naaaa\naaaaa\naaaaa\na\naaaaa\naa\naaaaa\naaa", }, { "aaaaaaaa aaaaaaa aaaaaa aaaaa aaaa aaa aa a", 5, "aaaaa\naaa\naaaaa\naa\naaaaa\na\naaaaa\naaaa\naaa\naa a", }, { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 5, "aaaaa\naaaaa\naaaaa\naaaaa\naaaaa\naaaaa\naaaaa\na", }, { NULL } }; START_TEST(test_word_wrap) int i; for( i = 0; word_wrap_tests[i].orig && *word_wrap_tests[i].orig; i ++ ) { char *wrapped = word_wrap( word_wrap_tests[i].orig, word_wrap_tests[i].line_len ); fail_unless( strcmp( word_wrap_tests[i].wrapped, wrapped ) == 0, "%s (line_len = %d) should wrap to `%s', not to `%s'", word_wrap_tests[i].orig, word_wrap_tests[i].line_len, word_wrap_tests[i].wrapped, wrapped ); g_free( wrapped ); } END_TEST START_TEST(test_http_encode) char s[80]; strcpy( s, "ee\xc3""\xab""ee!!..." ); http_encode( s ); fail_unless( strcmp( s, "ee%C3%ABee%21%21..." ) == 0 ); END_TEST Suite *util_suite (void) { Suite *s = suite_create("Util"); TCase *tc_core = tcase_create("Core"); suite_add_tcase (s, tc_core); tcase_add_test (tc_core, test_strip_linefeed); tcase_add_test (tc_core, test_strip_newlines); tcase_add_test (tc_core, test_set_url_http); tcase_add_test (tc_core, test_set_url_https); tcase_add_test (tc_core, test_set_url_port); tcase_add_test (tc_core, test_set_url_username); tcase_add_test (tc_core, test_set_url_username_pwd); tcase_add_test (tc_core, test_word_wrap); tcase_add_test (tc_core, test_http_encode); return s; } bitlbee-3.2.1/tests/check.c0000644000175000017500000000535212245474076015106 0ustar wilmerwilmer#include #include #include #include #include #include "bitlbee.h" #include "testsuite.h" global_t global; /* Against global namespace pollution */ gboolean g_io_channel_pair(GIOChannel **ch1, GIOChannel **ch2) { int sock[2]; if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNIX, sock) < 0) { perror("socketpair"); return FALSE; } *ch1 = g_io_channel_unix_new(sock[0]); *ch2 = g_io_channel_unix_new(sock[1]); return TRUE; } irc_t *torture_irc(void) { irc_t *irc; GIOChannel *ch1, *ch2; if (!g_io_channel_pair(&ch1, &ch2)) return NULL; irc = irc_new(g_io_channel_unix_get_fd(ch1)); return irc; } double gettime() { struct timeval time[1]; gettimeofday( time, 0 ); return( (double) time->tv_sec + (double) time->tv_usec / 1000000 ); } /* From check_util.c */ Suite *util_suite(void); /* From check_nick.c */ Suite *nick_suite(void); /* From check_md5.c */ Suite *md5_suite(void); /* From check_arc.c */ Suite *arc_suite(void); /* From check_irc.c */ Suite *irc_suite(void); /* From check_help.c */ Suite *help_suite(void); /* From check_user.c */ Suite *user_suite(void); /* From check_set.c */ Suite *set_suite(void); /* From check_jabber_sasl.c */ Suite *jabber_sasl_suite(void); /* From check_jabber_sasl.c */ Suite *jabber_util_suite(void); int main (int argc, char **argv) { int nf; SRunner *sr; GOptionContext *pc; gboolean no_fork = FALSE; gboolean verbose = FALSE; GOptionEntry options[] = { {"no-fork", 'n', 0, G_OPTION_ARG_NONE, &no_fork, "Don't fork" }, {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be verbose", NULL }, { NULL } }; int i; pc = g_option_context_new(""); g_option_context_add_main_entries(pc, options, NULL); if(!g_option_context_parse(pc, &argc, &argv, NULL)) return 1; g_option_context_free(pc); log_init(); b_main_init(); setlocale(LC_CTYPE, ""); if (verbose) { log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE ); #ifdef DEBUG log_link( LOGLVL_DEBUG, LOGOUTPUT_CONSOLE ); #endif log_link( LOGLVL_INFO, LOGOUTPUT_CONSOLE ); log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE ); } global.conf = conf_load( 0, NULL); global.conf->runmode = RUNMODE_DAEMON; sr = srunner_create(util_suite()); srunner_add_suite(sr, nick_suite()); srunner_add_suite(sr, md5_suite()); srunner_add_suite(sr, arc_suite()); srunner_add_suite(sr, irc_suite()); srunner_add_suite(sr, help_suite()); srunner_add_suite(sr, user_suite()); srunner_add_suite(sr, set_suite()); srunner_add_suite(sr, jabber_sasl_suite()); srunner_add_suite(sr, jabber_util_suite()); if (no_fork) srunner_set_fork_status(sr, CK_NOFORK); srunner_run_all (sr, verbose?CK_VERBOSE:CK_NORMAL); nf = srunner_ntests_failed(sr); srunner_free(sr); return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } bitlbee-3.2.1/doc/0000755000175000017500000000000012245474076013263 5ustar wilmerwilmerbitlbee-3.2.1/doc/Makefile0000644000175000017500000000107312245474076014724 0ustar wilmerwilmer-include ../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)doc/ endif all: # Only build the docs if this is a bzr checkout test ! -d ../.bzr || $(MAKE) -C user-guide install: mkdir -p $(DESTDIR)$(MANDIR)/man8/ $(DESTDIR)$(MANDIR)/man5/ $(INSTALL) -m 0644 $(_SRCDIR_)bitlbee.8 $(DESTDIR)$(MANDIR)/man8/ $(INSTALL) -m 0644 $(_SRCDIR_)bitlbee.conf.5 $(DESTDIR)$(MANDIR)/man5/ $(MAKE) -C user-guide $@ uninstall: rm -f $(DESTDIR)$(MANDIR)/man8/bitlbee.8* rm -f $(DESTDIR)$(MANDIR)/man5/bitlbee.conf.5* $(MAKE) -C user-guide $@ .PHONY: install uninstall bitlbee-3.2.1/doc/bitlbee.xinetd0000644000175000017500000000135512245474076016112 0ustar wilmerwilmer## xinetd file for BitlBee. Please check this file before using it, the ## user, port and/or binary location might be wrong. ## This file assumes you have ircd somewhere in your /etc/services, if things ## don't work, check that file first. service ircd { socket_type = stream protocol = tcp wait = no ## You most likely want to change these two user = nobody server = /usr/local/sbin/bitlbee -I ## You might want to limit access to localhost only: # bind = 127.0.0.1 ## Thanks a lot to friedman@splode.com for telling us about the type ## argument, so now this file can be used without having to edit ## /etc/services too. type = UNLISTED port = 6667 } bitlbee-3.2.1/doc/FAQ0000644000175000017500000000703712245474076013624 0ustar wilmerwilmerFrequently Asked Questions about BitlBee ======================================== Well, maybe not exactly "Frequently", but definitely "Asked" ... mostly by the developers :-) Q: WTH were you guys on when you thought of that _weird_ name? A: Though we live in The Netherlands and one of us even lives in Amsterdam, we're not on drugs ... most of the time. Q: Okay, so the cops are so evil there, you can't even admit the truth, but WTH does BitlBee mean then? A: There are a few explanations. But the most symbolical goes like: the two colors of the bee symbolize the two worlds betwee which the Bee flies. On the one hand there's the IM-networks, on the other is IRC. Truth be told, it's absolute nonsense. The biggest nutcase in the development team just played around with words for half an hour or so. BitlBee was the result. We liked it, we kept it. We lovingly shorten it to "the Bee" or even "het Bijtje" (Dutch for "the little Bee") sometimes. Q: What is 'root' doing in my control channel? I didn't start the Bee as root. A: 'root' is just the name for the most powerful user in BitlBee. Just like in the system, it is root who is the ... eh ... root of the functionality. Luckily, in BitlBee, root follows your orders (mostly), so no BOFHs there. We get some complaints from time to time that 'root' is a confusing name. Because of that name, some package maintainers have renamed root to, for example, BitlBee. We recognize that some people see that need. If the package maintainer hasn't renamed root, you can do this yourself with the 'rename' command. The name root is not likely to change in the 'official' releases, though. We find the metaphor of root correct and feel that there is no important (security threatening) reason to change this non-creative piece of artistic creativity. Q: When is $random_feature going to be implemented? A: It depends on the feature. We keep a list of all wishlist "bugs" in our Bug Tracking system at http://bugs.bitlbee.org/ Q: The messages I send and/or receive look weird. I see weird characters and annoying HTML codes. Or, BitlBee does evil things when I send messages with non-ASCII characters! A: You probably have to change some settings. To get rid of HTML in messages, see "help set strip_html". If you seem to have problems with your charset, see "help set charset". Although actually most of these problems should be gone by now. So if you can't get things to work well, you might have found a bug. Q: Is BitlBee forked from Gaim? A: BitlBee 0.7 was, sort-of. It contained a lot of code from Gaim 0.58 (mainly the IM-code), although heavily modified, to make it work better with BitlBee. We were planning to keep BitlBee up-to-date with later Gaim versions, but this turned out to be very time-consuming because the API changed a lot, and we don't have the time to keep up with those changes all the time. These days, we replaced the Yahoo! code with libyahoo2 (which is a separate Yahoo! module. It's derived from Gaim, but separately maintained) and wrote our own MSN, Jabber and Twitter modules from scratch. Most of the API has also been changed, so by now the only traces of Gaim left are in the "nogaim" filename. There is good news for Gaim (or now Pidgin, of course) fans though: BitlBee can now be compiled to use libpurple for all IM interactions. This makes BitlBee a bit more resource-hungry, but adds support for many IM protocols/networks that couldn't be used from BitlBee so far. bitlbee-3.2.1/doc/AUTHORS0000644000175000017500000000113012245474076014326 0ustar wilmerwilmerCore team: Wilmer van der Gaast Main developer Other contributors: Miklos Vajna Skype module pesco OTR support Retired developers: Sjoerd 'lucumo' Hemminga NickServ, documentation Jelmer 'ctrlsoft' Vernooij Documentation, general hacking, Win32 port Maurits Dijkstra Daemon dude, plus some other stuff Geert Mulders Initial version of the Twitter module ulim Marijn Kruisselbrink File transfer support bitlbee-3.2.1/doc/bitlbee.conf.50000644000175000017500000000130712245474076015704 0ustar wilmerwilmer.\" Manual page for bitlbee.conf, derived from the modules.conf manpage. .\" Writing a complete manpage from scratch is just too much work... .\" .\" This program is distributed according to the Gnu General Public License. .\" See the file COPYING in the base distribution directory .\" .TH BITLBEE.CONF 5 "07 March 2004" .UC 4 .SH NAME bitlbee.conf \- configuration file for .BR bitlbee (8) .SH DESCRIPTION This file contains system-wide settings for the .BR bitlbee (8) program. For more information about the file syntax, please read the example configuration file which comes with the program. The default file contains lots of comments which explain all the available options. .SH SEE ALSO .BR bitlbee (8) bitlbee-3.2.1/doc/bitlbee.80000644000175000017500000001001512245474076014757 0ustar wilmerwilmer.\" BitlBee 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 .\" along with this program; see the file COPYING. If not, write to .\" the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. .\" .TH bitlbee 8 "19 May 2010" .SH NAME BitlBee \- IRC gateway to IM chat networks .SH SYNOPSIS .PP .B bitlbee [\-I] [\-c \fIconfiguration file\fP] [\-d \fIconfiguration directory\fP] .PP .B bitlbee \-D [\-i \fIaddress\fP] [\-p \fIport number\fP] [\-n] [\-v] [\-c \fIconfiguration file\fP] [\-d \fIconfiguration directory\fP] .PP .B bitlbee \-h .RI .SH DESCRIPTION BitlBee is an IRC daemon that can talk to instant messaging networks and acts as a gateway. Users can connect to the server with any normal IRC client and see their 'buddy list' in &bitlbee. It currently supports Oscar (AIM and ICQ), MSN, Jabber, Yahoo! and Twitter. \fBbitlbee\fP should be called by .BR inetd (8), or you can run it as a stand-alone daemon. .PP .SH OPTIONS .PP .IP "-I" Run in .BR inetd (8) mode. This is the default setting, you usually don't have to specify this option. .IP "-D" Run in daemon mode. In this mode, BitlBee forks to the background and waits for new connections. All clients will be served from one process. .IP "-F" Run in ForkDaemon mode. This is similar to ordinary daemon mode, but every client gets its own process. Easier to set up than inetd mode, and without the possible stability issues. .IP "-i \fIaddress\fP" Only useful when running in daemon mode, to specify the network interface (identified by IP address) to which the daemon should attach. Use this if you don't want BitlBee to listen on every interface (which is the default behaviour). .IP "-p \fIport number\fP" Only useful when running in daemon mode, to specify the port number on which BitlBee should listen for connections. 6667 is the default value. .IP "-n" Only useful when running in daemon mode. This option prevents BitlBee from forking into the background. .IP "-v" Be more verbose. This only works together with the \fB-n\fP flag. .IP "-c \fIpath to other configuration file\fP" Use a different configuration file. .IP "-d \fIpath to user settings directory\fP" BitlBee normally saves every user's settings in \fB/var/lib/bitlbee/\fP. If you want the settings to be stored somewhere else (for example, if you don't have write permissions in the default location), use this option. .IP "-h" Show help information. .SH COMMANDS To get a complete list of commands, please use the \fBhelp commands\fP command in the &bitlbee channel. .SH "SEE ALSO" .BR ircd (8), .BR inetd (8), .BR inetd.conf (5), .BR gaim (1). .BR http://www.bitlbee.org/ For more information on using BitlBee, once connected, you should use the on-line help system. .SH BUGS Of course there are bugs. If you find some, please report them at \fBhttp://bugs.bitlbee.org/\fP. .SH LICENSE 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. .PP This program is distributed in the hope that it will be useful, but \fBWITHOUT ANY WARRANTY\fR; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. .PP 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 .SH AUTHORS .PP Wilmer van der Gaast bitlbee-3.2.1/doc/INSTALL0000644000175000017500000000006312245474076014313 0ustar wilmerwilmerSee the README file for installation instructions. bitlbee-3.2.1/doc/README0000644000175000017500000001762412245474076014155 0ustar wilmerwilmerINSTALLATION ============ If you installed BitlBee from a .deb or .rpm you probably don't have to do anything anymore for installation. Just skip this section. If you want to compile BitlBee yourself, that's fine. Just run ./configure to set up the build system. If configure succeeds, run make to build BitlBee. make install will move all the files to the right places. --- (Fork)Daemon mode These days ForkDaemon mode is the recommended way of running BitlBee. The difference between Daemon and ForkDaemon mode is that in the latter, a separate process is spawned for every user. This costs a little bit more memory, but means that if one user hits a bug in the code, not all other users get disconnected with him/her. To use BitlBee in any daemon mode, just start it with the right flags or enable it in bitlbee.conf (see the RunMode option). You probably want to write an init script to start BitlBee automatically after a reboot. (This is where you realise using a package from your distro would've been a better idea. :-P) Please do make sure that the user BitlBee runs as (not root, please!) is able to read from and write to the /var/lib/bitlbee directory to save your settings! --- inetd installation (more or less deprecated) After installation you have to set up inetd (you got that one running, right? If not, just take a look at utils/bitlbeed.c) to start BitlBee. You need to add BitlBee to inetd.conf, like this: 6667 stream tcp nowait nobody /usr/sbin/tcpd /usr/local/sbin/bitlbee Creating a special BitlBee user and running BitlBee with that UID (instead of just 'nobody') might be a good idea. *BSD/Darwin/OSX NOTE: Most *BSD inetds are more scrict than the one that comes with Linux systems. Possibly all non-Linux inetds are like this. They don't allow you to specify a port number in the inetd.conf entry, instead you have to put a service name there (one that is also mentioned in /etc/services). So if there's no line in /services for 6667/tcp (or whatever you choose), add it and use that name in the inetd.conf entry. -- xinetd installation (equally deprecated) Most machines use xinetd instead of inetd these days. If your machine runs xinetd, you can copy the bitlbee.xinetd file from the doc/ directory to your xinetd.d/ directory. Most likely you'll have to change a thing or two before it'll work. After configuring your (x)inetd, send the daemon a SIGHUP and things should work. If not, see your syslogs, since both daemons will complain there when something's wrong. Also, don't forget to create the configuration directory (/var/lib/bitlbee/ by default) and chown it to the UID BitlBee is running as. Make sure this directory is read-/writable by this user only. DEPENDENCIES ============ BitlBee's only real dependency is GLib. This is available on virtually every platform. Any recent version of GLib (2.4 or higher) will work. Off-the-Record encryption support can be included if libotr is available on your machine. Pass --otr=1 to configure to build it into BitlBee, or --otr=plugin to build it as a separate loadable plugin (mostly meant for distro packages). These days, many IM protocols use SSL/TLS connections (for authentication or for the whole session). BitlBee can use several SSL libraries for this: GnuTLS, NSS (which comes with Mozilla) and OpenSSL. OpenSSL is not GPL- compatible in some situations, so using GnuTLS is preferred. However, especially on *BSD, OpenSSL can be considered part of the operating system, which eliminates the GPL incompatibility. The incompatibility is also the reason why the SSL library detection code doesn't attempt to use OpenSSL. If you want to use OpenSSL, you have to force configure to use it using the --ssl=openssl parameter. For more information about this problem, see the URL's configure will write to stdout when you attempt to use OpenSSL. PORTABILITY ISSUES ================== Cygwin NOTE: You'll need a glib installation to run BitlBee. However, Cygwin doesn't provide a glib package. You can download a binary tar.gz from: . When you installed it, BitlBee should work fine. You'll probably like bitlbeed or xinetd to get it running on the network. On some non-Linux systems the program still suffers from some random bugs. Please do report them, we might be able to fix them if they're not too mysterious. Also, the configure script is known to not work very well with non-Bash shells, so if you experience problems, make sure you use bash to run the script. Same for the Makefile, it only works well with GNU make. (gmake on most BSD systems) If someone can tell us how to write Makefiles that work with both/all versions of make, we'd love to hear it, but it seems this just isn't possible. USAGE ===== Not much to say here, it's all documented elsewhere already. Just connect to the new BitlBee IRC server and the bot (root) will tell you what to do. BACKGROUNDS =========== We are both console lovers. But it is annoying to have a few tty's open with chat things in them. IRC, ICQ, MSN, AIM, Jabber... For X there is Gaim, which supports many chatprotocols. Why wasn't there such a thing for the console? The idea to port Gaim was easily thought of, of course. But we liked our IRC clients. And we used it the most, so we used it best. Importing it into the IRC client was a nice idea. But what if someone liked a different client. Then (s)he had to duplicate our work. That's a shame, we thought. Doing work twice is pointless. So when Wilmer got the ingenious thought in his mind while farming, to create an IRC to other chatnetworks gateway, we were both so excited, that we started working on it almost immediately. And the result is BitlBee. WEBSITE ======= You can find new releases of BitlBee at: http://www.bitlbee.org/ The bug tracking system: http://bugs.bitlbee.org/ Our version control system is Bazaar. Our repository is at: http://code.bitlbee.org/ More documentation on the Wiki: http://wiki.bitlbee.org/ A NOTE ON PASSWORD ENCRYPTION ============================= There used to be a note here about the simple obfuscation method used to make the passwords in the configuration files unreadable. However, BitlBee now uses a better format (and real encryption (salted MD5 and RC4)) to store the passwords. This means that people who somehow get their hands on your configuration files can't easily extract your passwords from them anymore. However, once you log into the BitlBee server and send your password, an intruder with tcpdump can still read your passwords. This can't really be avoided, of course. The new format is a lot more reliable (because it can't be cracked with just very basic crypto analysis anymore), but you still have to be careful. The main extra protection offered by the new format is that the files can only be cracked with some help from the user (by sending the password at login time). So if you run a public server, it's most important that you don't give root access to people who like to play with tcpdump. Also, it's a good idea to delete all *.nicks/*.accounts files as soon as BitlBee converted them to the new format (which happens as soon as the user logs in, it can't be done automatically because it needs the password for that account). You won't need them anymore (unless you want to switch back to an older BitlBee version) and they only make it easier for others to crack your passwords. LEGAL ===== BitlBee is distributed under the GPL (GNU General Public License). See the file COPYING for this license. The MD5 algorithm code is licensed under the Aladdin license. This license can be found in the files, to which this applies. The SHA1 algorithm code is licensed under the Mozilla Public License, see http://www.mozilla.org/MPL/ for details. The Yahoo! library used by BitlBee is libyahoo2 , also licensed under the GPL. BitlBee - An IRC to other chat networks gateway Copyright (C) 2002-2010 Wilmer van der Gaast and others bitlbee-3.2.1/doc/CREDITS0000644000175000017500000000633212245474076014307 0ustar wilmerwilmerThe authors thank the following people: - The Gaim team, for letting us steal their code. - Sander van Schouwenburg, for his testing. - Marten Klencke, for his testing. - Lennart Kats, for putting up with Sjoerd, who bothered him many times to test things. - Ralph Slooten, for creating the RPM packages and testing the program. - Erik Hensema, for creating SuSE RPM packages and some patching. - Tony Vroon, for being a happy user and patch-submitter. - Lots of Twente University students (and of course all the other users, it's just that BitlBee seems to be some sort of hype over there ;-), for spreading the word of the Bee. - Han Boetes, for testing on and porting to OpenBSD. - Paul Foote for some hints on running BitlBee on FreeBSD. - Floris Kruisselbrink, for submitting the "help set ..." patch. - Jan-Willem Lenting, for putting up with Wilmer, who wanted to test the MSN away messages in the middle of the night. - Jan Sabbe, for the hints about running BitlBee on Mac OS X. - Kenny Gryp, for thoroughly testing the groupchat code and submitting bug reports. - Bryan Williams, for the help in getting BitlBee to run on Cygwin. - Peter van Dijk for discovering a security leak in BitlBee. - Christian Häggeström, for the fix for the Jabber barf on high ASCII characters in away messages. - James Ray, for some testing, development and patching. - Yuri Pimenov, for writing the charset/iconv code, requested by a lot of people. - Wouter Paesen, for the MSN friendlyname code and the MSNP8 fix. - Tony Perrie, for the RPM's and the Yahoo! patch. - Andrej Kacian/Ticho for some patches. - Jochem Kossen, for giving an account on his OpenBSD box to do some portability testing. - Geert Hauwaerts, for maintaining quite a big public BitlBee server (msn.irssi.org, down for now) and reporting some very nice bugs. - Robert C Lorentz and other AIM users, for all the reports on bugs (and providing test accounts) about the stupid AIM spaces-in-screenname handling. - Scott Cruzen, for patching up strip_html() and more. - Samuel Tardieu, for random patches. - Tibor Csoegoer, for adding support for receiving URL messages to the ICQ module. - Jonathan/rise, for reporting and fixing a problem with the Yahoo! servers and supporting BitlBee in other ways. - Philip S Tellis, for libyahoo2. - Simon Schubert, for providing code to read the names of ICQ contacts. - NETRIC (www.netric.org) for auditting the BitlBee code security (and finding some small problems). - Elizabeth Krumbach, for her help on the docs. - Frank Thieme, for the info-command enhancements and other patches. - Marcus Dennis, for some bitlbeed enhancements. - infamous41md, for security auditing BitlBee code. - Tijmen Ruizendaal, for some useful BitlBee-related irssi scripts. - Ed Schouten, for reporting bugs. - Greg (gropeep.org), for updating the Yahoo! module to fix some issues that were there for quite some time already. - misc@mandriva.org for lots of Jabber contributions. - And all other users who help us by sending useful bug reports, positive feedback, nice patches and cool addons. Mentioning you all would make this list fill up the whole source tree, so please don't be offended by not seeing your name here. - All the people who run public BitlBee servers. bitlbee-3.2.1/doc/HACKING0000644000175000017500000001321212245474076014251 0ustar wilmerwilmerBitlBee post-1.x "architecture" DISCLAIMER: The messy architecture is being cleaned up. Although lots of progress was made already, this is still a work in progress, and possibly parts of this document aren't entirely accurate anymore by the time you read this. It's been a while since BitlBee started, as a semi-fork of Gaim (version 0.58 at the time). Some people believe nothing changed, but fortunately, many things have. The API is gone for a while already - which wasn't incredibly intrusive, just a few functions renamed for slightly better consistency, added some calls and arguments where that seemed useful, etc. However, up to late in the 1.2 series, the IRC core was still spread accross several files, mostly irc.c + irc_commands.c and pieces and bits in nogaim.c. If you're looking for a textbook example of layer violation, start there. This was all finally redone. Most of the IRC protocol code was rewritten, as was most of the glue between that and the IM modules. The core of BitlBee is now protocols/bee*. Some pieces are still left in protocols/nogaim*. Most stuff in the "root" directory belongs to the IRC UI, which should be considered "a" frontend (although currently, and possibly forever, the only one). Every subdirectory of protocols/ is another IM protocol backend (including purple/ which uses libpurple to define many different protocols). / The IRC core has code to show an IRC interface to a user, with contacts, channels, etc. To make channels and contacts do something, you add event handlers (that translate a message sent to a nick into an instant message to an IM contact, or translates joining a channel into joining an IM chatroom). To get events back from the BitlBee core, the bee_t object has a bunch of functions (struct bee_ui_funcs) that catch them and convert them back to IRC. Short description of what all irc*.c files (and some related ones) do: bitlbee.c: BitlBee bootstrap code, doing bits of I/O as well. ipc.c: For inter-process communication - communication between BitlBee sessions. Also used in daemon mode (in which it's not so much inter- process). irc.c: The main core, with bits of I/O handling, parsing, etc. irc_channel.c: Most things related to standard channels (also defines some of the control channel behaviour). irc_commands.c: Defines all IRC commands (JOIN, NICK, PRIVMSG, etc.). irc_im.c: Most of the glue between IRC and the IM core live here. This is where instant messages are converted to IRC and vice versa, contacts coming online is translated to one or more joins and/or mode changes. irc_send.c: Simple functions that send pieces of IRC output. Somewhat random, but if an IRC response is slightly more complicated than just a simple line, make it a function here. irc_user.c: Defines all IRC user details. Mostly defines the user "object". irc_util.c: Misc. stuff. Not much ATM. nick.c: Handling of nicknames: compare, ucase/lcase, generating and storing nicks for IM contacts. set.c: Settings management, used for user-changeable global/account/channel settings. Should really be considered part of the core. storage*.c: Storing user accounts. (The stuff you normally find in /var/lib/bitlbee) /protocols The IM core lives in protocols/. Whenever you write code there, try to avoid using any IRCisms there. Most header files in there have some of their details explained in comments. bee*.c and nogaim.c are the layer between the IM modules and the IRC frontend. They keep track of IM accounts, contacts and their status, groupchats, etc. You can control them by calling functions in there if available, and otherwise by just calling the functions exported via the prpl struct. Most of these functions are briefly explained in the header files, otherwise the best documentation is sadly in irc_im.c and root_commands.c. Events from the IM module go back to the core + frontend via imcb_* functions defined in bee*.c and nogaim.c. They're all described in the header files. /lib BitlBee uses GLib, which is a pretty nifty library adding a bunch of things that make life of a C coder better. Please try to not use features from recent GLib versions as whenever this happens, some people get cranky. :> There's also a whole bunch of nice stuff in lib/ that you can use: arc.c: ARC4 encryption, mostly used for encrypting IM passwords in the XML storage module. base64.c events_*.c: Event handling, using either GLib (default) or libevent (may make non-forking daemon mode with many users a little bit more efficient). ftutil.c: Some small utility functions currently just used for file transfers. http_client.c: A simple (but asynchronous) HTTP(S) client, used by the MSN, Yahoo! and Twitter module by now. ini.c: Simple INI file parser, used to parse bitlbee.conf. md5.c misc.c: What the name says, really. oauth.c: What the name says. If you don't know what OAuth is, ask Google. Currently only used by the Twitter module. Implements just version 1a ATM. proxy.c: Used together with events_*.c for asynchronous I/O directly or via proxies. sha1.c ssl_*.c: SSL client stuff, using GnuTLS (preferred) or OpenSSL. Other modules aren't working well ATM. url.c: URL parser. xmltree.c: Uses the GLib stream parser to build XML parse trees from a stream and convert the same structs back into XML. Good enough to do Jabber but not very aware of stuff like XML namespaces. Used for Jabber and Twitter. This, together with the headerfile comments, is most of the documentation we have right now. If you're trying to make some changes to BitlBee, do feel free to join #bitlbee on irc.oftc.net to ask any question you have. Suggestions for specific parts to document a little bit more are also welcome. bitlbee-3.2.1/doc/user-guide/0000755000175000017500000000000012245477432015333 5ustar wilmerwilmerbitlbee-3.2.1/doc/user-guide/Support.xml0000644000175000017500000000163512245474076017537 0ustar wilmerwilmer Support Disclaimer BitlBee doesn't come with a warranty and is still (and will probably always be) under development. That means it can crash at any time, corrupt your data or whatever. Don't use it in any production environment and don't rely on it, or at least don't blame us if things blow up. :-) Support channels The World Wide Web http://www.bitlbee.org/ is the homepage of bitlbee and contains the most recent news on bitlbee and the latest releases. IRC BitlBee is discussed on #bitlbee on the OFTC IRC network (server: irc.oftc.net). Mailinglists BitlBee doesn't have any mailinglists. bitlbee-3.2.1/doc/user-guide/Makefile0000644000175000017500000000267612245474076017007 0ustar wilmerwilmer-include ../../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)doc/user-guide/ endif EXTRAPARANEWLINE = 1 # EXTRAPARANEWLINE = 0 all: user-guide.txt user-guide.html help.txt # user-guide.pdf user-guide.ps user-guide.rtf %.tex: %.db.xml xsltproc --stringparam l10n.gentext.default.language "en" --stringparam latex.documentclass.common "" --stringparam latex.babel.language "" --output $@ http://db2latex.sourceforge.net/xsl/docbook.xsl $< %.txt: %.db.xml xmlto --skip-validation txt $< mv $*.db.txt $@ %.html: %.db.xml xsltproc --output $@ http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl $< %.pdf: %.db.xml xmlto --skip-validation pdf $< mv $*.db.pdf $@ %.ps: %.db.xml xmlto --skip-validation ps $< mv $*.db.ps $@ help.xml: commands.xml %.db.xml: %.xml docbook.xsl xsltproc --xinclude --output $@ docbook.xsl $< help.txt: help.xml help.xsl commands.xml misc.xml quickstart.xml xsltproc --stringparam extraparanewline "$(EXTRAPARANEWLINE)" --xinclude help.xsl $< | perl -0077 -pe 's/\n\n%/\n%/s; s/_b_/\002/g;' > $@ clean: rm -f *.html *.pdf *.ps *.rtf *.txt *.db.xml install: mkdir -p $(DESTDIR)$(DATADIR) chmod 0755 $(DESTDIR)$(DATADIR) rm -f $(DESTDIR)$(DATADIR)/help.txt # Prevent help function from breaking in running sessions $(INSTALL) -m 0644 $(_SRCDIR_)help.txt $(DESTDIR)$(DATADIR)/help.txt uninstall: rm -f $(DESTDIR)$(DATADIR)/help.txt -rmdir $(DESTDIR)$(DATADIR) .PHONY: clean install uninstall bitlbee-3.2.1/doc/user-guide/docbook.xsl0000644000175000017500000001012312245474076017501 0ustar wilmerwilmer < > * set_ Type: cmd_ - Syntax: bitlbee-3.2.1/doc/user-guide/quickstart.xml0000644000175000017500000001445212245474076020256 0ustar wilmerwilmer Quickstart Welcome to BitlBee, your IRC gateway to ICQ, MSN, AOL, Jabber, Yahoo! and Twitter. The center of BitlBee is the control channel, &bitlbee. Two users will always be there, you (where "you" is the nickname you are using) and the system user, root. You need register so that all your IM settings (passwords, contacts, etc) can be saved on the BitlBee server. It's important that you pick a good password so no one else can access your account. Register with this password using the register command: register <password> (without the brackets!). Be sure to remember your password. The next time you connect to the BitlBee server you will need to identify <password> so that you will be recognised and logged in to all the IM services automatically. When finished, type help quickstart2 to continue. Add and Connect To your IM Account(s) Step Two: Add and Connect To your IM Account(s). To add an account to the account list you will need to use the account add command: account add <protocol> <username> <password> [<server>]. For instance, suppose you have a Jabber account at jabber.org with handle bitlbee@jabber.org with password QuickStart, you would: account add jabber bitlbee@jabber.org QuickStart Account successfully added Other available IM protocols are msn, oscar, yahoo and twitter. OSCAR is the protocol used by ICQ and AOL. For more information about the account add command, see help account add. When you are finished adding your account(s) use the account on command to enable all your accounts, type help quickstart3 to continue. Step Four: Managing Contact Lists: Add, Remove and Rename Now you might want to add some contacts, to do this we will use the add command. It needs two arguments: a connection ID (which can be a number (try account list), protocol name or (part of) the screenname) and the user's handle. It is used in the following way: add <connection> <handle> add 0 r2d2@example.com has joined &bitlbee In this case r2d2 is online, since he/she joins the channel immediately. If the user is not online you will not see them join until they log on. Lets say you accidentally added r2d3@example.com rather than r2d2@example.com, or maybe you just want to remove a user from your list because you never talk to them. To remove a name you will want to use the remove command: remove r2d3 Finally, if you have multiple users with similar names you may use the rename command to make it easier to remember: rename r2d2_ r2d2_aim When finished, type help quickstart4 to continue. Chatting Step Five: Chatting. First of all, a person must be on your contact list for you to chat with them (unless it's a group chat, help groupchats for more). If someone not on your contact list sends you a message, simply add them to the proper account with the add command. Once they are on your list and online, you can chat with them in &bitlbee: tux: hey, how's the weather down there? you: a bit chilly! Note that, although all contacts are in the &bitlbee channel, only tux will actually receive this message. The &bitlbee channel shouldn't be confused with a real IRC channel. If you prefer chatting in a separate window, use the /msg or /query command, just like on real IRC. BitlBee will remember how you talk to someone and show his/her responses the same way. If you want to change the default behaviour (for people you haven't talked to yet), see help set private. You know the basics. If you want to know about some of the neat features BitlBee offers, please type help quickstart5. Further Resources So you want more than just chatting? Or maybe you're just looking for more features? With multiple channel support you can have contacts for specific protocols in their own channels, for instance, if you /join &msn you will join a channel that only contains your MSN contacts. Account tagging allows you to use the given account name rather than a number when referencing your account. If you wish to turn off your gtalk account, you may account gtalk off rather than account 3 off where "3" is the account number. You can type help set to learn more about the possible BitlBee user settings. Among these user settings you will find options for common issues, such as changing the charset, HTML stripping and automatic connecting (simply type set to see current user settings). For more subjects (like groupchats and away states), please type help index. If you're still looking for something, please visit us in #bitlbee on the OFTC network (you can connect via irc.bitlbee.org), or mail us your problem/suggestion. Good luck and enjoy the Bee! bitlbee-3.2.1/doc/user-guide/commands.xml0000644000175000017500000022065412245474076017670 0ustar wilmerwilmer Bitlbee commands IM-account list maintenance account [<account id>] <action> [<arguments>] Available actions: add, del, list, on, off and set. See help account <action> for more information. account add <protocol> <username> [<password>] Adds an account on the given server with the specified protocol, username and password to the account list. Supported protocols right now are: Jabber, MSN, OSCAR (AIM/ICQ), Yahoo and Twitter. For more information about adding an account, see help account add <protocol>. You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs. account add jabber <handle@server.tld> [<password>] The handle should be a full handle, including the domain name. You can specify a servername if necessary. Normally BitlBee doesn't need this though, since it's able to find out the server by doing DNS SRV lookups. In previous versions it was also possible to specify port numbers and/or SSL in the server tag. This is deprecated and should now be done using the account set command. This also applies to specifying a resource in the handle (like wilmer@bitlbee.org/work). account add msn <handle@server.tld> [<password>] For MSN connections there are no special arguments. account add oscar <handle> [<password>] OSCAR is the protocol used to connect to AIM and/or ICQ. The servers will automatically detect if you're using a numeric or non-numeric username so there's no need to tell which network you want to connect to. account add oscar 72696705 hobbelmeeuw Account successfully added account add twitter <handle> This module gives you simple access to Twitter and Twitter API compatible services. By default all your Twitter contacts will appear in a new channel called #twitter_yourusername. You can change this behaviour using the mode setting (see help set mode). To send tweets yourself, send them to the twitter_(yourusername) contact, or just write in the groupchat channel if you enabled that option. Since Twitter now requires OAuth authentication, you should not enter your Twitter password into BitlBee. Just type a bogus password. The first time you log in, BitlBee will start OAuth authentication. (See help set oauth.) To use a non-Twitter service, change the base_url setting. For identi.ca, you can simply use account add identica. account add identica <handle> Same protocol as twitter, but defaults to a base_url pointing at identi.ca. It also works with OAuth (so don't specify your password). account add yahoo <handle> [<password>] For Yahoo! connections there are no special arguments. account <account id> del This commands deletes an account from your account list. You should signoff the account before deleting it. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. account [<account id>] on This command will try to log into the specified account. If no account is specified, BitlBee will log into all the accounts that have the auto_connect flag set. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. account [<account id>] off This command disconnects the connection for the specified account. If no account is specified, BitlBee will deactivate all active accounts and cancel all pending reconnects. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. account list This command gives you a list of all the accounts known by BitlBee. account <account id> set account <account id> set <setting> account <account id> set <setting> <value> account <account id> set -del <setting> This command can be used to change various settings for IM accounts. For all protocols, this command can be used to change the handle or the password BitlBee uses to log in and if it should be logged in automatically. Some protocols have additional settings. You can see the settings available for a connection by typing account <account id> set. For more infomation about a setting, see help set <setting>. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. Channel list maintenance channel [<account id>] <action> [<arguments>] Available actions: del, list, set. See help channel <action> for more information. There is no channel add command. To create a new channel, just use the IRC /join command. See also help channels and help groupchats. channel <channel id> del Remove a channel and forget all its settings. You can only remove channels you're not currently in, and can't remove the main control channel. (You can, however, leave it.) channel list This command gives you a list of all the channels you configured. channel [<channel id>] set channel [<channel id>] set <setting> channel [<channel id>] set <setting> <value> channel [<channel id>] set -del <setting> This command can be used to change various settings for channels. Different channel types support different settings. You can see the settings available for a channel by typing channel <channel id> set. For more infomation about a setting, see help set <setting>. The channel ID can be a number (see channel list), or (part of) its name, as long as it matches only one channel. If you want to change settings of the current channel, you can omit the channel ID. Chatroom list maintenance chat <action> [<arguments>] Available actions: add, with. See help chat <action> for more information. chat add <account id> <room> [<channel>] Add a chatroom to the list of chatrooms you're interested in. BitlBee needs this list to map room names to a proper IRC channel name. After adding a room to your list, you can simply use the IRC /join command to enter the room. Also, you can tell BitlBee to automatically join the room when you log in. (See chat set) Password-protected rooms work exactly like on IRC, by passing the password as an extra argument to /join. chat with <nickname> While most chat subcommands are about named chatrooms, this command can be used to open an unnamed groupchat with one or more persons. This command is what /join #nickname used to do in older BitlBee versions. Add a buddy to your contact list add <account id> <handle> [<nick>] add -tmp <account id> <handle> [<nick>] Adds the given buddy at the specified connection to your buddy list. The account ID can be a number (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. If you want, you can also tell BitlBee what nick to give the new contact. The -tmp option adds the buddy to the internal BitlBee structures only, not to the real contact list (like done by set handle_unknown add). This allows you to talk to people who are not in your contact list. This normally won't show you any presence notifications. If you use this command in a control channel containing people from only one group, the new contact will be added to that group automatically. add 3 gryp@jabber.org grijp has joined &bitlbee Request user information info <connection> <handle> info <nick> Requests IM-network-specific information about the specified user. The amount of information you'll get differs per protocol. For some protocols (ATM Yahoo! and MSN) it'll give you an URL which you can visit with a normal web browser to get the information. info 0 72696705 User info - UIN: 72696705 Nick: Lintux First/Last name: Wilmer van der Gaast E-mail: lintux@lintux.cx Remove a buddy from your contact list remove <nick> Removes the specified nick from your buddy list. remove gryp has quit [Leaving...] Block someone block <nick> block <connection> <handle> block <connection> Puts the specified user on your ignore list. Either specify the user's nick when you have him/her in your contact list or a connection number and a user handle. When called with only a connection specification as an argument, the command displays the current block list for that connection. Unblock someone allow <nick> allow <connection> <handle> Reverse of block. Unignores the specified user or user handle on specified connection. When called with only a connection specification as an argument, the command displays the current allow list for that connection. Off-the-Record encryption control otr <subcommand> [<arguments>] Available subcommands: connect, disconnect, reconnect, smp, smpq, trust, info, keygen, and forget. See help otr <subcommand> for more information. otr connect <nick> Attempts to establish an encrypted connection with the specified user by sending a magic string. otr disconnect <nick> Resets the connection with the specified user to cleartext. otr reconnect <nick> Breaks and re-establishes the encrypted connection with the specified user. Useful if something got desynced. Equivalent to otr disconnect followed by otr connect. otr smp <nick> <secret> Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol. If an SMP challenge has been received from the given user, responds with the specified secret/answer. Otherwise, sends a challenge for the given secret. Note that there are two flavors of SMP challenges: "shared-secret" and "question & answer". This command is used to respond to both of them, or to initiate a shared-secret style exchange. Use the otr smpq command to initiate a "Q&A" session. When responding to a "Q&A" challenge, the local trust value is not altered. Only the asking party sets trust in the case of success. Use otr smpq to pose your challenge. In a shared-secret exchange, both parties set their trust according to the outcome. otr smpq <nick> <question> <answer> Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol, Q&A style. Initiates an SMP session in "question & answer" style. The question is transmitted with the initial SMP packet and used to prompt the other party. You must be confident that only they know the answer. If the protocol succeeds (i.e. they answer correctly), the fingerprint will be trusted. Note that the answer must be entered exactly, case and punctuation count! Note that this style of SMP only affects the trust setting on your side. Expect your opponent to send you their own challenge. Alternatively, if you and the other party have a shared secret, use the otr smp command. otr trust <nick> <fp1> <fp2> <fp3> <fp4> <fp5> Manually affirms trust in the specified fingerprint, given as five blocks of precisely eight (hexadecimal) digits each. otr info otr info <nick> Shows information about the OTR state. The first form lists our private keys and current OTR contexts. The second form displays information about the connection with a given user, including the list of their known fingerprints. otr keygen <account-no> Generates a new OTR private key for the given account. otr forget <thing> <arguments> Forgets some part of our OTR userstate. Available things: fingerprint, context, and key. See help otr forget <thing> for more information. otr forget fingerprint <nick> <fingerprint> Drops the specified fingerprint from the given user's OTR connection context. It is allowed to specify only a (unique) prefix of the desired fingerprint. otr forget context <nick> Forgets the entire OTR context associated with the given user. This includes current message and protocol states, as well as any fingerprints for that user. otr forget key <fingerprint> Forgets an OTR private key matching the specified fingerprint. It is allowed to specify only a (unique) prefix of the fingerprint. Miscellaneous settings set set <variable> set <variable> <value> set -del <variable> Without any arguments, this command lists all the set variables. You can also specify a single argument, a variable name, to get that variable's value. To change this value, specify the new value as the second argument. With -del you can reset a setting to its default value. To get more help information about a setting, try: help set private BitlBee help system help [subject] This command gives you the help information you're reading right now. If you don't give any arguments, it'll give a short help index. Save your account data save This command saves all your nicks and accounts immediately. Handy if you have the autosave functionality disabled, or if you don't trust the program's stability... ;-) For control channels with fill_by set to account: Set this setting to the account id (numeric, or part of the username) of the account containing the contacts you want to see in this channel. true When you're already connected to a BitlBee server and you connect (and identify) again, BitlBee will offer to migrate your existing session to the new connection. If for whatever reason you don't want this, you can disable this setting. true With this option enabled, when you identify BitlBee will automatically connect to your accounts, with this disabled it will not do this. This setting can also be changed for specific accounts using the account set command. (However, these values will be ignored if the global auto_connect setting is disabled!) false With this option enabled, BitlBee will automatically join this channel when you log in. true If an IM-connections breaks, you're supposed to bring it back up yourself. Having BitlBee do this automatically might not always be a good idea, for several reasons. If you want the connections to be restored automatically, you can enable this setting. See also the auto_reconnect_delay setting. This setting can also be changed for specific accounts using the account set command. (However, these values will be ignored if the global auto_reconnect setting is disabled!) 5*3<900 Tell BitlBee after how many seconds it should attempt to bring a broken IM-connection back up. This can be one integer, for a constant delay. One can also set it to something like "10*10", which means wait for ten seconds on the first reconnect, multiply it by ten on every failure. Once successfully connected, this delay is re-set to the initial value. With < you can give a maximum delay. See also the auto_reconnect setting. 10800 For Twitter accounts: If you respond to Tweets IRC-style (like "nickname: reply"), this will automatically be converted to the usual Twitter format ("@screenname reply"). By default, BitlBee will then also add a reference to that person's most recent Tweet, unless that message is older than the value of this setting in seconds. If you want to disable this feature, just set this to 0. Alternatively, if you want to write a message once that is not a reply, use the Twitter reply syntax (@screenname). To mark yourself as away, it is recommended to just use /away, like on normal IRC networks. If you want to mark yourself as away on only one IM network, you can use this per-account setting. You can set it to any value and BitlBee will try to map it to the most appropriate away state for every open IM connection, or set it as a free-form away message where possible. Any per-account away setting will override globally set away states. To un-set the setting, use set -del away. true With this option enabled, the root user devoices people when they go away (just away, not offline) and gives the voice back when they come back. You might dislike the voice-floods you'll get if your contact list is huge, so this option can be disabled. Replaced with the show_users setting. See help show_users. 3600 Most IRC servers send a user's away message every time s/he gets a private message, to inform the sender that they may not get a response immediately. With this setting set to 0, BitlBee will also behave like this. Since not all IRC clients do an excellent job at suppressing these messages, this setting lets BitlBee do it instead. BitlBee will wait this many seconds (or until the away state/message changes) before re-informing you that the person's away. http://api.twitter.com/1 There are more services that understand the Twitter API than just Twitter.com. BitlBee can connect to all Twitter API implementations. For example, set this setting to http://identi.ca/api to use Identi.ca. Keep two things in mind: When not using Twitter, you must also disable the oauth setting as it currently only works with Twitter. If you're still having issues, make sure there is no slash at the end of the URL you enter here. utf-8 you can get a list of all possible values by doing 'iconv -l' in a shell This setting tells BitlBee what your IRC client sends and expects. It should be equal to the charset setting of your IRC client if you want to be able to send and receive non-ASCII text properly. Most systems use UTF-8 these days. On older systems, an iso8859 charset may work better. For example, iso8859-1 is the best choice for most Western countries. You can try to find what works best for you on http://www.unicodecharacter.com/charsets/iso8859.html true If set to true, BitlBee will color incoming encrypted messages according to their fingerprint trust level: untrusted=red, trusted=green. groupchat groupchat, room There are two kinds of chat channels: simple groupchats (basically normal IM chats with more than two participants) and names chatrooms, more similar to IRC channels. BitlBee supports both types. With this setting set to groupchat (the default), you can just invite people into the room and start talking. For setting up named chatrooms, it's currently easier to just use the chat add command. true true, false, strict With this setting enabled, you can use some commands in your Twitter channel/query. The commands are simple and not documented in too much detail: undo #[<id>]Delete your last Tweet (or one with the given ID) rt <screenname|#id>Retweet someone's last Tweet (or one with the given ID) reply <screenname|#id>Reply to a Tweet (with a reply-to reference) report <screenname|#id>Report the given user (or the user who posted the tweet with the given ID) for sending spam. This will also block them. follow <screenname>Start following a person unfollow <screenname>Stop following a person favourite <screenname|#id>Favourite the given user's most recent tweet, or the given tweet ID. post <message>Post a tweet Anything that doesn't look like a command will be treated as a tweet. Watch out for typos, or to avoid this behaviour, you can set this setting to strict, which causes the post command to become mandatory for posting a tweet. false Some debugging messages can be logged if you wish. They're probably not really useful for you, unless you're doing some development on BitlBee. This feature is not currently used for anything so don't expect this to generate any output. root root, last With this value set to root, lines written in a control channel without any nickname in front of them will be interpreted as commands. If you want BitlBee to send those lines to the last person you addressed in that control channel, set this to last. Currently only available for MSN connections. This setting allows you to read and change your "friendly name" for this connection. Since this is a server-side setting, it can't be changed when the account is off-line. false With this option enabled, root will inform you when someone in your buddy list changes his/her "friendly name". true When incoming messages are old (i.e. offline messages and channel backlogs), BitlBee will prepend them with a timestamp. If you find them ugly or useless, you can use this setting to hide them. all all, group, account, protocol For control channels only: This setting determines which contacts the channel gets populated with. By default, control channels will contain all your contacts. You instead select contacts by buddy group, IM account or IM protocol. Change this setting and the corresponding account/group/protocol setting to set up this selection. With a ! prefix an inverted channel can be created, for example with this setting set to !group you can create a channel with all users not in that group. Note that, when creating a new channel, BitlBee will try to preconfigure the channel for you, based on the channel name. See help channels. For control channels with fill_by set to group: Set this setting to the name of the group containing the contacts you want to see in this channel. add_channel root, add, add_private, add_channel, ignore By default, messages from people who aren't in your contact list are shown in a control channel instead of as a private message. If you prefer to ignore messages from people you don't know, you can set this one to "ignore". "add_private" and "add_channel" are like add, but you can use them to make messages from unknown buddies appear in the channel instead of a query window. Although these users will appear in your control channel, they aren't added to your real contact list. When you restart BitlBee, these auto-added users will be gone. If you want to keep someone in your list, you have to fixate the add using the add command. false Only supported by OSCAR so far, you can use this setting to ignore ICQ authorization requests, which are hardly used for legitimate (i.e. non-spam) reasons anymore. true Hereby you can change whether you want all lower case nick names or leave the case as it intended by your peer. false Mostly meant to work around a bug in MSN servers (forgetting the display name set by the user), this setting tells BitlBee to store your display name locally and set this name on the MSN servers when connecting. false Some protocols (MSN, Yahoo!) can notify via IM about new e-mail. Since most people use their Hotmail/Yahoo! addresses as a spam-box, this is disabled default. If you want these notifications, you can enable this setting. 140 Since Twitter rejects messages longer than 140 characters, BitlBee can count message length and emit a warning instead of waiting for Twitter to reject it. You can change this limit here but this won't disable length checks on Twitter's side. You can also set it to 0 to disable the check in case you believe BitlBee doesn't count the characters correctly. true For Twitter accounts, this setting enables use of the Streaming API. This automatically gives you incoming DMs as well. For other Twitter-like services, this setting is not supported. 20 Twitter replaces every URL with fixed-length t.co URLs. BitlBee is able to take t.co urls into account when calculating message_length replacing the actual URL length with target_url_length. Setting target_url_length to 0 disables this feature. This setting is disabled for identica accounts by default and will not affect anything other than message safety checks (i.e. Twitter will still replace your URLs with t.co links, even if that makes them longer). one, many, chat chat By default, BitlBee will create a separate channel (called #twitter_yourusername) for all your Twitter contacts/messages. If you don't want an extra channel, you can set this setting to "one" (everything will come from one nick, twitter_yourusername), or to "many" (individual nicks for everyone). With modes "chat" and "many", you can send direct messages by /msg'ing your contacts directly. Note, however, that incoming DMs are not fetched yet. With modes "many" and "one", you can post tweets by /msg'ing the twitter_yourusername contact. In mode "chat", messages posted in the Twitter channel will also be posted as tweets. false Most IM networks have a mobile version of their client. People who use these may not be paying that much attention to messages coming in. By enabling this setting, people using mobile clients will always be shown as away. You can use this option to set your nickname in a chatroom. You won't see this nickname yourself, but other people in the room will. By default, BitlBee will use your username as the chatroom nickname. %-@nick By default, BitlBee tries to derive sensible nicknames for all your contacts from their IM handles. In some cases, IM modules (ICQ for example) will provide a nickname suggestion, which will then be used instead. This setting lets you change this behaviour. Whenever this setting is set for an account, it will be used for all its contacts. If it's not set, the global value will be used. It's easier to describe this setting using a few examples: FB-%full_name will make all nicknames start with "FB-", followed by the person's full name. For example you can set this format for your Facebook account so all Facebook contacts are clearly marked. [%group]%-@nick will make all nicknames start with the group the contact is in between square brackets, followed by the nickname suggestions from the IM module if available, or otherwise the handle. Because of the "-@" part, everything from the first @ will be stripped. See help nick_format for more information. handle handle, full_name, first_name By default, BitlBee generates a nickname for every contact by taking its handle and chopping off everything after the @. In some cases, this gives very inconvenient nicknames. The Facebook XMPP server is a good example, as all Facebook XMPP handles are numeric. With this setting set to full_name, the person's full name is used to generate a nickname. Or if you don't like long nicknames, set this setting to first_name instead and only the first word will be used. Note that the full name can be full of non-ASCII characters which will be stripped off. true This enables OAuth authentication for an IM account; right now the Twitter (working for Twitter only) and Jabber (for Google Talk, Facebook and MSN Messenger) module support it. With OAuth enabled, you shouldn't tell BitlBee your account password. Just add your account with a bogus password and type account on. BitlBee will then give you a URL to authenticate with the service. If this succeeds, you will get a PIN code which you can give back to BitlBee to finish the process. The resulting access token will be saved permanently, so you have to do this only once. If for any reason you want to/have to reauthenticate, you can use account set to reset the account password to something random. both both, root, user, none Some people prefer themself and root to have operator status in &bitlbee, other people don't. You can change these states using this setting. The value "both" means both user and root get ops. "root" means, well, just root. "user" means just the user. "none" means nobody will get operator status. opportunistic never, opportunistic, manual, always This setting controls the policy for establishing Off-the-Record connections. A value of "never" effectively disables the OTR subsystem. In "opportunistic" mode, a magic whitespace pattern will be appended to the first message sent to any user. If the peer is also running opportunistic OTR, an encrypted connection will be set up automatically. On "manual", on the other hand, OTR connections must be established explicitly using otr connect. Finally, the setting "always" enforces encrypted communication by causing BitlBee to refuse to send any cleartext messages at all. Use this global setting to change your "NickServ" password. This setting is also available for all IM accounts to change the password BitlBee uses to connect to the service. Note that BitlBee will always say this setting is empty. This doesn't mean there is no password, it just means that, for security reasons, BitlBee stores passwords somewhere else so they can't just be retrieved in plain text. false By default, when you send a message to someone, BitlBee forwards this message to the user immediately. When you paste a large number of lines, the lines will be sent in separate messages, which might not be very nice to read. If you enable this setting, BitlBee will buffer your messages and wait for more data. Using the paste_buffer_delay setting you can specify the number of seconds BitlBee should wait for more data before the complete message is sent. Please note that if you remove a buddy from your list (or if the connection to that user drops) and there's still data in the buffer, this data will be lost. BitlBee will not try to send the message to the user in those cases. 200 Tell BitlBee after how many (mili)seconds a buffered message should be sent. Values greater than 5 will be interpreted as miliseconds, 5 and lower as seconds. See also the paste_buffer setting. Currently only available for Jabber connections. Specifies the port number to connect to. Usually this should be set to 5222, or 5223 for SSL-connections. 0 Can be set for Jabber connections. When connecting to one account from multiple places, this priority value will help the server to determine where to deliver incoming messages (that aren't addressed to a specific resource already). According to RFC 3921 servers will always deliver messages to the server with the highest priority value. Mmessages will not be delivered to resources with a negative priority setting (and should be saved as an off-line message if all available resources have a negative priority value). true If value is true, messages from users will appear in separate query windows. If false, messages from users will appear in a control channel. This setting is remembered (during one session) per-user, this setting only changes the default state. This option takes effect as soon as you reconnect. For control channels with fill_by set to protocol: Set this setting to the name of the IM protocol of all contacts you want to see in this channel. lifo lifo, fifo This changes the order in which the questions from root (usually authorization requests from buddies) should be answered. When set to lifo, BitlBee immediately displays all new questions and they should be answered in reverse order. When this is set to fifo, BitlBee displays the first question which comes in and caches all the others until you answer the first one. Although the fifo setting might sound more logical (and used to be the default behaviour in older BitlBee versions), it turned out not to be very convenient for many users when they missed the first question (and never received the next ones). BitlBee Can be set for Jabber connections. You can use this to connect to your Jabber account from multiple clients at once, with every client using a different resource string. activity priority, activity Because the IRC interface makes it pretty hard to specify the resource to talk to (when a buddy is online through different resources), this setting was added. Normally it's set to priority which means messages will always be delivered to the buddy's resource with the highest priority. If the setting is set to activity, messages will be delivered to the resource that was last used to send you a message (or the resource that most recently connected). root Normally the "bot" that takes all your BitlBee commands is called "root". If you don't like this name, you can rename it to anything else using the rename command, or by changing this setting. true If enabled causes BitlBee to save all current settings and account details when user disconnects. This is enabled by default, and these days there's not really a reason to have it disabled anymore. Can be set for Jabber- and OSCAR-connections. For Jabber, you might have to set this if the servername isn't equal to the part after the @ in the Jabber handle. For OSCAR this shouldn't be necessary anymore in recent BitlBee versions. true Enable this setting on a Twitter account to have BitlBee include a two-digit "id" in front of every message. This id can then be used for replies and retweets. false If enabled causes BitlBee to also show offline users in Channel. Online-users will get op, away-users voice and offline users none of both. This option takes effect as soon as you reconnect. Replaced with the show_users setting. See help show_users. online+,away Comma-separated list of statuses of users you want in the channel, and any modes they should have. The following statuses are currently recognised: online (i.e. available, not away), away, and offline. If a status is followed by a valid channel mode character (@, % or +), it will be given to users with that status. For example, online@,away+,offline will show all users in the channel. Online people will have +o, people who are online but away will have +v, and others will have no special modes. true Some IRC clients parse quit messages sent by the IRC server to see if someone really left or just disappeared because of a netsplit. By default, BitlBee tries to simulate netsplit-like quit messages to keep the control channels window clean. If you don't like this (or if your IRC client doesn't support this) you can disable this setting. false Currently only available for Jabber connections. Set this to true if you want to connect to the server on an SSL-enabled port (usually 5223). Please note that this method of establishing a secure connection to the server has long been deprecated. You are encouraged to look at the tls setting instead. Most IM protocols support status messages, similar to away messages. They can be used to indicate things like your location or activity, without showing up as away/busy. This setting can be used to set such a message. It will be available as a per-account setting for protocols that support it, and also as a global setting (which will then automatically be used for all protocols that support it). Away states set using /away or the away setting will override this setting. To clear the setting, use set -del status. true Determines what BitlBee should do with HTML in messages. Normally this is turned on and HTML will be stripped from messages, if BitlBee thinks there is HTML. If BitlBee fails to detect this sometimes (most likely in AIM messages over an ICQ connection), you can set this setting to always, but this might sometimes accidentally strip non-HTML things too. false Turn on this flag to prevent tweets from spanning over multiple lines. 20 This setting specifies the number of old mentions to fetch on connection. Must be less or equal to 200. Setting it to 0 disables this feature. false Turn on this flag if you have difficulties talking to offline/invisible contacts. With this setting enabled, BitlBee will send keepalives to MSN switchboards with offline/invisible contacts every twenty seconds. This should keep the server and client on the other side from shutting it down. This is useful because BitlBee doesn't support MSN offline messages yet and the MSN servers won't let the user reopen switchboards to offline users. Once offline messaging is supported, this flag might be removed. For every account you have, you can set a tag you can use to uniquely identify that account. This tag can be used instead of the account number (or protocol name, or part of the screenname) when using commands like account, add, etc. You can't have two accounts with one and the same account tag. By default, it will be set to the name of the IM protocol. Once you add a second account on an IM network, a numeric suffix will be added, starting with 2. local local, utc, gmt, timezone-spec If message timestamps are available for offline messages or chatroom backlogs, BitlBee will display them as part of the message. By default it will use the local timezone. If you're not in the same timezone as the BitlBee server, you can adjust the timestamps using this setting. Values local/utc/gmt should be self-explanatory. timezone-spec is a time offset in hours:minutes, for example: -8 for Pacific Standard Time, +2 for Central European Summer Time, +5:30 for Indian Standard Time. true By default (with this setting enabled), BitlBee will require Jabber servers to offer encryption via StartTLS and refuse to connect if they don't. If you set this to "try", BitlBee will use StartTLS only if it's offered. With the setting disabled, StartTLS support will be ignored and avoided entirely. true Currently only available for Jabber connections in combination with the tls setting. Set this to true if you want BitlBee to strictly verify the server's certificate against a list of trusted certificate authorities. The hostname used in the certificate verification is the value of the server setting if the latter is nonempty and the domain of the username else. If you get a hostname related error when connecting to Google Talk with a username from the gmail.com or googlemail.com domain, please try to empty the server setting. Please note that no certificate verification is performed when the ssl setting is used, or when the CAfile setting in bitlbee.conf is not set. ": " It's customary that messages meant for one specific person on an IRC channel are prepended by his/her alias followed by a colon ':'. BitlBee does this by default. If you prefer a different character, you can set it using set to_char. Please note that this setting is only used for incoming messages. For outgoing messages you can use ':' (colon) or ',' to separate the destination nick from the message, and this is not configurable. true IRC's nickname namespace is quite limited compared to most IM protocols. Not any non-ASCII characters are allowed, in fact nicknames have to be mostly alpha-numeric. Also, BitlBee has to add underscores sometimes to avoid nickname collisions. While normally the BitlBee user is the only one seeing these names, they may be exposed to other chatroom participants for example when addressing someone in the channel (with or without tab completion). By default BitlBee will translate these stripped nicknames back to the original nick. If you don't want this, disable this setting. control control, chat BitlBee supports two kinds of channels: control channels (usually with a name starting with a &) and chatroom channels (name usually starts with a #). See help channels for a full description of channel types in BitlBee. false Sends you a /notice when a user starts typing a message (if supported by the IM protocol and the user's client). To use this, you most likely want to use a script in your IRC client to show this information in a more sensible way. BitlBee Some Jabber servers are configured to only allow a few (or even just one) kinds of XMPP clients to connect to them. You can change this setting to make BitlBee present itself as a different client, so that you can still connect to these servers. false Officially, IRC nicknames are restricted to ASCII. Recently some clients and servers started supporting Unicode nicknames though. To enable UTF-8 nickname support (contacts only) in BitlBee, enable this setting. To avoid confusing old clients, this setting is disabled by default. Be careful when you try it, and be prepared to be locked out of your BitlBee in case your client interacts poorly with UTF-8 nicknames. false ICQ allows people to see if you're on-line via a CGI-script. (http://status.icq.com/online.gif?icq=UIN) This can be nice to put on your website, but it seems that spammers also use it to see if you're online without having to add you to their contact list. So to prevent ICQ spamming, recent versions of BitlBee disable this feature by default. Unless you really intend to use this feature somewhere (on forums or maybe a website), it's probably better to keep this setting disabled. false The Jabber module allows you to add a buddy xmlconsole to your contact list, which will then show you the raw XMPP stream between you and the server. You can also send XMPP packets to this buddy, which will then be sent to the server. If you want to enable this XML console permanently (and at login time already), you can set this setting. Rename (renick) a buddy rename <oldnick> <newnick> rename -del <oldnick> Renick a user in your buddy list. Very useful, in fact just very important, if you got a lot of people with stupid account names (or hard ICQ numbers). rename -del can be used to erase your manually set nickname for a contact and reset it to what was automatically generated. rename itsme_ you is now known as you Accept a request yes [<number>] Sometimes an IM-module might want to ask you a question. (Accept this user as your buddy or not?) To accept a question, use the yes command. By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the qlist command for a list of questions. Deny a request no [<number>] Sometimes an IM-module might want to ask you a question. (Accept this user as your buddy or not?) To reject a question, use the no command. By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the qlist command for a list of questions. List all the unanswered questions root asked qlist This gives you a list of all the unanswered questions from root. Register yourself register [<password>] BitlBee can save your settings so you won't have to enter all your IM passwords every time you log in. If you want the Bee to save your settings, use the register command. Please do pick a secure password, don't just use your nick as your password. Please note that IRC is not an encrypted protocol, so the passwords still go over the network in plaintext. Evil people with evil sniffers will read it all. (So don't use your root password.. ;-) To identify yourself in later sessions, you can use the identify command. To change your password later, you can use the set password command. You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs. identify [-noload|-force] [<password>] Identify yourself with your password BitlBee saves all your settings (contacts, accounts, passwords) on-server. To prevent other users from just logging in as you and getting this information, you'll have to identify yourself with your password. You can register this password using the register command. Once you're registered, you can change your password using set password <password>. The -noload and -force flags can be used to identify when you're logged into some IM accounts already. -force will let you identify yourself and load all saved accounts (and keep the accounts you're logged into already). -noload will log you in but not load any accounts and settings saved under your current nickname. These will be overwritten once you save your settings (i.e. when you disconnect). You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs. drop <password> Drop your account Drop your BitlBee registration. Your account files will be removed and your password will be forgotten. For obvious security reasons, you have to specify your NickServ password to make this command work. blist [all|online|offline|away] List all the buddies in the current channel You can get a more readable buddy list using the blist command. If you want a complete list (including the offline users) you can use the all argument. Contact group management group [ list | info <group> ] The group list command shows a list of all groups defined so far. The group info command shows a list of all members of a the group <group>. If you want to move contacts between groups, you can use the IRC /invite command. Also, if you use the add command in a control channel configured to show just one group, the new contact will automatically be added to that group. Monitor, cancel, or reject file transfers transfer [<cancel> id | <reject>] Without parameters the currently pending file transfers and their status will be listed. Available actions are cancel and reject. See help transfer <action> for more information. transfer Cancels the file transfer with the given id transfer <cancel> id Cancels the file transfer with the given id transfer cancel 1 Canceling file transfer for test Rejects all incoming transfers transfer <reject> Rejects all incoming (not already transferring) file transfers. Since you probably have only one incoming transfer at a time, no id is neccessary. Or is it? transfer reject bitlbee-3.2.1/doc/user-guide/help.txt0000644000175000017500000020531312245477432017030 0ustar wilmerwilmer? These are the available help subjects: quickstart - A short introduction into BitlBee commands - All available commands and settings channels - About creating and customizing channels away - About setting away states groupchats - How to work with groupchats on BitlBee nick_changes - Changing your nickname without losing any settings smileys - A summary of some non-standard smileys you might find and fail to understand You can read more about them with help  Some more help can be found on http://wiki.bitlbee.org/. Bugs can be reported at http://bugs.bitlbee.org/. For other things than bug reports, you can join #BitlBee on OFTC (irc.oftc.net) (OFTC, *not* FreeNode!) and flame us right in the face. :-) % ?index These are the available help subjects: quickstart - A short introduction into BitlBee commands - All available commands and settings channels - About creating and customizing channels away - About setting away states groupchats - How to work with groupchats on BitlBee nick_changes - Changing your nickname without losing any settings smileys - A summary of some non-standard smileys you might find and fail to understand You can read more about them with help  % ?quickstart Welcome to BitlBee, your IRC gateway to ICQ, MSN, AOL, Jabber, Yahoo! and Twitter. The center of BitlBee is the control channel, &bitlbee. Two users will always be there, you (where "you" is the nickname you are using) and the system user, root. You need register so that all your IM settings (passwords, contacts, etc) can be saved on the BitlBee server. It's important that you pick a good password so no one else can access your account. Register with this password using the register command: register  (without the brackets!). Be sure to remember your password. The next time you connect to the BitlBee server you will need to identify  so that you will be recognised and logged in to all the IM services automatically. When finished, type help quickstart2 to continue. % ?quickstart2 Step Two: Add and Connect To your IM Account(s). To add an account to the account list you will need to use the account add command: account add []. For instance, suppose you have a Jabber account at jabber.org with handle bitlbee@jabber.org with password QuickStart, you would:  account add jabber bitlbee@jabber.org QuickStart  Account successfully added Other available IM protocols are msn, oscar, yahoo and twitter. OSCAR is the protocol used by ICQ and AOL. For more information about the account add command, see help account add. When you are finished adding your account(s) use the account on command to enable all your accounts, type help quickstart3 to continue. % ?quickstart3 Now you might want to add some contacts, to do this we will use the add command. It needs two arguments: a connection ID (which can be a number (try account list), protocol name or (part of) the screenname) and the user's handle. It is used in the following way: add   add 0 r2d2@example.com * r2d2 has joined &bitlbee In this case r2d2 is online, since he/she joins the channel immediately. If the user is not online you will not see them join until they log on. Lets say you accidentally added r2d3@example.com rather than r2d2@example.com, or maybe you just want to remove a user from your list because you never talk to them. To remove a name you will want to use the remove command: remove r2d3 Finally, if you have multiple users with similar names you may use the rename command to make it easier to remember: rename r2d2_ r2d2_aim When finished, type help quickstart4 to continue. % ?quickstart4 Step Five: Chatting. First of all, a person must be on your contact list for you to chat with them (unless it's a group chat, help groupchats for more). If someone not on your contact list sends you a message, simply add them to the proper account with the add command. Once they are on your list and online, you can chat with them in &bitlbee:  tux: hey, how's the weather down there?  you: a bit chilly! Note that, although all contacts are in the &bitlbee channel, only tux will actually receive this message. The &bitlbee channel shouldn't be confused with a real IRC channel. If you prefer chatting in a separate window, use the /msg or /query command, just like on real IRC. BitlBee will remember how you talk to someone and show his/her responses the same way. If you want to change the default behaviour (for people you haven't talked to yet), see help set private. You know the basics. If you want to know about some of the neat features BitlBee offers, please type help quickstart5. % ?quickstart5 So you want more than just chatting? Or maybe you're just looking for more features? With multiple channel support you can have contacts for specific protocols in their own channels, for instance, if you /join &msn you will join a channel that only contains your MSN contacts. Account tagging allows you to use the given account name rather than a number when referencing your account. If you wish to turn off your gtalk account, you may account gtalk off rather than account 3 off where "3" is the account number. You can type help set to learn more about the possible BitlBee user settings. Among these user settings you will find options for common issues, such as changing the charset, HTML stripping and automatic connecting (simply type set to see current user settings). For more subjects (like groupchats and away states), please type help index. If you're still looking for something, please visit us in #bitlbee on the OFTC network (you can connect via irc.bitlbee.org), or mail us your problem/suggestion. Good luck and enjoy the Bee! % ?commands These are all root commands. See help  for more details on each command. * account - IM-account list maintenance * channel - Channel list maintenance * chat - Chatroom list maintenance * add - Add a buddy to your contact list * info - Request user information * remove - Remove a buddy from your contact list * block - Block someone * allow - Unblock someone * otr - Off-the-Record encryption control * set - Miscellaneous settings * help - BitlBee help system * save - Save your account data * rename - Rename (renick) a buddy * yes - Accept a request * no - Deny a request * qlist - List all the unanswered questions root asked * register - Register yourself * identify - Identify yourself with your password * drop - Drop your account * blist - List all the buddies in the current channel * group - Contact group management * transfer - Monitor, cancel, or reject file transfers Most commands can be shortened. For example instead of account list, try ac l. % ?account Syntax: account [] [] Available actions: add, del, list, on, off and set. See help account  for more information. % ?account add Syntax: account add [] Adds an account on the given server with the specified protocol, username and password to the account list. Supported protocols right now are: Jabber, MSN, OSCAR (AIM/ICQ), Yahoo and Twitter. For more information about adding an account, see help account add . You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs. % ?account add jabber Syntax: account add jabber [] The handle should be a full handle, including the domain name. You can specify a servername if necessary. Normally BitlBee doesn't need this though, since it's able to find out the server by doing DNS SRV lookups. In previous versions it was also possible to specify port numbers and/or SSL in the server tag. This is deprecated and should now be done using the account set command. This also applies to specifying a resource in the handle (like wilmer@bitlbee.org/work). % ?account add msn Syntax: account add msn [] For MSN connections there are no special arguments. % ?account add oscar Syntax: account add oscar [] OSCAR is the protocol used to connect to AIM and/or ICQ. The servers will automatically detect if you're using a numeric or non-numeric username so there's no need to tell which network you want to connect to. Example:  account add oscar 72696705 hobbelmeeuw  Account successfully added % ?account add twitter Syntax: account add twitter This module gives you simple access to Twitter and Twitter API compatible services. By default all your Twitter contacts will appear in a new channel called #twitter_yourusername. You can change this behaviour using the mode setting (see help set mode). To send tweets yourself, send them to the twitter_(yourusername) contact, or just write in the groupchat channel if you enabled that option. Since Twitter now requires OAuth authentication, you should not enter your Twitter password into BitlBee. Just type a bogus password. The first time you log in, BitlBee will start OAuth authentication. (See help set oauth.) To use a non-Twitter service, change the base_url setting. For identi.ca, you can simply use account add identica. % ?account add identica Syntax: account add identica Same protocol as twitter, but defaults to a base_url pointing at identi.ca. It also works with OAuth (so don't specify your password). % ?account add yahoo Syntax: account add yahoo [] For Yahoo! connections there are no special arguments. % ?account del Syntax: account del This commands deletes an account from your account list. You should signoff the account before deleting it. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. % ?account on Syntax: account [] on This command will try to log into the specified account. If no account is specified, BitlBee will log into all the accounts that have the auto_connect flag set. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. % ?account off Syntax: account [] off This command disconnects the connection for the specified account. If no account is specified, BitlBee will deactivate all active accounts and cancel all pending reconnects. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. % ?account list Syntax: account list This command gives you a list of all the accounts known by BitlBee. % ?account set Syntax: account set Syntax: account set Syntax: account set Syntax: account set -del This command can be used to change various settings for IM accounts. For all protocols, this command can be used to change the handle or the password BitlBee uses to log in and if it should be logged in automatically. Some protocols have additional settings. You can see the settings available for a connection by typing account set. For more infomation about a setting, see help set . The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. % ?channel Syntax: channel [] [] Available actions: del, list, set. See help channel  for more information. There is no channel add command. To create a new channel, just use the IRC /join command. See also help channels and help groupchats. % ?channel del Syntax: channel del Remove a channel and forget all its settings. You can only remove channels you're not currently in, and can't remove the main control channel. (You can, however, leave it.) % ?channel list Syntax: channel list This command gives you a list of all the channels you configured. % ?channel set Syntax: channel [] set Syntax: channel [] set Syntax: channel [] set Syntax: channel [] set -del This command can be used to change various settings for channels. Different channel types support different settings. You can see the settings available for a channel by typing channel set. For more infomation about a setting, see help set . The channel ID can be a number (see channel list), or (part of) its name, as long as it matches only one channel. If you want to change settings of the current channel, you can omit the channel ID. % ?chat Syntax: chat [] Available actions: add, with. See help chat  for more information. % ?chat add Syntax: chat add [] Add a chatroom to the list of chatrooms you're interested in. BitlBee needs this list to map room names to a proper IRC channel name. After adding a room to your list, you can simply use the IRC /join command to enter the room. Also, you can tell BitlBee to automatically join the room when you log in. (See chat set) Password-protected rooms work exactly like on IRC, by passing the password as an extra argument to /join. % ?chat with Syntax: chat with While most chat subcommands are about named chatrooms, this command can be used to open an unnamed groupchat with one or more persons. This command is what /join #nickname used to do in older BitlBee versions. % ?add Syntax: add [] Syntax: add -tmp [] Adds the given buddy at the specified connection to your buddy list. The account ID can be a number (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. If you want, you can also tell BitlBee what nick to give the new contact. The -tmp option adds the buddy to the internal BitlBee structures only, not to the real contact list (like done by set handle_unknown add). This allows you to talk to people who are not in your contact list. This normally won't show you any presence notifications. If you use this command in a control channel containing people from only one group, the new contact will be added to that group automatically. Example:  add 3 gryp@jabber.org grijp * grijp has joined &bitlbee % ?info Syntax: info Syntax: info Requests IM-network-specific information about the specified user. The amount of information you'll get differs per protocol. For some protocols (ATM Yahoo! and MSN) it'll give you an URL which you can visit with a normal web browser to get the information. Example:  info 0 72696705  User info - UIN: 72696705 Nick: Lintux First/Last name: Wilmer van der Gaast E-mail: lintux@lintux.cx % ?remove Syntax: remove Removes the specified nick from your buddy list. Example:  remove gryp * gryp has quit [Leaving...] % ?block Syntax: block Syntax: block Syntax: block Puts the specified user on your ignore list. Either specify the user's nick when you have him/her in your contact list or a connection number and a user handle. When called with only a connection specification as an argument, the command displays the current block list for that connection. % ?allow Syntax: allow Syntax: allow Reverse of block. Unignores the specified user or user handle on specified connection. When called with only a connection specification as an argument, the command displays the current allow list for that connection. % ?otr Syntax: otr [] Available subcommands: connect, disconnect, reconnect, smp, smpq, trust, info, keygen, and forget. See help otr  for more information. % ?otr connect Syntax: otr connect Attempts to establish an encrypted connection with the specified user by sending a magic string. % ?otr disconnect Syntax: otr disconnect Resets the connection with the specified user to cleartext. % ?otr reconnect Syntax: otr reconnect Breaks and re-establishes the encrypted connection with the specified user. Useful if something got desynced. Equivalent to otr disconnect followed by otr connect. % ?otr smp Syntax: otr smp Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol. If an SMP challenge has been received from the given user, responds with the specified secret/answer. Otherwise, sends a challenge for the given secret. Note that there are two flavors of SMP challenges: "shared-secret" and "question & answer". This command is used to respond to both of them, or to initiate a shared-secret style exchange. Use the otr smpq command to initiate a "Q&A" session. When responding to a "Q&A" challenge, the local trust value is not altered. Only the asking party sets trust in the case of success. Use otr smpq to pose your challenge. In a shared-secret exchange, both parties set their trust according to the outcome. % ?otr smpq Syntax: otr smpq Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol, Q&A style. Initiates an SMP session in "question & answer" style. The question is transmitted with the initial SMP packet and used to prompt the other party. You must be confident that only they know the answer. If the protocol succeeds (i.e. they answer correctly), the fingerprint will be trusted. Note that the answer must be entered exactly, case and punctuation count! Note that this style of SMP only affects the trust setting on your side. Expect your opponent to send you their own challenge. Alternatively, if you and the other party have a shared secret, use the otr smp command. % ?otr trust Syntax: otr trust Manually affirms trust in the specified fingerprint, given as five blocks of precisely eight (hexadecimal) digits each. % ?otr info Syntax: otr info Syntax: otr info Shows information about the OTR state. The first form lists our private keys and current OTR contexts. The second form displays information about the connection with a given user, including the list of their known fingerprints. % ?otr keygen Syntax: otr keygen Generates a new OTR private key for the given account. % ?otr forget Syntax: otr forget Forgets some part of our OTR userstate. Available things: fingerprint, context, and key. See help otr forget  for more information. % ?otr forget fingerprint Syntax: otr forget fingerprint Drops the specified fingerprint from the given user's OTR connection context. It is allowed to specify only a (unique) prefix of the desired fingerprint. % ?otr forget context Syntax: otr forget context Forgets the entire OTR context associated with the given user. This includes current message and protocol states, as well as any fingerprints for that user. % ?otr forget key Syntax: otr forget key Forgets an OTR private key matching the specified fingerprint. It is allowed to specify only a (unique) prefix of the fingerprint. % ?set Syntax: set Syntax: set Syntax: set Syntax: set -del Without any arguments, this command lists all the set variables. You can also specify a single argument, a variable name, to get that variable's value. To change this value, specify the new value as the second argument. With -del you can reset a setting to its default value. To get more help information about a setting, try: Example:  help set private % ?help Syntax: help [subject] This command gives you the help information you're reading right now. If you don't give any arguments, it'll give a short help index. % ?save Syntax: save This command saves all your nicks and accounts immediately. Handy if you have the autosave functionality disabled, or if you don't trust the program's stability... ;-) % ?rename Syntax: rename Syntax: rename -del Renick a user in your buddy list. Very useful, in fact just very important, if you got a lot of people with stupid account names (or hard ICQ numbers). rename -del can be used to erase your manually set nickname for a contact and reset it to what was automatically generated. Example:  rename itsme_ you * itsme_ is now known as you % ?yes Syntax: yes [] Sometimes an IM-module might want to ask you a question. (Accept this user as your buddy or not?) To accept a question, use the yes command. By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the qlist command for a list of questions. % ?no Syntax: no [] Sometimes an IM-module might want to ask you a question. (Accept this user as your buddy or not?) To reject a question, use the no command. By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the qlist command for a list of questions. % ?qlist Syntax: qlist This gives you a list of all the unanswered questions from root. % ?register Syntax: register [] BitlBee can save your settings so you won't have to enter all your IM passwords every time you log in. If you want the Bee to save your settings, use the register command. Please do pick a secure password, don't just use your nick as your password. Please note that IRC is not an encrypted protocol, so the passwords still go over the network in plaintext. Evil people with evil sniffers will read it all. (So don't use your root password.. ;-) To identify yourself in later sessions, you can use the identify command. To change your password later, you can use the set password command. You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs. % ?identify Syntax: identify [-noload|-force] [] BitlBee saves all your settings (contacts, accounts, passwords) on-server. To prevent other users from just logging in as you and getting this information, you'll have to identify yourself with your password. You can register this password using the register command. Once you're registered, you can change your password using set password . The -noload and -force flags can be used to identify when you're logged into some IM accounts already. -force will let you identify yourself and load all saved accounts (and keep the accounts you're logged into already). -noload will log you in but not load any accounts and settings saved under your current nickname. These will be overwritten once you save your settings (i.e. when you disconnect). You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs. % ?drop Syntax: drop Drop your BitlBee registration. Your account files will be removed and your password will be forgotten. For obvious security reasons, you have to specify your NickServ password to make this command work. % ?blist Syntax: blist [all|online|offline|away] You can get a more readable buddy list using the blist command. If you want a complete list (including the offline users) you can use the all argument. % ?group Syntax: group [ list | info ] The group list command shows a list of all groups defined so far. The group info command shows a list of all members of a the group . If you want to move contacts between groups, you can use the IRC /invite command. Also, if you use the add command in a control channel configured to show just one group, the new contact will automatically be added to that group. % ?transfer Syntax: transfer [ id | ] Without parameters the currently pending file transfers and their status will be listed. Available actions are cancel and reject. See help transfer  for more information.  transfer % ?transfer cancel Syntax: transfer id Cancels the file transfer with the given id Example:  transfer cancel 1  Canceling file transfer for test % ?transfer reject Syntax: transfer Rejects all incoming (not already transferring) file transfers. Since you probably have only one incoming transfer at a time, no id is neccessary. Or is it? Example:  transfer reject % ?set account Type: string Scope: channel For control channels with fill_by set to account: Set this setting to the account id (numeric, or part of the username) of the account containing the contacts you want to see in this channel. % ?set allow_takeover Type: boolean Scope: global Default: true When you're already connected to a BitlBee server and you connect (and identify) again, BitlBee will offer to migrate your existing session to the new connection. If for whatever reason you don't want this, you can disable this setting. % ?set auto_connect Type: boolean Scope: account,global Default: true With this option enabled, when you identify BitlBee will automatically connect to your accounts, with this disabled it will not do this. This setting can also be changed for specific accounts using the account set command. (However, these values will be ignored if the global auto_connect setting is disabled!) % ?set auto_join Type: boolean Scope: channel Default: false With this option enabled, BitlBee will automatically join this channel when you log in. % ?set auto_reconnect Type: boolean Scope: account,global Default: true If an IM-connections breaks, you're supposed to bring it back up yourself. Having BitlBee do this automatically might not always be a good idea, for several reasons. If you want the connections to be restored automatically, you can enable this setting. See also the auto_reconnect_delay setting. This setting can also be changed for specific accounts using the account set command. (However, these values will be ignored if the global auto_reconnect setting is disabled!) % ?set auto_reconnect_delay Type: string Scope: global Default: 5*3<900 Tell BitlBee after how many seconds it should attempt to bring a broken IM-connection back up. This can be one integer, for a constant delay. One can also set it to something like "10*10", which means wait for ten seconds on the first reconnect, multiply it by ten on every failure. Once successfully connected, this delay is re-set to the initial value. With < you can give a maximum delay. See also the auto_reconnect setting. % ?set auto_reply_timeout Type: integer Scope: account Default: 10800 For Twitter accounts: If you respond to Tweets IRC-style (like "nickname: reply"), this will automatically be converted to the usual Twitter format ("@screenname reply"). By default, BitlBee will then also add a reference to that person's most recent Tweet, unless that message is older than the value of this setting in seconds. If you want to disable this feature, just set this to 0. Alternatively, if you want to write a message once that is not a reply, use the Twitter reply syntax (@screenname). % ?set away Type: string Scope: account,global To mark yourself as away, it is recommended to just use /away, like on normal IRC networks. If you want to mark yourself as away on only one IM network, you can use this per-account setting. You can set it to any value and BitlBee will try to map it to the most appropriate away state for every open IM connection, or set it as a free-form away message where possible. Any per-account away setting will override globally set away states. To un-set the setting, use set -del away. % ?set away_devoice Type: boolean Scope: global Default: true With this option enabled, the root user devoices people when they go away (just away, not offline) and gives the voice back when they come back. You might dislike the voice-floods you'll get if your contact list is huge, so this option can be disabled. Replaced with the show_users setting. See help show_users. % ?set away_reply_timeout Type: integer Scope: global Default: 3600 Most IRC servers send a user's away message every time s/he gets a private message, to inform the sender that they may not get a response immediately. With this setting set to 0, BitlBee will also behave like this. Since not all IRC clients do an excellent job at suppressing these messages, this setting lets BitlBee do it instead. BitlBee will wait this many seconds (or until the away state/message changes) before re-informing you that the person's away. % ?set base_url Type: string Scope: account Default: http://api.twitter.com/1 There are more services that understand the Twitter API than just Twitter.com. BitlBee can connect to all Twitter API implementations. For example, set this setting to http://identi.ca/api to use Identi.ca. Keep two things in mind: When not using Twitter, you must also disable the oauth setting as it currently only works with Twitter. If you're still having issues, make sure there is no slash at the end of the URL you enter here. % ?set charset Type: string Scope: global Default: utf-8 Possible Values: you can get a list of all possible values by doing 'iconv -l' in a shell This setting tells BitlBee what your IRC client sends and expects. It should be equal to the charset setting of your IRC client if you want to be able to send and receive non-ASCII text properly. Most systems use UTF-8 these days. On older systems, an iso8859 charset may work better. For example, iso8859-1 is the best choice for most Western countries. You can try to find what works best for you on http://www.unicodecharacter.com/charsets/iso8859.html % ?set color_encrypted Type: boolean Scope: global Default: true If set to true, BitlBee will color incoming encrypted messages according to their fingerprint trust level: untrusted=red, trusted=green. % ?set chat_type Type: string Scope: channel Default: groupchat Possible Values: groupchat, room There are two kinds of chat channels: simple groupchats (basically normal IM chats with more than two participants) and names chatrooms, more similar to IRC channels. BitlBee supports both types. With this setting set to groupchat (the default), you can just invite people into the room and start talking. For setting up named chatrooms, it's currently easier to just use the chat add command. % ?set commands Type: boolean Scope: account Default: true Possible Values: true, false, strict With this setting enabled, you can use some commands in your Twitter channel/query. The commands are simple and not documented in too much detail: undo #[] - Delete your last Tweet (or one with the given ID) rt  - Retweet someone's last Tweet (or one with the given ID) reply  - Reply to a Tweet (with a reply-to reference) report  - Report the given user (or the user who posted the tweet with the given ID) for sending spam. This will also block them. follow  - Start following a person unfollow  - Stop following a person favourite  - Favourite the given user's most recent tweet, or the given tweet ID. post  - Post a tweet Anything that doesn't look like a command will be treated as a tweet. Watch out for typos, or to avoid this behaviour, you can set this setting to strict, which causes the post command to become mandatory for posting a tweet. % ?set debug Type: boolean Scope: global Default: false Some debugging messages can be logged if you wish. They're probably not really useful for you, unless you're doing some development on BitlBee. This feature is not currently used for anything so don't expect this to generate any output. % ?set default_target Type: string Scope: global Default: root Possible Values: root, last With this value set to root, lines written in a control channel without any nickname in front of them will be interpreted as commands. If you want BitlBee to send those lines to the last person you addressed in that control channel, set this to last. % ?set display_name Type: string Scope: account Currently only available for MSN connections. This setting allows you to read and change your "friendly name" for this connection. Since this is a server-side setting, it can't be changed when the account is off-line. % ?set display_namechanges Type: boolean Scope: global Default: false With this option enabled, root will inform you when someone in your buddy list changes his/her "friendly name". % ?set display_timestamps Type: boolean Scope: global Default: true When incoming messages are old (i.e. offline messages and channel backlogs), BitlBee will prepend them with a timestamp. If you find them ugly or useless, you can use this setting to hide them. % ?set fill_by Type: string Scope: channel Default: all Possible Values: all, group, account, protocol For control channels only: This setting determines which contacts the channel gets populated with. By default, control channels will contain all your contacts. You instead select contacts by buddy group, IM account or IM protocol. Change this setting and the corresponding account/group/protocol setting to set up this selection. With a ! prefix an inverted channel can be created, for example with this setting set to !group you can create a channel with all users not in that group. Note that, when creating a new channel, BitlBee will try to preconfigure the channel for you, based on the channel name. See help channels. % ?set group Type: string Scope: channel For control channels with fill_by set to group: Set this setting to the name of the group containing the contacts you want to see in this channel. % ?set handle_unknown Type: string Scope: global Default: add_channel Possible Values: root, add, add_private, add_channel, ignore By default, messages from people who aren't in your contact list are shown in a control channel instead of as a private message. If you prefer to ignore messages from people you don't know, you can set this one to "ignore". "add_private" and "add_channel" are like add, but you can use them to make messages from unknown buddies appear in the channel instead of a query window. Although these users will appear in your control channel, they aren't added to your real contact list. When you restart BitlBee, these auto-added users will be gone. If you want to keep someone in your list, you have to fixate the add using the add command. % ?set ignore_auth_requests Type: boolean Scope: account Default: false Only supported by OSCAR so far, you can use this setting to ignore ICQ authorization requests, which are hardly used for legitimate (i.e. non-spam) reasons anymore. % ?set lcnicks Type: boolean Scope: global Default: true Hereby you can change whether you want all lower case nick names or leave the case as it intended by your peer. % ?set local_display_name Type: boolean Scope: account Default: false Mostly meant to work around a bug in MSN servers (forgetting the display name set by the user), this setting tells BitlBee to store your display name locally and set this name on the MSN servers when connecting. % ?set mail_notifications Type: boolean Scope: account Default: false Some protocols (MSN, Yahoo!) can notify via IM about new e-mail. Since most people use their Hotmail/Yahoo! addresses as a spam-box, this is disabled default. If you want these notifications, you can enable this setting. % ?set message_length Type: integer Scope: account Default: 140 Since Twitter rejects messages longer than 140 characters, BitlBee can count message length and emit a warning instead of waiting for Twitter to reject it. You can change this limit here but this won't disable length checks on Twitter's side. You can also set it to 0 to disable the check in case you believe BitlBee doesn't count the characters correctly. % ?set stream Type: boolean Scope: account Default: true For Twitter accounts, this setting enables use of the Streaming API. This automatically gives you incoming DMs as well. For other Twitter-like services, this setting is not supported. % ?set target_url_length Type: integer Scope: account Default: 20 Twitter replaces every URL with fixed-length t.co URLs. BitlBee is able to take t.co urls into account when calculating message_length replacing the actual URL length with target_url_length. Setting target_url_length to 0 disables this feature. This setting is disabled for identica accounts by default and will not affect anything other than message safety checks (i.e. Twitter will still replace your URLs with t.co links, even if that makes them longer). % ?set mode Type: string Scope: account Default: chat Possible Values: one, many, chat By default, BitlBee will create a separate channel (called #twitter_yourusername) for all your Twitter contacts/messages. If you don't want an extra channel, you can set this setting to "one" (everything will come from one nick, twitter_yourusername), or to "many" (individual nicks for everyone). With modes "chat" and "many", you can send direct messages by /msg'ing your contacts directly. Note, however, that incoming DMs are not fetched yet. With modes "many" and "one", you can post tweets by /msg'ing the twitter_yourusername contact. In mode "chat", messages posted in the Twitter channel will also be posted as tweets. % ?set mobile_is_away Type: boolean Scope: global Default: false Most IM networks have a mobile version of their client. People who use these may not be paying that much attention to messages coming in. By enabling this setting, people using mobile clients will always be shown as away. % ?set nick Type: string Scope: chat You can use this option to set your nickname in a chatroom. You won't see this nickname yourself, but other people in the room will. By default, BitlBee will use your username as the chatroom nickname. % ?set nick_format Type: string Scope: account,global Default: %-@nick By default, BitlBee tries to derive sensible nicknames for all your contacts from their IM handles. In some cases, IM modules (ICQ for example) will provide a nickname suggestion, which will then be used instead. This setting lets you change this behaviour. Whenever this setting is set for an account, it will be used for all its contacts. If it's not set, the global value will be used. It's easier to describe this setting using a few examples: FB-%full_name will make all nicknames start with "FB-", followed by the person's full name. For example you can set this format for your Facebook account so all Facebook contacts are clearly marked. [%group]%-@nick will make all nicknames start with the group the contact is in between square brackets, followed by the nickname suggestions from the IM module if available, or otherwise the handle. Because of the "-@" part, everything from the first @ will be stripped. See help nick_format for more information. % ?set nick_source Type: string Scope: account Default: handle Possible Values: handle, full_name, first_name By default, BitlBee generates a nickname for every contact by taking its handle and chopping off everything after the @. In some cases, this gives very inconvenient nicknames. The Facebook XMPP server is a good example, as all Facebook XMPP handles are numeric. With this setting set to full_name, the person's full name is used to generate a nickname. Or if you don't like long nicknames, set this setting to first_name instead and only the first word will be used. Note that the full name can be full of non-ASCII characters which will be stripped off. % ?set oauth Type: boolean Scope: account Default: true This enables OAuth authentication for an IM account; right now the Twitter (working for Twitter only) and Jabber (for Google Talk, Facebook and MSN Messenger) module support it. With OAuth enabled, you shouldn't tell BitlBee your account password. Just add your account with a bogus password and type account on. BitlBee will then give you a URL to authenticate with the service. If this succeeds, you will get a PIN code which you can give back to BitlBee to finish the process. The resulting access token will be saved permanently, so you have to do this only once. If for any reason you want to/have to reauthenticate, you can use account set to reset the account password to something random. % ?set ops Type: string Scope: global Default: both Possible Values: both, root, user, none Some people prefer themself and root to have operator status in &bitlbee, other people don't. You can change these states using this setting. The value "both" means both user and root get ops. "root" means, well, just root. "user" means just the user. "none" means nobody will get operator status. % ?set otr_policy Type: string Scope: global Default: opportunistic Possible Values: never, opportunistic, manual, always This setting controls the policy for establishing Off-the-Record connections. A value of "never" effectively disables the OTR subsystem. In "opportunistic" mode, a magic whitespace pattern will be appended to the first message sent to any user. If the peer is also running opportunistic OTR, an encrypted connection will be set up automatically. On "manual", on the other hand, OTR connections must be established explicitly using otr connect. Finally, the setting "always" enforces encrypted communication by causing BitlBee to refuse to send any cleartext messages at all. % ?set password Type: string Scope: account,global Use this global setting to change your "NickServ" password. This setting is also available for all IM accounts to change the password BitlBee uses to connect to the service. Note that BitlBee will always say this setting is empty. This doesn't mean there is no password, it just means that, for security reasons, BitlBee stores passwords somewhere else so they can't just be retrieved in plain text. % ?set paste_buffer Type: boolean Scope: global Default: false By default, when you send a message to someone, BitlBee forwards this message to the user immediately. When you paste a large number of lines, the lines will be sent in separate messages, which might not be very nice to read. If you enable this setting, BitlBee will buffer your messages and wait for more data. Using the paste_buffer_delay setting you can specify the number of seconds BitlBee should wait for more data before the complete message is sent. Please note that if you remove a buddy from your list (or if the connection to that user drops) and there's still data in the buffer, this data will be lost. BitlBee will not try to send the message to the user in those cases. % ?set paste_buffer_delay Type: integer Scope: global Default: 200 Tell BitlBee after how many (mili)seconds a buffered message should be sent. Values greater than 5 will be interpreted as miliseconds, 5 and lower as seconds. See also the paste_buffer setting. % ?set port Type: integer Scope: account Currently only available for Jabber connections. Specifies the port number to connect to. Usually this should be set to 5222, or 5223 for SSL-connections. % ?set priority Type: integer Scope: account Default: 0 Can be set for Jabber connections. When connecting to one account from multiple places, this priority value will help the server to determine where to deliver incoming messages (that aren't addressed to a specific resource already). According to RFC 3921 servers will always deliver messages to the server with the highest priority value. Mmessages will not be delivered to resources with a negative priority setting (and should be saved as an off-line message if all available resources have a negative priority value). % ?set private Type: boolean Scope: global Default: true If value is true, messages from users will appear in separate query windows. If false, messages from users will appear in a control channel. This setting is remembered (during one session) per-user, this setting only changes the default state. This option takes effect as soon as you reconnect. % ?set protocol Type: string Scope: channel For control channels with fill_by set to protocol: Set this setting to the name of the IM protocol of all contacts you want to see in this channel. % ?set query_order Type: string Scope: global Default: lifo Possible Values: lifo, fifo This changes the order in which the questions from root (usually authorization requests from buddies) should be answered. When set to lifo, BitlBee immediately displays all new questions and they should be answered in reverse order. When this is set to fifo, BitlBee displays the first question which comes in and caches all the others until you answer the first one. Although the fifo setting might sound more logical (and used to be the default behaviour in older BitlBee versions), it turned out not to be very convenient for many users when they missed the first question (and never received the next ones). % ?set resource Type: string Scope: account Default: BitlBee Can be set for Jabber connections. You can use this to connect to your Jabber account from multiple clients at once, with every client using a different resource string. % ?set resource_select Type: string Scope: account Default: activity Possible Values: priority, activity Because the IRC interface makes it pretty hard to specify the resource to talk to (when a buddy is online through different resources), this setting was added. Normally it's set to priority which means messages will always be delivered to the buddy's resource with the highest priority. If the setting is set to activity, messages will be delivered to the resource that was last used to send you a message (or the resource that most recently connected). % ?set root_nick Type: string Scope: global Default: root Normally the "bot" that takes all your BitlBee commands is called "root". If you don't like this name, you can rename it to anything else using the rename command, or by changing this setting. % ?set save_on_quit Type: boolean Scope: global Default: true If enabled causes BitlBee to save all current settings and account details when user disconnects. This is enabled by default, and these days there's not really a reason to have it disabled anymore. % ?set server Type: string Scope: account Can be set for Jabber- and OSCAR-connections. For Jabber, you might have to set this if the servername isn't equal to the part after the @ in the Jabber handle. For OSCAR this shouldn't be necessary anymore in recent BitlBee versions. % ?set show_ids Type: boolean Scope: account Default: true Enable this setting on a Twitter account to have BitlBee include a two-digit "id" in front of every message. This id can then be used for replies and retweets. % ?set show_offline Type: boolean Scope: global Default: false If enabled causes BitlBee to also show offline users in Channel. Online-users will get op, away-users voice and offline users none of both. This option takes effect as soon as you reconnect. Replaced with the show_users setting. See help show_users. % ?set show_users Type: string Scope: channel Default: online+,away Comma-separated list of statuses of users you want in the channel, and any modes they should have. The following statuses are currently recognised: online (i.e. available, not away), away, and offline. If a status is followed by a valid channel mode character (@, % or +), it will be given to users with that status. For example, online@,away+,offline will show all users in the channel. Online people will have +o, people who are online but away will have +v, and others will have no special modes. % ?set simulate_netsplit Type: boolean Scope: global Default: true Some IRC clients parse quit messages sent by the IRC server to see if someone really left or just disappeared because of a netsplit. By default, BitlBee tries to simulate netsplit-like quit messages to keep the control channels window clean. If you don't like this (or if your IRC client doesn't support this) you can disable this setting. % ?set ssl Type: boolean Scope: account Default: false Currently only available for Jabber connections. Set this to true if you want to connect to the server on an SSL-enabled port (usually 5223). Please note that this method of establishing a secure connection to the server has long been deprecated. You are encouraged to look at the tls setting instead. % ?set status Type: string Scope: account,global Most IM protocols support status messages, similar to away messages. They can be used to indicate things like your location or activity, without showing up as away/busy. This setting can be used to set such a message. It will be available as a per-account setting for protocols that support it, and also as a global setting (which will then automatically be used for all protocols that support it). Away states set using /away or the away setting will override this setting. To clear the setting, use set -del status. % ?set strip_html Type: boolean Scope: global Default: true Determines what BitlBee should do with HTML in messages. Normally this is turned on and HTML will be stripped from messages, if BitlBee thinks there is HTML. If BitlBee fails to detect this sometimes (most likely in AIM messages over an ICQ connection), you can set this setting to always, but this might sometimes accidentally strip non-HTML things too. % ?set strip_newlines Type: boolean Scope: account Default: false Turn on this flag to prevent tweets from spanning over multiple lines. % ?set show_old_mentions Type: integer Scope: account Default: 20 This setting specifies the number of old mentions to fetch on connection. Must be less or equal to 200. Setting it to 0 disables this feature. % ?set switchboard_keepalives Type: boolean Scope: account Default: false Turn on this flag if you have difficulties talking to offline/invisible contacts. With this setting enabled, BitlBee will send keepalives to MSN switchboards with offline/invisible contacts every twenty seconds. This should keep the server and client on the other side from shutting it down. This is useful because BitlBee doesn't support MSN offline messages yet and the MSN servers won't let the user reopen switchboards to offline users. Once offline messaging is supported, this flag might be removed. % ?set tag Type: string Scope: account For every account you have, you can set a tag you can use to uniquely identify that account. This tag can be used instead of the account number (or protocol name, or part of the screenname) when using commands like account, add, etc. You can't have two accounts with one and the same account tag. By default, it will be set to the name of the IM protocol. Once you add a second account on an IM network, a numeric suffix will be added, starting with 2. % ?set timezone Type: string Scope: global Default: local Possible Values: local, utc, gmt, timezone-spec If message timestamps are available for offline messages or chatroom backlogs, BitlBee will display them as part of the message. By default it will use the local timezone. If you're not in the same timezone as the BitlBee server, you can adjust the timestamps using this setting. Values local/utc/gmt should be self-explanatory. timezone-spec is a time offset in hours:minutes, for example: -8 for Pacific Standard Time, +2 for Central European Summer Time, +5:30 for Indian Standard Time. % ?set tls Type: boolean Scope: account Default: true By default (with this setting enabled), BitlBee will require Jabber servers to offer encryption via StartTLS and refuse to connect if they don't. If you set this to "try", BitlBee will use StartTLS only if it's offered. With the setting disabled, StartTLS support will be ignored and avoided entirely. % ?set tls_verify Type: boolean Scope: account Default: true Currently only available for Jabber connections in combination with the tls setting. Set this to true if you want BitlBee to strictly verify the server's certificate against a list of trusted certificate authorities. The hostname used in the certificate verification is the value of the server setting if the latter is nonempty and the domain of the username else. If you get a hostname related error when connecting to Google Talk with a username from the gmail.com or googlemail.com domain, please try to empty the server setting. Please note that no certificate verification is performed when the ssl setting is used, or when the CAfile setting in bitlbee.conf is not set. % ?set to_char Type: string Scope: global Default: ": " It's customary that messages meant for one specific person on an IRC channel are prepended by his/her alias followed by a colon ':'. BitlBee does this by default. If you prefer a different character, you can set it using set to_char. Please note that this setting is only used for incoming messages. For outgoing messages you can use ':' (colon) or ',' to separate the destination nick from the message, and this is not configurable. % ?set translate_to_nicks Type: boolean Scope: channel Default: true IRC's nickname namespace is quite limited compared to most IM protocols. Not any non-ASCII characters are allowed, in fact nicknames have to be mostly alpha-numeric. Also, BitlBee has to add underscores sometimes to avoid nickname collisions. While normally the BitlBee user is the only one seeing these names, they may be exposed to other chatroom participants for example when addressing someone in the channel (with or without tab completion). By default BitlBee will translate these stripped nicknames back to the original nick. If you don't want this, disable this setting. % ?set type Type: string Scope: channel Default: control Possible Values: control, chat BitlBee supports two kinds of channels: control channels (usually with a name starting with a &) and chatroom channels (name usually starts with a #). See help channels for a full description of channel types in BitlBee. % ?set typing_notice Type: boolean Scope: global Default: false Sends you a /notice when a user starts typing a message (if supported by the IM protocol and the user's client). To use this, you most likely want to use a script in your IRC client to show this information in a more sensible way. % ?set user_agent Type: string Scope: account Default: BitlBee Some Jabber servers are configured to only allow a few (or even just one) kinds of XMPP clients to connect to them. You can change this setting to make BitlBee present itself as a different client, so that you can still connect to these servers. % ?set utf8_nicks Type: boolean Scope: global Default: false Officially, IRC nicknames are restricted to ASCII. Recently some clients and servers started supporting Unicode nicknames though. To enable UTF-8 nickname support (contacts only) in BitlBee, enable this setting. To avoid confusing old clients, this setting is disabled by default. Be careful when you try it, and be prepared to be locked out of your BitlBee in case your client interacts poorly with UTF-8 nicknames. % ?set web_aware Type: string Scope: account Default: false ICQ allows people to see if you're on-line via a CGI-script. (http://status.icq.com/online.gif?icq=UIN) This can be nice to put on your website, but it seems that spammers also use it to see if you're online without having to add you to their contact list. So to prevent ICQ spamming, recent versions of BitlBee disable this feature by default. Unless you really intend to use this feature somewhere (on forums or maybe a website), it's probably better to keep this setting disabled. % ?set xmlconsole Type: boolean Scope: account Default: false The Jabber module allows you to add a buddy xmlconsole to your contact list, which will then show you the raw XMPP stream between you and the server. You can also send XMPP packets to this buddy, which will then be sent to the server. If you want to enable this XML console permanently (and at login time already), you can set this setting. % ?misc % ?smileys All MSN smileys (except one) are case insensitive and work without the nose too. (Y) - Thumbs up (N) - Thumbs down (B) - Beer mug (D) - Martini glass (X) - Girl (Z) - Boy (6) - Devil smiley :-[ - Vampire bat (}) - Right hug ({) - Left hug (M) - MSN Messenger or Windows Messenger icon (think a BitlBee logo here ;) :-S - Crooked smiley (Confused smiley) :-$ - Embarrassed smiley (H) - Smiley with sunglasses :-@ - Angry smiley (A) - Angel smiley (L) - Red heart (Love) (U) - Broken heart (K) - Red lips (Kiss) (G) - Gift with bow (F) - Red rose (W) - Wilted rose (P) - Camera (~) - Film strip (T) - Telephone receiver (@) - Cat face (&) - Dog's head (C) - Coffee cup (I) - Light bulb (S) - Half-moon (Case sensitive!) (*) - Star (8) - Musical eighth note (E) - Envelope (^) - Birthday cake (O) - Clock % ?groupchats BitlBee now supports groupchats on all IM networks. This text will try to explain you how they work. As soon as someone invites you into a groupchat, you will be force-joined or invited (depending on the protocol) into a new virtual channel with all the people in there. You can leave the channel at any time, just like you would close the window in regular IM clients. Please note that root-commands don't work in groupchat channels, they only work in control channels (or to root directly). Of course you can also create your own groupchats. Type help groupchats2 to see how. % ?groupchats2 To open a groupchat, use the chat with command. For example, to start a groupchat with the person lisa_msn in it, just type chat with lisa_msn. BitlBee will create a new virtual channel with root, you and lisa_msn in it. Then, just use the ordinary IRC /invite command to invite more people. Please do keep in mind that all the people have to be on the same network and contact list! You can't invite Yahoo! buddies into an MSN groupchat. Some protocols (like Jabber) also support named groupchats. BitlBee now supports these too. You can use the chat add command to join them. See help chat add for more information. % ?away To mark yourself as away, you can just use the /away command in your IRC client. BitlBee supports most away-states supported by the protocols. Away states have different names accross different protocols. BitlBee will try to pick the best available option for every connection: - Away - NA - Busy, DND - BRB - Phone - Lunch, Food - Invisible, Hidden So /away Food will set your state to "Out to lunch" on your MSN connection, and for most other connections the default, "Away" will be chosen. You can also add more information to your away message. Setting it to "Busy - Fixing BitlBee bugs" will set your IM-away-states to Busy, but your away message will be more descriptive for people on IRC. Most IM-protocols can also show this additional information to your buddies. If you want to set an away state for only one of your connections, you can use the per-account away setting. See help set away. % ?nick_changes BitlBee now allows you to change your nickname. So far this was not possible because it made managing saved accounts more complicated. The restriction no longer exists now though. When you change your nick (just using the /nick command), your logged-in status will be reset, which means any changes made to your settings/accounts will not be saved. To restore your logged-in status, you need to either use the register command to create an account under the new nickname, or use identify -noload to re-identify yourself under the new nickname. The -noload flag tells the command to verify your password and log you in, but not load any new settings. See help identify for more information. % ?channels You can have as many channels in BitlBee as you want. You maintain your channel list using the channel command. You can create new channels by just joining them, like on regular IRC networks. You can create two kinds of channels. Control channels, and groupchat channels. By default, BitlBee will set up new channels as control channels if their name starts with an &, and as chat channels if it starts with a #. Control channels are where you see your contacts. By default, you will have one control channel called &bitlbee, containing all your contacts. But you can create more, if you want, and divide your contact list accross several channels. For example, you can have one channel with all contacts from your MSN Messenger account in it. Or all contacts from the group called "Work". Type help channels2 to read more. % ?channels2 When you create a new channel, BitlBee will try to guess from its name which contacts to fill it with. For example, if the channel name (excluding the &) matches the name of a group in which you have one or more contacts, the channel will contain all those contacts. Any valid account ID (so a number, protocol name or part of screenname, as long as it's unique) can also be used as a channel name. So if you just join &msn, it will contain all your MSN contacts. And if you have a Facebook account set up, you can see its contacts by just joining &facebook. To start a simple group chat, you simply join a channel which a name starting with #, and invite people into it. All people you invite have to be on the same IM network and contact list. If you want to configure your own channels, you can use the channel set command. See help channels3 for more information. % ?channels3 The most important setting for a control channel is fill_by. It tells BitlBee what information should be used to decide if someone should be shown in the channel or not. After setting this setting to, for example, account, you also have to set the account setting. Example:  chan &wlm set fill_by account  fill_by = `account'  chan &wlm set account msn  account = `msn' Also, each channel has a show_users setting which lets you choose, for example, if you want to see only online contacts in a channel, or also/just offline contacts. Example:  chan &offline set show_users offline  show_users = `offline' See the help information for all these settings for more information. % ?nick_format The nick_format setting can be set globally using the set command, or per account using account set (so that you can set a per-account suffix/prefix or have nicknames generated from full names for certain accounts). The setting is basically some kind of format string. It can contain normal text that will be copied to the nick, combined with several variables: %nick - Nickname suggested for this contact by the IM protocol, or just the handle if no nickname was suggested. %handle - The handle/screenname of the contact. %full_name - The full name of the contact. %first_name - The first name of the contact (the full name up to the first space). %group - The name of the group this contact is a member of %account - Account tag of the contact Invalid characters (like spaces) will always be stripped. Depending on your locale settings, characters with accents will be converted to ASCII. See help nick_format2 for some more information. % ?nick_format2 Two modifiers are currently available: You can include only the first few characters of a variable by putting a number right after the %. For example, [%3group]%-@nick will include only the first three characters of the group name in the nick. Also, you can truncate variables from a certain character using the - modifier. For example, you may want to leave out everything after the @. %-@handle will expand to everything in the handle up to the first @. % ?whatsnew010206 Twitter support. See help account add twitter. % ?whatsnew010300 Support for multiple configurable control channels, each with a subset of your contact list. See help channels for more information. File transfer support for some protocols (more if you use libpurple). Just /DCC SEND stuff. Incoming files also become DCC transfers. Only if you run your own BitlBee instance: You can build a BitlBee that uses libpurple for connecting to IM networks instead of its own code, adding support for some of the more obscure IM protocols and features. Many more things, briefly described in help news1.3. % ?whatsnew030000 BitlBee can be compiled with support for OTR message encryption (not available on public servers since encryption should be end-to-end). The MSN module was heavily updated to support features added to MSN Messenger over the recent years. You can now see/set status messages, send offline messages, and many strange issues caused by Microsoft breaking old-protocol compatibility should now be resolved. Twitter extended: IRC-style replies ("BitlBee:") now get converted to proper Twitter replies ("@BitlBee") and get a reference to the original message (see help set auto_reply_timeout). Retweets and some other stuff is also supported now (see help set commands). % ?news1.3 Most of the core of BitlBee was rewritten since the last release. This entry should sum up the majority of the changes. First of all, you can now have as many control channels as you want. Or you can have none, it's finally possible to leave &bitlbee and still talk to all your contacts. Or you can have a &work with all your work-related contacts, or a &msn with all your MSN Messenger contacts. See help channels for more information about this. Also, you can change how nicknames are generated for your contacts. Like automatically adding a [fb] tag to the nicks of all your Facebook contacts. See help nick_format. When you're already connected to a BitlBee server and you connect from elsewhere, you can take over the old session. Instead of account numbers, accounts now also get tags. These are automatically generated but can be changed (help set tag). You can now use them instead of accounts numbers. (Example: acc gtalk on) Last of all: You can finally change your nickname and shorten root commands (try acc li instead of account list). % ?whatsnew030005 OAuth2 support in Jabber module (see help set oauth). For better password security when using Google Talk, Facebook XMPP, or for using MSN Messenger via XMPP. Especially recommended on public servers. Starting quick groupchats on Jabber is easier now (using the chat with command, or /join + /invite). SSL certificate verification. Works only with GnuTLS, and needs to be enabled by updating your bitlbee.conf. % ?whatsnew030200 Upgradeed to Twitter API version 1.1. This is necessary because previous versions will stop working from March 2013. At the same time, BitlBee now supports the streaming API and incoming direct messages. % bitlbee-3.2.1/doc/user-guide/Usage.xml0000644000175000017500000000341612245474076017126 0ustar wilmerwilmer Usage Connecting to the server Since BitlBee acts just like any other irc daemon, you can connect to it with your favorite irc client. Launch it and connect to localhost port 6667 (or whatever host/port you are running bitlbee on). The &bitlbee control channel Once you are connected to the BitlBee server, you are automatically joined to &bitlbee on that server. This channel acts like the 'buddy list' you have on the various other chat networks. The user 'root' always hangs around in &bitlbee and acts as your interface to bitlbee. All commands you give on &bitlbee are 'answered' by root. You might be slightly confused by the & in the channel name. This is, however, completely allowed by the IRC standards. Just try it on a regular IRC server, it should work. The difference between the standard #channels and &channels is that the #channels are distributed over all the servers on the IRC network, while &channels are local to one server. Because the BitlBee control channel is local to one server (and in fact, to one person), this name seems more suitable. Also, with this name, it's harder to confuse the control channel with the #bitlbee channel on OFTC. Talking to people You can talk to by starting a query with them. In most irc clients, this can be done with either /msg <nick> <text> or /query <nick>. To keep the number of open query windows limited, you can also talk to people in the control channel, like <nick>: <text>. bitlbee-3.2.1/doc/user-guide/misc.xml0000644000175000017500000004067512245474076017025 0ustar wilmerwilmer Misc Stuff Smileys All MSN smileys (except one) are case insensitive and work without the nose too. (Y)Thumbs up (N)Thumbs down (B)Beer mug (D)Martini glass (X)Girl (Z)Boy (6)Devil smiley :-[Vampire bat (})Right hug ({)Left hug (M)MSN Messenger or Windows Messenger icon (think a BitlBee logo here ;) :-SCrooked smiley (Confused smiley) :-$Embarrassed smiley (H)Smiley with sunglasses :-@Angry smiley (A)Angel smiley (L)Red heart (Love) (U)Broken heart (K)Red lips (Kiss) (G)Gift with bow (F)Red rose (W)Wilted rose (P)Camera (~)Film strip (T)Telephone receiver (@)Cat face (&)Dog's head (C)Coffee cup (I)Light bulb (S)Half-moon (Case sensitive!) (*)Star (8)Musical eighth note (E)Envelope (^)Birthday cake (O)Clock Groupchats BitlBee now supports groupchats on all IM networks. This text will try to explain you how they work. As soon as someone invites you into a groupchat, you will be force-joined or invited (depending on the protocol) into a new virtual channel with all the people in there. You can leave the channel at any time, just like you would close the window in regular IM clients. Please note that root-commands don't work in groupchat channels, they only work in control channels (or to root directly). Of course you can also create your own groupchats. Type help groupchats2 to see how. Creating groupchats To open a groupchat, use the chat with command. For example, to start a groupchat with the person lisa_msn in it, just type chat with lisa_msn. BitlBee will create a new virtual channel with root, you and lisa_msn in it. Then, just use the ordinary IRC /invite command to invite more people. Please do keep in mind that all the people have to be on the same network and contact list! You can't invite Yahoo! buddies into an MSN groupchat. Some protocols (like Jabber) also support named groupchats. BitlBee now supports these too. You can use the chat add command to join them. See help chat add for more information. Away states To mark yourself as away, you can just use the /away command in your IRC client. BitlBee supports most away-states supported by the protocols. Away states have different names accross different protocols. BitlBee will try to pick the best available option for every connection: Away NA Busy, DND BRB Phone Lunch, Food Invisible, Hidden So /away Food will set your state to "Out to lunch" on your MSN connection, and for most other connections the default, "Away" will be chosen. You can also add more information to your away message. Setting it to "Busy - Fixing BitlBee bugs" will set your IM-away-states to Busy, but your away message will be more descriptive for people on IRC. Most IM-protocols can also show this additional information to your buddies. If you want to set an away state for only one of your connections, you can use the per-account away setting. See help set away. Changing your nickname BitlBee now allows you to change your nickname. So far this was not possible because it made managing saved accounts more complicated. The restriction no longer exists now though. When you change your nick (just using the /nick command), your logged-in status will be reset, which means any changes made to your settings/accounts will not be saved. To restore your logged-in status, you need to either use the register command to create an account under the new nickname, or use identify -noload to re-identify yourself under the new nickname. The -noload flag tells the command to verify your password and log you in, but not load any new settings. See help identify for more information. Dealing with channels You can have as many channels in BitlBee as you want. You maintain your channel list using the channel command. You can create new channels by just joining them, like on regular IRC networks. You can create two kinds of channels. Control channels, and groupchat channels. By default, BitlBee will set up new channels as control channels if their name starts with an &, and as chat channels if it starts with a #. Control channels are where you see your contacts. By default, you will have one control channel called &bitlbee, containing all your contacts. But you can create more, if you want, and divide your contact list accross several channels. For example, you can have one channel with all contacts from your MSN Messenger account in it. Or all contacts from the group called "Work". Type help channels2 to read more. Creating a channel When you create a new channel, BitlBee will try to guess from its name which contacts to fill it with. For example, if the channel name (excluding the &) matches the name of a group in which you have one or more contacts, the channel will contain all those contacts. Any valid account ID (so a number, protocol name or part of screenname, as long as it's unique) can also be used as a channel name. So if you just join &msn, it will contain all your MSN contacts. And if you have a Facebook account set up, you can see its contacts by just joining &facebook. To start a simple group chat, you simply join a channel which a name starting with #, and invite people into it. All people you invite have to be on the same IM network and contact list. If you want to configure your own channels, you can use the channel set command. See help channels3 for more information. Configuring a control channel The most important setting for a control channel is fill_by. It tells BitlBee what information should be used to decide if someone should be shown in the channel or not. After setting this setting to, for example, account, you also have to set the account setting. Example: chan &wlm set fill_by account fill_by = `account' chan &wlm set account msn account = `msn' Also, each channel has a show_users setting which lets you choose, for example, if you want to see only online contacts in a channel, or also/just offline contacts. Example: chan &offline set show_users offline show_users = `offline' See the help information for all these settings for more information. Nickname formatting The nick_format setting can be set globally using the set command, or per account using account set (so that you can set a per-account suffix/prefix or have nicknames generated from full names for certain accounts). The setting is basically some kind of format string. It can contain normal text that will be copied to the nick, combined with several variables: %nickNickname suggested for this contact by the IM protocol, or just the handle if no nickname was suggested. %handleThe handle/screenname of the contact. %full_nameThe full name of the contact. %first_nameThe first name of the contact (the full name up to the first space). %groupThe name of the group this contact is a member of %accountAccount tag of the contact Invalid characters (like spaces) will always be stripped. Depending on your locale settings, characters with accents will be converted to ASCII. See help nick_format2 for some more information. Nickname formatting - modifiers Two modifiers are currently available: You can include only the first few characters of a variable by putting a number right after the %. For example, [%3group]%-@nick will include only the first three characters of the group name in the nick. Also, you can truncate variables from a certain character using the - modifier. For example, you may want to leave out everything after the @. %-@handle will expand to everything in the handle up to the first @. New stuff in BitlBee 1.2.6 Twitter support. See help account add twitter. New stuff in BitlBee 1.3dev Support for multiple configurable control channels, each with a subset of your contact list. See help channels for more information. File transfer support for some protocols (more if you use libpurple). Just /DCC SEND stuff. Incoming files also become DCC transfers. Only if you run your own BitlBee instance: You can build a BitlBee that uses libpurple for connecting to IM networks instead of its own code, adding support for some of the more obscure IM protocols and features. Many more things, briefly described in help news1.3. New stuff in BitlBee 3.0 BitlBee can be compiled with support for OTR message encryption (not available on public servers since encryption should be end-to-end). The MSN module was heavily updated to support features added to MSN Messenger over the recent years. You can now see/set status messages, send offline messages, and many strange issues caused by Microsoft breaking old-protocol compatibility should now be resolved. Twitter extended: IRC-style replies ("BitlBee:") now get converted to proper Twitter replies ("@BitlBee") and get a reference to the original message (see help set auto_reply_timeout). Retweets and some other stuff is also supported now (see help set commands). New stuff in BitlBee 1.3dev (details) Most of the core of BitlBee was rewritten since the last release. This entry should sum up the majority of the changes. First of all, you can now have as many control channels as you want. Or you can have none, it's finally possible to leave &bitlbee and still talk to all your contacts. Or you can have a &work with all your work-related contacts, or a &msn with all your MSN Messenger contacts. See help channels for more information about this. Also, you can change how nicknames are generated for your contacts. Like automatically adding a [fb] tag to the nicks of all your Facebook contacts. See help nick_format. When you're already connected to a BitlBee server and you connect from elsewhere, you can take over the old session. Instead of account numbers, accounts now also get tags. These are automatically generated but can be changed (help set tag). You can now use them instead of accounts numbers. (Example: acc gtalk on) Last of all: You can finally change your nickname and shorten root commands (try acc li instead of account list). New stuff in BitlBee 3.0.5 OAuth2 support in Jabber module (see help set oauth). For better password security when using Google Talk, Facebook XMPP, or for using MSN Messenger via XMPP. Especially recommended on public servers. Starting quick groupchats on Jabber is easier now (using the chat with command, or /join + /invite). SSL certificate verification. Works only with GnuTLS, and needs to be enabled by updating your bitlbee.conf. New stuff in BitlBee 3.2 Upgradeed to Twitter API version 1.1. This is necessary because previous versions will stop working from March 2013. At the same time, BitlBee now supports the streaming API and incoming direct messages. bitlbee-3.2.1/doc/user-guide/user-guide.xml0000644000175000017500000000231512245474076020130 0ustar wilmerwilmer BitlBee User Guide JelmerVernooij
jelmer@samba.org
Wilmervan der Gaast
wilmer@gaast.net
SjoerdHemminga
sjoerd@huiswerkservice.nl
This is the BitlBee User Guide. For now, the on-line help is the most up-to-date documentation. Although this document shares some parts with the on-line help system, other parts might be very outdated.
bitlbee-3.2.1/doc/user-guide/help.xsl0000644000175000017500000001337612245474076017026 0ustar wilmerwilmer Processing: ? % Processing setting '' ?set _b_Type:_b_ _b_Scope:_b_ _b_Default:_b_ _b_Possible Values:_b_ % These are all root commands. See _b_help <command name>_b_ for more details on each command. * _b__b_ - Most commands can be shortened. For example instead of _b_account list_b_, try _b_ac l_b_. _b_ _b_ _b__b_ - - _b_<>_b_ _b_* _b_ Processing command '' ? _b_Syntax:_b_ _b_Example:_b_ % bitlbee-3.2.1/doc/user-guide/help.xml0000644000175000017500000000573712245474076017022 0ustar wilmerwilmer BitlBee help system These are the available help subjects: quickstartA short introduction into BitlBee commandsAll available commands and settings channelsAbout creating and customizing channels awayAbout setting away states groupchatsHow to work with groupchats on BitlBee nick_changesChanging your nickname without losing any settings smileysA summary of some non-standard smileys you might find and fail to understand You can read more about them with help <subject> Some more help can be found on http://wiki.bitlbee.org/. Bugs can be reported at http://bugs.bitlbee.org/. For other things than bug reports, you can join #BitlBee on OFTC (irc.oftc.net) (OFTC, *not* FreeNode!) and flame us right in the face. :-) Index These are the available help subjects: quickstartA short introduction into BitlBee commandsAll available commands and settings channelsAbout creating and customizing channels awayAbout setting away states groupchatsHow to work with groupchats on BitlBee nick_changesChanging your nickname without losing any settings smileysA summary of some non-standard smileys you might find and fail to understand You can read more about them with help <subject> bitlbee-3.2.1/doc/user-guide/Installation.xml0000644000175000017500000001107312245474076020521 0ustar wilmerwilmer Installation Downloading the package The latest BitlBee release is always available from http://www.bitlbee.org/. Download the package with your favorite program and unpack it: tar xvfz bitlbee-<version>.tar.gz where <version> is to be replaced by the version number of the BitlBee you downloaded (e.g. 0.91). Compiling BitlBee's build system has to be configured before compiling. The configure script will do this for you. Just run it, it'll set up with nice and hopefully well-working defaults. If you want to change some settings, just try ./configure --help and see what you can do. Some variables that might be of interest to the normal user: prefix, bindir, etcdir, mandir, datadir - The place where all the BitlBee program files will be put. There's usually no reason to specify them all separately, just specifying prefix (or keeping the default /usr/local/) should be okay. config - The place where BitlBee will save all the per-user settings and buddy information. /var/lib/bitlbee/ is the default value. msn, jabber, oscar, yahoo - By default, support for all these IM-protocols (OSCAR is the protocol used by both ICQ and AIM) will be compiled in. To make the binary a bit smaller, you can use these options to leave out support for protocols you're not planning to use. debug - Generate an unoptimized binary with debugging symbols, mainly useful if you want to do some debugging or help us to track down a problem. strip - By default, unnecessary parts of the generated binary will be stripped out to make it as small as possible. If you don't want this (because it might cause problems on some platforms), set this to 0. flood - To secure your BitlBee server against flooding attacks, you can use this option. It's not compiled in by default because it needs more testing first. ssl - The MSN and Jabber modules require an SSL library for some of their tasks. BitlBee can use three different SSL libraries: GnuTLS, mozilla-nss and OpenSSL. (OpenSSL is, however, a bit troublesome because of licensing issues, so don't forget to read the information configure will give you when you try to use OpenSSL!) By default, configure will try to detect GnuTLS or mozilla-nss. If none of them can be found, it'll give up. If you want BitlBee to use OpenSSL, you have to explicitly specify that. After running configure, you should run make. After that, run make install as root. Configuration By default, BitlBee runs as the user nobody. You might want to run it as a seperate user (some computers run named or apache as nobody). Since BitlBee uses inetd, you should add the following line to /etc/inetd.conf: 6667 stream tcp nowait nobody /usr/local/sbin/bitlbee bitlbee Inetd has to be restarted after changing the configuration. Either killall -HUP inetd or /etc/init.d/inetd restart should do the job on most systems. You might be one of the.. ehr, lucky people running an xinetd-powered distro. xinetd is quite different and they seem to be proud of that.. ;-) Anyway, if you want BitlBee to work with xinetd, just copy the bitlbee.xinetd file to your /etc/xinetd.d/ directory (and probably edit it to suit your needs). You should create a directory where BitlBee can store it's data files. This should be the directory named after the value 'CONFIG' in Makefile.settings. The default is /var/lib/bitlbee, which can be created with the command mkdir -p /var/lib/bitlbee. This directory has to be owned by the user that runs bitlbee. To make 'nobody' owner of this directory, run chown nobody /var/lib/bitlbee. Because things like passwords are saved in this directory, it's probably a good idea to make this directory owner-read-/writable only. bitlbee-3.2.1/doc/user-guide/user-guide.txt0000644000175000017500000022166412245477431020157 0ustar wilmerwilmerBitlBee User Guide Jelmer Vernooij Wilmer van der Gaast Sjoerd Hemminga This is the BitlBee User Guide. For now, the on-line help is the most up-to-date documentation. Although this document shares some parts with the on-line help system, other parts might be very outdated. â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â” Table of Contents 1. Installation Downloading the package Compiling Configuration 2. Usage Connecting to the server The &bitlbee control channel Talking to people 3. Support Disclaimer Support channels The World Wide Web IRC Mailinglists 4. Quickstart Add and Connect To your IM Account(s) Step Four: Managing Contact Lists: Add, Remove and Rename Chatting Further Resources 5. Bitlbee commands account - IM-account list maintenance account add account del account on account off account list account set channel - Channel list maintenance channel del channel list channel set chat - Chatroom list maintenance chat add chat with add - Add a buddy to your contact list info - Request user information remove - Remove a buddy from your contact list block - Block someone allow - Unblock someone otr - Off-the-Record encryption control otr connect otr disconnect otr reconnect otr smp otr smpq otr trust otr info otr keygen otr forget set - Miscellaneous settings help - BitlBee help system save - Save your account data account allow_takeover auto_connect auto_join auto_reconnect auto_reconnect_delay auto_reply_timeout away away_devoice away_reply_timeout base_url charset color_encrypted chat_type commands debug default_target display_name display_namechanges display_timestamps fill_by group handle_unknown ignore_auth_requests lcnicks local_display_name mail_notifications message_length stream target_url_length mode mobile_is_away nick nick_format nick_source oauth ops otr_policy password paste_buffer paste_buffer_delay port priority private protocol query_order resource resource_select root_nick save_on_quit server show_ids show_offline show_users simulate_netsplit ssl status strip_html strip_newlines show_old_mentions switchboard_keepalives tag timezone tls tls_verify to_char translate_to_nicks type typing_notice user_agent utf8_nicks web_aware xmlconsole rename - Rename (renick) a buddy yes - Accept a request no - Deny a request qlist - List all the unanswered questions root asked register - Register yourself identify - Identify yourself with your password drop - Drop your account blist - List all the buddies in the current channel group - Contact group management transfer - Monitor, cancel, or reject file transfers transfer cancel - Cancels the file transfer with the given id transfer reject - Rejects all incoming transfers 6. Misc Stuff Smileys Groupchats Creating groupchats Away states Changing your nickname Dealing with channels Creating a channel Configuring a control channel Nickname formatting Nickname formatting - modifiers New stuff in BitlBee 1.2.6 New stuff in BitlBee 1.3dev New stuff in BitlBee 3.0 New stuff in BitlBee 1.3dev (details) New stuff in BitlBee 3.0.5 New stuff in BitlBee 3.2 Chapter 1. Installation Table of Contents Downloading the package Compiling Configuration Downloading the package The latest BitlBee release is always available from http://www.bitlbee.org/. Download the package with your favorite program and unpack it: tar xvfz bitlbee-.tar.gz where is to be replaced by the version number of the BitlBee you downloaded (e.g. 0.91). Compiling BitlBee's build system has to be configured before compiling. The configure script will do this for you. Just run it, it'll set up with nice and hopefully well-working defaults. If you want to change some settings, just try ./ configure --help and see what you can do. Some variables that might be of interest to the normal user: • prefix, bindir, etcdir, mandir, datadir - The place where all the BitlBee program files will be put. There's usually no reason to specify them all separately, just specifying prefix (or keeping the default /usr/local/) should be okay. • config - The place where BitlBee will save all the per-user settings and buddy information. /var/lib/bitlbee/ is the default value. • msn, jabber, oscar, yahoo - By default, support for all these IM-protocols (OSCAR is the protocol used by both ICQ and AIM) will be compiled in. To make the binary a bit smaller, you can use these options to leave out support for protocols you're not planning to use. • debug - Generate an unoptimized binary with debugging symbols, mainly useful if you want to do some debugging or help us to track down a problem. • strip - By default, unnecessary parts of the generated binary will be stripped out to make it as small as possible. If you don't want this (because it might cause problems on some platforms), set this to 0. • flood - To secure your BitlBee server against flooding attacks, you can use this option. It's not compiled in by default because it needs more testing first. • ssl - The MSN and Jabber modules require an SSL library for some of their tasks. BitlBee can use three different SSL libraries: GnuTLS, mozilla-nss and OpenSSL. (OpenSSL is, however, a bit troublesome because of licensing issues, so don't forget to read the information configure will give you when you try to use OpenSSL!) By default, configure will try to detect GnuTLS or mozilla-nss. If none of them can be found, it'll give up. If you want BitlBee to use OpenSSL, you have to explicitly specify that. After running configure, you should run make. After that, run make install as root. Configuration By default, BitlBee runs as the user nobody. You might want to run it as a seperate user (some computers run named or apache as nobody). Since BitlBee uses inetd, you should add the following line to /etc/inetd.conf: 6667 stream tcp nowait nobody /usr/local/sbin/bitlbee bitlbee Inetd has to be restarted after changing the configuration. Either killall -HUP inetd or /etc/init.d/inetd restart should do the job on most systems. You might be one of the.. ehr, lucky people running an xinetd-powered distro. xinetd is quite different and they seem to be proud of that.. ;-) Anyway, if you want BitlBee to work with xinetd, just copy the bitlbee.xinetd file to your /etc/xinetd.d/ directory (and probably edit it to suit your needs). You should create a directory where BitlBee can store it's data files. This should be the directory named after the value 'CONFIG' in Makefile.settings. The default is /var/lib/bitlbee, which can be created with the command mkdir -p /var/lib/bitlbee. This directory has to be owned by the user that runs bitlbee. To make 'nobody' owner of this directory, run chown nobody /var/lib/bitlbee. Because things like passwords are saved in this directory, it's probably a good idea to make this directory owner-read-/writable only. Chapter 2. Usage Table of Contents Connecting to the server The &bitlbee control channel Talking to people Connecting to the server Since BitlBee acts just like any other irc daemon, you can connect to it with your favorite irc client. Launch it and connect to localhost port 6667 (or whatever host/port you are running bitlbee on). The &bitlbee control channel Once you are connected to the BitlBee server, you are automatically joined to & bitlbee on that server. This channel acts like the 'buddy list' you have on the various other chat networks. The user 'root' always hangs around in &bitlbee and acts as your interface to bitlbee. All commands you give on &bitlbee are 'answered' by root. You might be slightly confused by the & in the channel name. This is, however, completely allowed by the IRC standards. Just try it on a regular IRC server, it should work. The difference between the standard #channels and &channels is that the #channels are distributed over all the servers on the IRC network, while &channels are local to one server. Because the BitlBee control channel is local to one server (and in fact, to one person), this name seems more suitable. Also, with this name, it's harder to confuse the control channel with the #bitlbee channel on OFTC. Talking to people You can talk to by starting a query with them. In most irc clients, this can be done with either /msg or /query . To keep the number of open query windows limited, you can also talk to people in the control channel, like : . Chapter 3. Support Table of Contents Disclaimer Support channels The World Wide Web IRC Mailinglists Disclaimer BitlBee doesn't come with a warranty and is still (and will probably always be) under development. That means it can crash at any time, corrupt your data or whatever. Don't use it in any production environment and don't rely on it, or at least don't blame us if things blow up. :-) Support channels The World Wide Web http://www.bitlbee.org/ is the homepage of bitlbee and contains the most recent news on bitlbee and the latest releases. IRC BitlBee is discussed on #bitlbee on the OFTC IRC network (server: irc.oftc.net). Mailinglists BitlBee doesn't have any mailinglists. Chapter 4. Quickstart Table of Contents Add and Connect To your IM Account(s) Step Four: Managing Contact Lists: Add, Remove and Rename Chatting Further Resources Welcome to BitlBee, your IRC gateway to ICQ, MSN, AOL, Jabber, Yahoo! and Twitter. The center of BitlBee is the control channel, &bitlbee. Two users will always be there, you (where "you" is the nickname you are using) and the system user, root. You need register so that all your IM settings (passwords, contacts, etc) can be saved on the BitlBee server. It's important that you pick a good password so no one else can access your account. Register with this password using the register command: register (without the brackets!). Be sure to remember your password. The next time you connect to the BitlBee server you will need to identify so that you will be recognised and logged in to all the IM services automatically. When finished, type help quickstart2 to continue. Add and Connect To your IM Account(s) Step Two: Add and Connect To your IM Account(s). To add an account to the account list you will need to use the account add command: account add []. For instance, suppose you have a Jabber account at jabber.org with handle bitlbee@jabber.org with password QuickStart, you would: < you> account add jabber bitlbee@jabber.org QuickStart < root> Account successfully added Other available IM protocols are msn, oscar, yahoo and twitter. OSCAR is the protocol used by ICQ and AOL. For more information about the account add command, see help account add. When you are finished adding your account(s) use the account on command to enable all your accounts, type help quickstart3 to continue. Step Four: Managing Contact Lists: Add, Remove and Rename Now you might want to add some contacts, to do this we will use the add command. It needs two arguments: a connection ID (which can be a number (try account list), protocol name or (part of) the screenname) and the user's handle. It is used in the following way: add < you> add 0 r2d2@example.com * r2d2 has joined &bitlbee In this case r2d2 is online, since he/she joins the channel immediately. If the user is not online you will not see them join until they log on. Lets say you accidentally added r2d3@example.com rather than r2d2@example.com, or maybe you just want to remove a user from your list because you never talk to them. To remove a name you will want to use the remove command: remove r2d3 Finally, if you have multiple users with similar names you may use the rename command to make it easier to remember: rename r2d2_ r2d2_aim When finished, type help quickstart4 to continue. Chatting Step Five: Chatting. First of all, a person must be on your contact list for you to chat with them (unless it's a group chat, help groupchats for more). If someone not on your contact list sends you a message, simply add them to the proper account with the add command. Once they are on your list and online, you can chat with them in &bitlbee: < you> tux: hey, how's the weather down there? < tux> you: a bit chilly! Note that, although all contacts are in the &bitlbee channel, only tux will actually receive this message. The &bitlbee channel shouldn't be confused with a real IRC channel. If you prefer chatting in a separate window, use the /msg or /query command, just like on real IRC. BitlBee will remember how you talk to someone and show his/her responses the same way. If you want to change the default behaviour (for people you haven't talked to yet), see help set private. You know the basics. If you want to know about some of the neat features BitlBee offers, please type help quickstart5. Further Resources So you want more than just chatting? Or maybe you're just looking for more features? With multiple channel support you can have contacts for specific protocols in their own channels, for instance, if you /join &msn you will join a channel that only contains your MSN contacts. Account tagging allows you to use the given account name rather than a number when referencing your account. If you wish to turn off your gtalk account, you may account gtalk off rather than account 3 off where "3" is the account number. You can type help set to learn more about the possible BitlBee user settings. Among these user settings you will find options for common issues, such as changing the charset, HTML stripping and automatic connecting (simply type set to see current user settings). For more subjects (like groupchats and away states), please type help index. If you're still looking for something, please visit us in #bitlbee on the OFTC network (you can connect via irc.bitlbee.org), or mail us your problem/ suggestion. Good luck and enjoy the Bee! Chapter 5. Bitlbee commands Table of Contents account - IM-account list maintenance account add account del account on account off account list account set channel - Channel list maintenance channel del channel list channel set chat - Chatroom list maintenance chat add chat with add - Add a buddy to your contact list info - Request user information remove - Remove a buddy from your contact list block - Block someone allow - Unblock someone otr - Off-the-Record encryption control otr connect otr disconnect otr reconnect otr smp otr smpq otr trust otr info otr keygen otr forget set - Miscellaneous settings help - BitlBee help system save - Save your account data account allow_takeover auto_connect auto_join auto_reconnect auto_reconnect_delay auto_reply_timeout away away_devoice away_reply_timeout base_url charset color_encrypted chat_type commands debug default_target display_name display_namechanges display_timestamps fill_by group handle_unknown ignore_auth_requests lcnicks local_display_name mail_notifications message_length stream target_url_length mode mobile_is_away nick nick_format nick_source oauth ops otr_policy password paste_buffer paste_buffer_delay port priority private protocol query_order resource resource_select root_nick save_on_quit server show_ids show_offline show_users simulate_netsplit ssl status strip_html strip_newlines show_old_mentions switchboard_keepalives tag timezone tls tls_verify to_char translate_to_nicks type typing_notice user_agent utf8_nicks web_aware xmlconsole rename - Rename (renick) a buddy yes - Accept a request no - Deny a request qlist - List all the unanswered questions root asked register - Register yourself identify - Identify yourself with your password drop - Drop your account blist - List all the buddies in the current channel group - Contact group management transfer - Monitor, cancel, or reject file transfers transfer cancel - Cancels the file transfer with the given id transfer reject - Rejects all incoming transfers account - IM-account list maintenance Syntax:  account [] [] Available actions: add, del, list, on, off and set. See help account for more information. account add Syntax:  account add [] Adds an account on the given server with the specified protocol, username and password to the account list. Supported protocols right now are: Jabber, MSN, OSCAR (AIM/ICQ), Yahoo and Twitter. For more information about adding an account, see help account add . You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs. account add jabber Syntax:  account add jabber [] The handle should be a full handle, including the domain name. You can specify a servername if necessary. Normally BitlBee doesn't need this though, since it's able to find out the server by doing DNS SRV lookups. In previous versions it was also possible to specify port numbers and/or SSL in the server tag. This is deprecated and should now be done using the account set command. This also applies to specifying a resource in the handle (like wilmer@bitlbee.org/work). account add msn Syntax:  account add msn [] For MSN connections there are no special arguments. account add oscar Syntax:  account add oscar [] OSCAR is the protocol used to connect to AIM and/or ICQ. The servers will automatically detect if you're using a numeric or non-numeric username so there's no need to tell which network you want to connect to. < wilmer> account add oscar 72696705 hobbelmeeuw < root> Account successfully added account add twitter Syntax:  account add twitter This module gives you simple access to Twitter and Twitter API compatible services. By default all your Twitter contacts will appear in a new channel called # twitter_yourusername. You can change this behaviour using the mode setting (see help set mode). To send tweets yourself, send them to the twitter_(yourusername) contact, or just write in the groupchat channel if you enabled that option. Since Twitter now requires OAuth authentication, you should not enter your Twitter password into BitlBee. Just type a bogus password. The first time you log in, BitlBee will start OAuth authentication. (See help set oauth.) To use a non-Twitter service, change the base_url setting. For identi.ca, you can simply use account add identica. account add identica Syntax:  account add identica Same protocol as twitter, but defaults to a base_url pointing at identi.ca. It also works with OAuth (so don't specify your password). account add yahoo Syntax:  account add yahoo [] For Yahoo! connections there are no special arguments. account del Syntax:  account del This commands deletes an account from your account list. You should signoff the account before deleting it. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. account on Syntax:  account [] on This command will try to log into the specified account. If no account is specified, BitlBee will log into all the accounts that have the auto_connect flag set. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. account off Syntax:  account [] off This command disconnects the connection for the specified account. If no account is specified, BitlBee will deactivate all active accounts and cancel all pending reconnects. The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. account list Syntax:  account list This command gives you a list of all the accounts known by BitlBee. account set Syntax:  account set account set account set account set -del This command can be used to change various settings for IM accounts. For all protocols, this command can be used to change the handle or the password BitlBee uses to log in and if it should be logged in automatically. Some protocols have additional settings. You can see the settings available for a connection by typing account set. For more infomation about a setting, see help set . The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. channel - Channel list maintenance Syntax:  channel [] [] Available actions: del, list, set. See help channel for more information. There is no channel add command. To create a new channel, just use the IRC / join command. See also help channels and help groupchats. channel del Syntax:  channel del Remove a channel and forget all its settings. You can only remove channels you're not currently in, and can't remove the main control channel. (You can, however, leave it.) channel list Syntax:  channel list This command gives you a list of all the channels you configured. channel set Syntax:  channel [] set channel [] set channel [] set channel [] set -del This command can be used to change various settings for channels. Different channel types support different settings. You can see the settings available for a channel by typing channel set. For more infomation about a setting, see help set . The channel ID can be a number (see channel list), or (part of) its name, as long as it matches only one channel. If you want to change settings of the current channel, you can omit the channel ID. chat - Chatroom list maintenance Syntax:  chat [] Available actions: add, with. See help chat for more information. chat add Syntax:  chat add [] Add a chatroom to the list of chatrooms you're interested in. BitlBee needs this list to map room names to a proper IRC channel name. After adding a room to your list, you can simply use the IRC /join command to enter the room. Also, you can tell BitlBee to automatically join the room when you log in. (See chat set) Password-protected rooms work exactly like on IRC, by passing the password as an extra argument to /join. chat with Syntax:  chat with While most chat subcommands are about named chatrooms, this command can be used to open an unnamed groupchat with one or more persons. This command is what / join #nickname used to do in older BitlBee versions. add - Add a buddy to your contact list Syntax:  add [] add -tmp [] Adds the given buddy at the specified connection to your buddy list. The account ID can be a number (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection. If you want, you can also tell BitlBee what nick to give the new contact. The -tmp option adds the buddy to the internal BitlBee structures only, not to the real contact list (like done by set handle_unknown add). This allows you to talk to people who are not in your contact list. This normally won't show you any presence notifications. If you use this command in a control channel containing people from only one group, the new contact will be added to that group automatically. < ctrlsoft> add 3 gryp@jabber.org grijp * grijp has joined &bitlbee info - Request user information Syntax:  info info Requests IM-network-specific information about the specified user. The amount of information you'll get differs per protocol. For some protocols (ATM Yahoo! and MSN) it'll give you an URL which you can visit with a normal web browser to get the information. < ctrlsoft> info 0 72696705 < root> User info - UIN: 72696705 Nick: Lintux First/Last name: Wilmer van der Gaast E-mail: lintux@lintux.cx remove - Remove a buddy from your contact list Syntax:  remove Removes the specified nick from your buddy list. < ctrlsoft> remove gryp * gryp has quit [Leaving...] block - Block someone Syntax:  block block block Puts the specified user on your ignore list. Either specify the user's nick when you have him/her in your contact list or a connection number and a user handle. When called with only a connection specification as an argument, the command displays the current block list for that connection. allow - Unblock someone Syntax:  allow allow Reverse of block. Unignores the specified user or user handle on specified connection. When called with only a connection specification as an argument, the command displays the current allow list for that connection. otr - Off-the-Record encryption control Syntax:  otr [] Available subcommands: connect, disconnect, reconnect, smp, smpq, trust, info, keygen, and forget. See help otr for more information. otr connect Syntax:  otr connect Attempts to establish an encrypted connection with the specified user by sending a magic string. otr disconnect Syntax:  otr disconnect Resets the connection with the specified user to cleartext. otr reconnect Syntax:  otr reconnect Breaks and re-establishes the encrypted connection with the specified user. Useful if something got desynced. Equivalent to otr disconnect followed by otr connect. otr smp Syntax:  otr smp Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol. If an SMP challenge has been received from the given user, responds with the specified secret/answer. Otherwise, sends a challenge for the given secret. Note that there are two flavors of SMP challenges: "shared-secret" and "question & answer". This command is used to respond to both of them, or to initiate a shared-secret style exchange. Use the otr smpq command to initiate a "Q&A" session. When responding to a "Q&A" challenge, the local trust value is not altered. Only the asking party sets trust in the case of success. Use otr smpq to pose your challenge. In a shared-secret exchange, both parties set their trust according to the outcome. otr smpq Syntax:  otr smpq Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol, Q&A style. Initiates an SMP session in "question & answer" style. The question is transmitted with the initial SMP packet and used to prompt the other party. You must be confident that only they know the answer. If the protocol succeeds (i.e. they answer correctly), the fingerprint will be trusted. Note that the answer must be entered exactly, case and punctuation count! Note that this style of SMP only affects the trust setting on your side. Expect your opponent to send you their own challenge. Alternatively, if you and the other party have a shared secret, use the otr smp command. otr trust Syntax:  otr trust Manually affirms trust in the specified fingerprint, given as five blocks of precisely eight (hexadecimal) digits each. otr info Syntax:  otr info otr info Shows information about the OTR state. The first form lists our private keys and current OTR contexts. The second form displays information about the connection with a given user, including the list of their known fingerprints. otr keygen Syntax:  otr keygen Generates a new OTR private key for the given account. otr forget Syntax:  otr forget Forgets some part of our OTR userstate. Available things: fingerprint, context, and key. See help otr forget for more information. otr forget fingerprint Syntax:  otr forget fingerprint Drops the specified fingerprint from the given user's OTR connection context. It is allowed to specify only a (unique) prefix of the desired fingerprint. otr forget context Syntax:  otr forget context Forgets the entire OTR context associated with the given user. This includes current message and protocol states, as well as any fingerprints for that user. otr forget key Syntax:  otr forget key Forgets an OTR private key matching the specified fingerprint. It is allowed to specify only a (unique) prefix of the fingerprint. set - Miscellaneous settings Syntax:  set set set set -del Without any arguments, this command lists all the set variables. You can also specify a single argument, a variable name, to get that variable's value. To change this value, specify the new value as the second argument. With -del you can reset a setting to its default value. To get more help information about a setting, try: < ctrlsoft> help set private help - BitlBee help system Syntax:  help [subject] This command gives you the help information you're reading right now. If you don't give any arguments, it'll give a short help index. save - Save your account data Syntax:  save This command saves all your nicks and accounts immediately. Handy if you have the autosave functionality disabled, or if you don't trust the program's stability... ;-) account Type: string For control channels with fill_by set to account: Set this setting to the account id (numeric, or part of the username) of the account containing the contacts you want to see in this channel. allow_takeover Type: boolean When you're already connected to a BitlBee server and you connect (and identify) again, BitlBee will offer to migrate your existing session to the new connection. If for whatever reason you don't want this, you can disable this setting. auto_connect Type: boolean With this option enabled, when you identify BitlBee will automatically connect to your accounts, with this disabled it will not do this. This setting can also be changed for specific accounts using the account set command. (However, these values will be ignored if the global auto_connect setting is disabled!) auto_join Type: boolean With this option enabled, BitlBee will automatically join this channel when you log in. auto_reconnect Type: boolean If an IM-connections breaks, you're supposed to bring it back up yourself. Having BitlBee do this automatically might not always be a good idea, for several reasons. If you want the connections to be restored automatically, you can enable this setting. See also the auto_reconnect_delay setting. This setting can also be changed for specific accounts using the account set command. (However, these values will be ignored if the global auto_reconnect setting is disabled!) auto_reconnect_delay Type: string Tell BitlBee after how many seconds it should attempt to bring a broken IM-connection back up. This can be one integer, for a constant delay. One can also set it to something like "10*10", which means wait for ten seconds on the first reconnect, multiply it by ten on every failure. Once successfully connected, this delay is re-set to the initial value. With < you can give a maximum delay. See also the auto_reconnect setting. auto_reply_timeout Type: integer For Twitter accounts: If you respond to Tweets IRC-style (like "nickname: reply"), this will automatically be converted to the usual Twitter format ("@screenname reply"). By default, BitlBee will then also add a reference to that person's most recent Tweet, unless that message is older than the value of this setting in seconds. If you want to disable this feature, just set this to 0. Alternatively, if you want to write a message once that is not a reply, use the Twitter reply syntax (@screenname). away Type: string To mark yourself as away, it is recommended to just use /away, like on normal IRC networks. If you want to mark yourself as away on only one IM network, you can use this per-account setting. You can set it to any value and BitlBee will try to map it to the most appropriate away state for every open IM connection, or set it as a free-form away message where possible. Any per-account away setting will override globally set away states. To un-set the setting, use set -del away. away_devoice Type: boolean With this option enabled, the root user devoices people when they go away (just away, not offline) and gives the voice back when they come back. You might dislike the voice-floods you'll get if your contact list is huge, so this option can be disabled. Replaced with the show_users setting. See help show_users. away_reply_timeout Type: integer Most IRC servers send a user's away message every time s/he gets a private message, to inform the sender that they may not get a response immediately. With this setting set to 0, BitlBee will also behave like this. Since not all IRC clients do an excellent job at suppressing these messages, this setting lets BitlBee do it instead. BitlBee will wait this many seconds (or until the away state/message changes) before re-informing you that the person's away. base_url Type: string There are more services that understand the Twitter API than just Twitter.com. BitlBee can connect to all Twitter API implementations. For example, set this setting to http://identi.ca/api to use Identi.ca. Keep two things in mind: When not using Twitter, you must also disable the oauth setting as it currently only works with Twitter. If you're still having issues, make sure there is no slash at the end of the URL you enter here. charset Type: string This setting tells BitlBee what your IRC client sends and expects. It should be equal to the charset setting of your IRC client if you want to be able to send and receive non-ASCII text properly. Most systems use UTF-8 these days. On older systems, an iso8859 charset may work better. For example, iso8859-1 is the best choice for most Western countries. You can try to find what works best for you on http:// www.unicodecharacter.com/charsets/iso8859.html color_encrypted Type: boolean If set to true, BitlBee will color incoming encrypted messages according to their fingerprint trust level: untrusted=red, trusted=green. chat_type Type: string There are two kinds of chat channels: simple groupchats (basically normal IM chats with more than two participants) and names chatrooms, more similar to IRC channels. BitlBee supports both types. With this setting set to groupchat (the default), you can just invite people into the room and start talking. For setting up named chatrooms, it's currently easier to just use the chat add command. commands Type: boolean With this setting enabled, you can use some commands in your Twitter channel/ query. The commands are simple and not documented in too much detail: Anything that doesn't look like a command will be treated as a tweet. Watch out for typos, or to avoid this behaviour, you can set this setting to strict, which causes the post command to become mandatory for posting a tweet. debug Type: boolean Some debugging messages can be logged if you wish. They're probably not really useful for you, unless you're doing some development on BitlBee. This feature is not currently used for anything so don't expect this to generate any output. default_target Type: string With this value set to root, lines written in a control channel without any nickname in front of them will be interpreted as commands. If you want BitlBee to send those lines to the last person you addressed in that control channel, set this to last. display_name Type: string Currently only available for MSN connections. This setting allows you to read and change your "friendly name" for this connection. Since this is a server-side setting, it can't be changed when the account is off-line. display_namechanges Type: boolean With this option enabled, root will inform you when someone in your buddy list changes his/her "friendly name". display_timestamps Type: boolean When incoming messages are old (i.e. offline messages and channel backlogs), BitlBee will prepend them with a timestamp. If you find them ugly or useless, you can use this setting to hide them. fill_by Type: string For control channels only: This setting determines which contacts the channel gets populated with. By default, control channels will contain all your contacts. You instead select contacts by buddy group, IM account or IM protocol. Change this setting and the corresponding account/group/protocol setting to set up this selection. With a ! prefix an inverted channel can be created, for example with this setting set to !group you can create a channel with all users not in that group. Note that, when creating a new channel, BitlBee will try to preconfigure the channel for you, based on the channel name. See help channels. group Type: string For control channels with fill_by set to group: Set this setting to the name of the group containing the contacts you want to see in this channel. handle_unknown Type: string By default, messages from people who aren't in your contact list are shown in a control channel instead of as a private message. If you prefer to ignore messages from people you don't know, you can set this one to "ignore". "add_private" and "add_channel" are like add, but you can use them to make messages from unknown buddies appear in the channel instead of a query window. ignore_auth_requests Type: boolean Only supported by OSCAR so far, you can use this setting to ignore ICQ authorization requests, which are hardly used for legitimate (i.e. non-spam) reasons anymore. lcnicks Type: boolean Hereby you can change whether you want all lower case nick names or leave the case as it intended by your peer. local_display_name Type: boolean Mostly meant to work around a bug in MSN servers (forgetting the display name set by the user), this setting tells BitlBee to store your display name locally and set this name on the MSN servers when connecting. mail_notifications Type: boolean Some protocols (MSN, Yahoo!) can notify via IM about new e-mail. Since most people use their Hotmail/Yahoo! addresses as a spam-box, this is disabled default. If you want these notifications, you can enable this setting. message_length Type: integer Since Twitter rejects messages longer than 140 characters, BitlBee can count message length and emit a warning instead of waiting for Twitter to reject it. You can change this limit here but this won't disable length checks on Twitter's side. You can also set it to 0 to disable the check in case you believe BitlBee doesn't count the characters correctly. stream Type: boolean For Twitter accounts, this setting enables use of the Streaming API. This automatically gives you incoming DMs as well. For other Twitter-like services, this setting is not supported. target_url_length Type: integer Twitter replaces every URL with fixed-length t.co URLs. BitlBee is able to take t.co urls into account when calculating message_length replacing the actual URL length with target_url_length. Setting target_url_length to 0 disables this feature. This setting is disabled for identica accounts by default and will not affect anything other than message safety checks (i.e. Twitter will still replace your URLs with t.co links, even if that makes them longer). mode Type: string By default, BitlBee will create a separate channel (called # twitter_yourusername) for all your Twitter contacts/messages. If you don't want an extra channel, you can set this setting to "one" (everything will come from one nick, twitter_yourusername), or to "many" (individual nicks for everyone). With modes "chat" and "many", you can send direct messages by /msg'ing your contacts directly. Note, however, that incoming DMs are not fetched yet. With modes "many" and "one", you can post tweets by /msg'ing the twitter_yourusername contact. In mode "chat", messages posted in the Twitter channel will also be posted as tweets. mobile_is_away Type: boolean Most IM networks have a mobile version of their client. People who use these may not be paying that much attention to messages coming in. By enabling this setting, people using mobile clients will always be shown as away. nick Type: string You can use this option to set your nickname in a chatroom. You won't see this nickname yourself, but other people in the room will. By default, BitlBee will use your username as the chatroom nickname. nick_format Type: string By default, BitlBee tries to derive sensible nicknames for all your contacts from their IM handles. In some cases, IM modules (ICQ for example) will provide a nickname suggestion, which will then be used instead. This setting lets you change this behaviour. Whenever this setting is set for an account, it will be used for all its contacts. If it's not set, the global value will be used. It's easier to describe this setting using a few examples: FB-%full_name will make all nicknames start with "FB-", followed by the person's full name. For example you can set this format for your Facebook account so all Facebook contacts are clearly marked. [%group]%-@nick will make all nicknames start with the group the contact is in between square brackets, followed by the nickname suggestions from the IM module if available, or otherwise the handle. Because of the "-@" part, everything from the first @ will be stripped. See help nick_format for more information. nick_source Type: string By default, BitlBee generates a nickname for every contact by taking its handle and chopping off everything after the @. In some cases, this gives very inconvenient nicknames. The Facebook XMPP server is a good example, as all Facebook XMPP handles are numeric. With this setting set to full_name, the person's full name is used to generate a nickname. Or if you don't like long nicknames, set this setting to first_name instead and only the first word will be used. Note that the full name can be full of non-ASCII characters which will be stripped off. oauth Type: boolean This enables OAuth authentication for an IM account; right now the Twitter (working for Twitter only) and Jabber (for Google Talk, Facebook and MSN Messenger) module support it. With OAuth enabled, you shouldn't tell BitlBee your account password. Just add your account with a bogus password and type account on. BitlBee will then give you a URL to authenticate with the service. If this succeeds, you will get a PIN code which you can give back to BitlBee to finish the process. The resulting access token will be saved permanently, so you have to do this only once. If for any reason you want to/have to reauthenticate, you can use account set to reset the account password to something random. ops Type: string Some people prefer themself and root to have operator status in &bitlbee, other people don't. You can change these states using this setting. The value "both" means both user and root get ops. "root" means, well, just root. "user" means just the user. "none" means nobody will get operator status. otr_policy Type: string This setting controls the policy for establishing Off-the-Record connections. A value of "never" effectively disables the OTR subsystem. In "opportunistic" mode, a magic whitespace pattern will be appended to the first message sent to any user. If the peer is also running opportunistic OTR, an encrypted connection will be set up automatically. On "manual", on the other hand, OTR connections must be established explicitly using otr connect. Finally, the setting "always" enforces encrypted communication by causing BitlBee to refuse to send any cleartext messages at all. password Type: string Use this global setting to change your "NickServ" password. This setting is also available for all IM accounts to change the password BitlBee uses to connect to the service. Note that BitlBee will always say this setting is empty. This doesn't mean there is no password, it just means that, for security reasons, BitlBee stores passwords somewhere else so they can't just be retrieved in plain text. paste_buffer Type: boolean By default, when you send a message to someone, BitlBee forwards this message to the user immediately. When you paste a large number of lines, the lines will be sent in separate messages, which might not be very nice to read. If you enable this setting, BitlBee will buffer your messages and wait for more data. Using the paste_buffer_delay setting you can specify the number of seconds BitlBee should wait for more data before the complete message is sent. Please note that if you remove a buddy from your list (or if the connection to that user drops) and there's still data in the buffer, this data will be lost. BitlBee will not try to send the message to the user in those cases. paste_buffer_delay Type: integer Tell BitlBee after how many (mili)seconds a buffered message should be sent. Values greater than 5 will be interpreted as miliseconds, 5 and lower as seconds. See also the paste_buffer setting. port Type: integer Currently only available for Jabber connections. Specifies the port number to connect to. Usually this should be set to 5222, or 5223 for SSL-connections. priority Type: integer Can be set for Jabber connections. When connecting to one account from multiple places, this priority value will help the server to determine where to deliver incoming messages (that aren't addressed to a specific resource already). According to RFC 3921 servers will always deliver messages to the server with the highest priority value. Mmessages will not be delivered to resources with a negative priority setting (and should be saved as an off-line message if all available resources have a negative priority value). private Type: boolean If value is true, messages from users will appear in separate query windows. If false, messages from users will appear in a control channel. This setting is remembered (during one session) per-user, this setting only changes the default state. This option takes effect as soon as you reconnect. protocol Type: string For control channels with fill_by set to protocol: Set this setting to the name of the IM protocol of all contacts you want to see in this channel. query_order Type: string This changes the order in which the questions from root (usually authorization requests from buddies) should be answered. When set to lifo, BitlBee immediately displays all new questions and they should be answered in reverse order. When this is set to fifo, BitlBee displays the first question which comes in and caches all the others until you answer the first one. Although the fifo setting might sound more logical (and used to be the default behaviour in older BitlBee versions), it turned out not to be very convenient for many users when they missed the first question (and never received the next ones). resource Type: string Can be set for Jabber connections. You can use this to connect to your Jabber account from multiple clients at once, with every client using a different resource string. resource_select Type: string Because the IRC interface makes it pretty hard to specify the resource to talk to (when a buddy is online through different resources), this setting was added. Normally it's set to priority which means messages will always be delivered to the buddy's resource with the highest priority. If the setting is set to activity, messages will be delivered to the resource that was last used to send you a message (or the resource that most recently connected). root_nick Type: string Normally the "bot" that takes all your BitlBee commands is called "root". If you don't like this name, you can rename it to anything else using the rename command, or by changing this setting. save_on_quit Type: boolean If enabled causes BitlBee to save all current settings and account details when user disconnects. This is enabled by default, and these days there's not really a reason to have it disabled anymore. server Type: string Can be set for Jabber- and OSCAR-connections. For Jabber, you might have to set this if the servername isn't equal to the part after the @ in the Jabber handle. For OSCAR this shouldn't be necessary anymore in recent BitlBee versions. show_ids Type: boolean Enable this setting on a Twitter account to have BitlBee include a two-digit "id" in front of every message. This id can then be used for replies and retweets. show_offline Type: boolean If enabled causes BitlBee to also show offline users in Channel. Online-users will get op, away-users voice and offline users none of both. This option takes effect as soon as you reconnect. Replaced with the show_users setting. See help show_users. show_users Type: string Comma-separated list of statuses of users you want in the channel, and any modes they should have. The following statuses are currently recognised: online (i.e. available, not away), away, and offline. If a status is followed by a valid channel mode character (@, % or +), it will be given to users with that status. For example, online@,away+,offline will show all users in the channel. Online people will have +o, people who are online but away will have +v, and others will have no special modes. simulate_netsplit Type: boolean Some IRC clients parse quit messages sent by the IRC server to see if someone really left or just disappeared because of a netsplit. By default, BitlBee tries to simulate netsplit-like quit messages to keep the control channels window clean. If you don't like this (or if your IRC client doesn't support this) you can disable this setting. ssl Type: boolean Currently only available for Jabber connections. Set this to true if you want to connect to the server on an SSL-enabled port (usually 5223). Please note that this method of establishing a secure connection to the server has long been deprecated. You are encouraged to look at the tls setting instead. status Type: string Most IM protocols support status messages, similar to away messages. They can be used to indicate things like your location or activity, without showing up as away/busy. This setting can be used to set such a message. It will be available as a per-account setting for protocols that support it, and also as a global setting (which will then automatically be used for all protocols that support it). Away states set using /away or the away setting will override this setting. To clear the setting, use set -del status. strip_html Type: boolean Determines what BitlBee should do with HTML in messages. Normally this is turned on and HTML will be stripped from messages, if BitlBee thinks there is HTML. If BitlBee fails to detect this sometimes (most likely in AIM messages over an ICQ connection), you can set this setting to always, but this might sometimes accidentally strip non-HTML things too. strip_newlines Type: boolean Turn on this flag to prevent tweets from spanning over multiple lines. show_old_mentions Type: integer This setting specifies the number of old mentions to fetch on connection. Must be less or equal to 200. Setting it to 0 disables this feature. switchboard_keepalives Type: boolean Turn on this flag if you have difficulties talking to offline/invisible contacts. With this setting enabled, BitlBee will send keepalives to MSN switchboards with offline/invisible contacts every twenty seconds. This should keep the server and client on the other side from shutting it down. This is useful because BitlBee doesn't support MSN offline messages yet and the MSN servers won't let the user reopen switchboards to offline users. Once offline messaging is supported, this flag might be removed. tag Type: string For every account you have, you can set a tag you can use to uniquely identify that account. This tag can be used instead of the account number (or protocol name, or part of the screenname) when using commands like account, add, etc. You can't have two accounts with one and the same account tag. By default, it will be set to the name of the IM protocol. Once you add a second account on an IM network, a numeric suffix will be added, starting with 2. timezone Type: string If message timestamps are available for offline messages or chatroom backlogs, BitlBee will display them as part of the message. By default it will use the local timezone. If you're not in the same timezone as the BitlBee server, you can adjust the timestamps using this setting. Values local/utc/gmt should be self-explanatory. timezone-spec is a time offset in hours:minutes, for example: -8 for Pacific Standard Time, +2 for Central European Summer Time, +5:30 for Indian Standard Time. tls Type: boolean By default (with this setting enabled), BitlBee will require Jabber servers to offer encryption via StartTLS and refuse to connect if they don't. If you set this to "try", BitlBee will use StartTLS only if it's offered. With the setting disabled, StartTLS support will be ignored and avoided entirely. tls_verify Type: boolean Currently only available for Jabber connections in combination with the tls setting. Set this to true if you want BitlBee to strictly verify the server's certificate against a list of trusted certificate authorities. The hostname used in the certificate verification is the value of the server setting if the latter is nonempty and the domain of the username else. If you get a hostname related error when connecting to Google Talk with a username from the gmail.com or googlemail.com domain, please try to empty the server setting. Please note that no certificate verification is performed when the ssl setting is used, or when the CAfile setting in bitlbee.conf is not set. to_char Type: string It's customary that messages meant for one specific person on an IRC channel are prepended by his/her alias followed by a colon ':'. BitlBee does this by default. If you prefer a different character, you can set it using set to_char. Please note that this setting is only used for incoming messages. For outgoing messages you can use ':' (colon) or ',' to separate the destination nick from the message, and this is not configurable. translate_to_nicks Type: boolean IRC's nickname namespace is quite limited compared to most IM protocols. Not any non-ASCII characters are allowed, in fact nicknames have to be mostly alpha-numeric. Also, BitlBee has to add underscores sometimes to avoid nickname collisions. While normally the BitlBee user is the only one seeing these names, they may be exposed to other chatroom participants for example when addressing someone in the channel (with or without tab completion). By default BitlBee will translate these stripped nicknames back to the original nick. If you don't want this, disable this setting. type Type: string BitlBee supports two kinds of channels: control channels (usually with a name starting with a &) and chatroom channels (name usually starts with a #). See help channels for a full description of channel types in BitlBee. typing_notice Type: boolean Sends you a /notice when a user starts typing a message (if supported by the IM protocol and the user's client). To use this, you most likely want to use a script in your IRC client to show this information in a more sensible way. user_agent Type: string Some Jabber servers are configured to only allow a few (or even just one) kinds of XMPP clients to connect to them. You can change this setting to make BitlBee present itself as a different client, so that you can still connect to these servers. utf8_nicks Type: boolean Officially, IRC nicknames are restricted to ASCII. Recently some clients and servers started supporting Unicode nicknames though. To enable UTF-8 nickname support (contacts only) in BitlBee, enable this setting. To avoid confusing old clients, this setting is disabled by default. Be careful when you try it, and be prepared to be locked out of your BitlBee in case your client interacts poorly with UTF-8 nicknames. web_aware Type: string ICQ allows people to see if you're on-line via a CGI-script. (http:// status.icq.com/online.gif?icq=UIN) This can be nice to put on your website, but it seems that spammers also use it to see if you're online without having to add you to their contact list. So to prevent ICQ spamming, recent versions of BitlBee disable this feature by default. Unless you really intend to use this feature somewhere (on forums or maybe a website), it's probably better to keep this setting disabled. xmlconsole Type: boolean The Jabber module allows you to add a buddy xmlconsole to your contact list, which will then show you the raw XMPP stream between you and the server. You can also send XMPP packets to this buddy, which will then be sent to the server. If you want to enable this XML console permanently (and at login time already), you can set this setting. rename - Rename (renick) a buddy Syntax:  rename rename -del Renick a user in your buddy list. Very useful, in fact just very important, if you got a lot of people with stupid account names (or hard ICQ numbers). rename -del can be used to erase your manually set nickname for a contact and reset it to what was automatically generated. < itsme> rename itsme_ you * itsme_ is now known as you yes - Accept a request Syntax:  yes [] Sometimes an IM-module might want to ask you a question. (Accept this user as your buddy or not?) To accept a question, use the yes command. By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the qlist command for a list of questions. no - Deny a request Syntax:  no [] Sometimes an IM-module might want to ask you a question. (Accept this user as your buddy or not?) To reject a question, use the no command. By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the qlist command for a list of questions. qlist - List all the unanswered questions root asked Syntax:  qlist This gives you a list of all the unanswered questions from root. register - Register yourself Syntax:  register [] BitlBee can save your settings so you won't have to enter all your IM passwords every time you log in. If you want the Bee to save your settings, use the register command. Please do pick a secure password, don't just use your nick as your password. Please note that IRC is not an encrypted protocol, so the passwords still go over the network in plaintext. Evil people with evil sniffers will read it all. (So don't use your root password.. ;-) To identify yourself in later sessions, you can use the identify command. To change your password later, you can use the set password command. You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs. identify - Identify yourself with your password Syntax:  identify [-noload|-force] [] BitlBee saves all your settings (contacts, accounts, passwords) on-server. To prevent other users from just logging in as you and getting this information, you'll have to identify yourself with your password. You can register this password using the register command. Once you're registered, you can change your password using set password . The -noload and -force flags can be used to identify when you're logged into some IM accounts already. -force will let you identify yourself and load all saved accounts (and keep the accounts you're logged into already). -noload will log you in but not load any accounts and settings saved under your current nickname. These will be overwritten once you save your settings (i.e. when you disconnect). You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs. drop - Drop your account Syntax:  drop Drop your BitlBee registration. Your account files will be removed and your password will be forgotten. For obvious security reasons, you have to specify your NickServ password to make this command work. blist - List all the buddies in the current channel Syntax:  blist [all|online|offline|away] You can get a more readable buddy list using the blist command. If you want a complete list (including the offline users) you can use the all argument. group - Contact group management Syntax:  group [ list | info ] The group list command shows a list of all groups defined so far. The group info command shows a list of all members of a the group . If you want to move contacts between groups, you can use the IRC /invite command. Also, if you use the add command in a control channel configured to show just one group, the new contact will automatically be added to that group. transfer - Monitor, cancel, or reject file transfers Syntax:  transfer [ id | ] Without parameters the currently pending file transfers and their status will be listed. Available actions are cancel and reject. See help transfer for more information. transfer cancel - Cancels the file transfer with the given id Syntax:  transfer id Cancels the file transfer with the given id < ulim> transfer cancel 1 < root> Canceling file transfer for test transfer reject - Rejects all incoming transfers Syntax:  transfer Rejects all incoming (not already transferring) file transfers. Since you probably have only one incoming transfer at a time, no id is neccessary. Or is it? < ulim> transfer reject Chapter 6. Misc Stuff Table of Contents Smileys Groupchats Creating groupchats Away states Changing your nickname Dealing with channels Creating a channel Configuring a control channel Nickname formatting Nickname formatting - modifiers New stuff in BitlBee 1.2.6 New stuff in BitlBee 1.3dev New stuff in BitlBee 3.0 New stuff in BitlBee 1.3dev (details) New stuff in BitlBee 3.0.5 New stuff in BitlBee 3.2 Smileys All MSN smileys (except one) are case insensitive and work without the nose too. (Y) Thumbs up (N) Thumbs down (B) Beer mug (D) Martini glass (X) Girl (Z) Boy (6) Devil smiley :-[ Vampire bat (}) Right hug ({) Left hug (M) MSN Messenger or Windows Messenger icon (think a BitlBee logo here ;) :-S Crooked smiley (Confused smiley) :-$ Embarrassed smiley (H) Smiley with sunglasses :-@ Angry smiley (A) Angel smiley (L) Red heart (Love) (U) Broken heart (K) Red lips (Kiss) (G) Gift with bow (F) Red rose (W) Wilted rose (P) Camera (~) Film strip (T) Telephone receiver (@) Cat face (&) Dog's head (C) Coffee cup (I) Light bulb (S) Half-moon (Case sensitive!) (*) Star (8) Musical eighth note (E) Envelope (^) Birthday cake (O) Clock Groupchats BitlBee now supports groupchats on all IM networks. This text will try to explain you how they work. As soon as someone invites you into a groupchat, you will be force-joined or invited (depending on the protocol) into a new virtual channel with all the people in there. You can leave the channel at any time, just like you would close the window in regular IM clients. Please note that root-commands don't work in groupchat channels, they only work in control channels (or to root directly). Of course you can also create your own groupchats. Type help groupchats2 to see how. Creating groupchats To open a groupchat, use the chat with command. For example, to start a groupchat with the person lisa_msn in it, just type chat with lisa_msn. BitlBee will create a new virtual channel with root, you and lisa_msn in it. Then, just use the ordinary IRC /invite command to invite more people. Please do keep in mind that all the people have to be on the same network and contact list! You can't invite Yahoo! buddies into an MSN groupchat. Some protocols (like Jabber) also support named groupchats. BitlBee now supports these too. You can use the chat add command to join them. See help chat add for more information. Away states To mark yourself as away, you can just use the /away command in your IRC client. BitlBee supports most away-states supported by the protocols. Away states have different names accross different protocols. BitlBee will try to pick the best available option for every connection: Away NA Busy, DND BRB Phone Lunch, Food Invisible, Hidden So /away Food will set your state to "Out to lunch" on your MSN connection, and for most other connections the default, "Away" will be chosen. You can also add more information to your away message. Setting it to "Busy - Fixing BitlBee bugs" will set your IM-away-states to Busy, but your away message will be more descriptive for people on IRC. Most IM-protocols can also show this additional information to your buddies. If you want to set an away state for only one of your connections, you can use the per-account away setting. See help set away. Changing your nickname BitlBee now allows you to change your nickname. So far this was not possible because it made managing saved accounts more complicated. The restriction no longer exists now though. When you change your nick (just using the /nick command), your logged-in status will be reset, which means any changes made to your settings/accounts will not be saved. To restore your logged-in status, you need to either use the register command to create an account under the new nickname, or use identify -noload to re-identify yourself under the new nickname. The -noload flag tells the command to verify your password and log you in, but not load any new settings. See help identify for more information. Dealing with channels You can have as many channels in BitlBee as you want. You maintain your channel list using the channel command. You can create new channels by just joining them, like on regular IRC networks. You can create two kinds of channels. Control channels, and groupchat channels. By default, BitlBee will set up new channels as control channels if their name starts with an &, and as chat channels if it starts with a #. Control channels are where you see your contacts. By default, you will have one control channel called &bitlbee, containing all your contacts. But you can create more, if you want, and divide your contact list accross several channels. For example, you can have one channel with all contacts from your MSN Messenger account in it. Or all contacts from the group called "Work". Type help channels2 to read more. Creating a channel When you create a new channel, BitlBee will try to guess from its name which contacts to fill it with. For example, if the channel name (excluding the &) matches the name of a group in which you have one or more contacts, the channel will contain all those contacts. Any valid account ID (so a number, protocol name or part of screenname, as long as it's unique) can also be used as a channel name. So if you just join &msn, it will contain all your MSN contacts. And if you have a Facebook account set up, you can see its contacts by just joining &facebook. To start a simple group chat, you simply join a channel which a name starting with #, and invite people into it. All people you invite have to be on the same IM network and contact list. If you want to configure your own channels, you can use the channel set command. See help channels3 for more information. Configuring a control channel The most important setting for a control channel is fill_by. It tells BitlBee what information should be used to decide if someone should be shown in the channel or not. After setting this setting to, for example, account, you also have to set the account setting. Example: < wilmer> chan &wlm set fill_by account < root> fill_by = `account' < wilmer> chan &wlm set account msn < root> account = `msn' Also, each channel has a show_users setting which lets you choose, for example, if you want to see only online contacts in a channel, or also/just offline contacts. Example: < wilmer> chan &offline set show_users offline < root> show_users = `offline' See the help information for all these settings for more information. Nickname formatting The nick_format setting can be set globally using the set command, or per account using account set (so that you can set a per-account suffix/prefix or have nicknames generated from full names for certain accounts). The setting is basically some kind of format string. It can contain normal text that will be copied to the nick, combined with several variables: %nick Nickname suggested for this contact by the IM protocol, or just the handle if no nickname was suggested. %handle The handle/screenname of the contact. %full_name The full name of the contact. %first_name The first name of the contact (the full name up to the first space). %group The name of the group this contact is a member of %account Account tag of the contact Invalid characters (like spaces) will always be stripped. Depending on your locale settings, characters with accents will be converted to ASCII. See help nick_format2 for some more information. Nickname formatting - modifiers Two modifiers are currently available: You can include only the first few characters of a variable by putting a number right after the %. For example, [%3group]%-@nick will include only the first three characters of the group name in the nick. Also, you can truncate variables from a certain character using the - modifier. For example, you may want to leave out everything after the @. %-@handle will expand to everything in the handle up to the first @. New stuff in BitlBee 1.2.6 Twitter support. See help account add twitter. New stuff in BitlBee 1.3dev Support for multiple configurable control channels, each with a subset of your contact list. See help channels for more information. File transfer support for some protocols (more if you use libpurple). Just /DCC SEND stuff. Incoming files also become DCC transfers. Only if you run your own BitlBee instance: You can build a BitlBee that uses libpurple for connecting to IM networks instead of its own code, adding support for some of the more obscure IM protocols and features. Many more things, briefly described in help news1.3. New stuff in BitlBee 3.0 BitlBee can be compiled with support for OTR message encryption (not available on public servers since encryption should be end-to-end). The MSN module was heavily updated to support features added to MSN Messenger over the recent years. You can now see/set status messages, send offline messages, and many strange issues caused by Microsoft breaking old-protocol compatibility should now be resolved. Twitter extended: IRC-style replies ("BitlBee:") now get converted to proper Twitter replies ("@BitlBee") and get a reference to the original message (see help set auto_reply_timeout). Retweets and some other stuff is also supported now (see help set commands). New stuff in BitlBee 1.3dev (details) Most of the core of BitlBee was rewritten since the last release. This entry should sum up the majority of the changes. First of all, you can now have as many control channels as you want. Or you can have none, it's finally possible to leave &bitlbee and still talk to all your contacts. Or you can have a &work with all your work-related contacts, or a & msn with all your MSN Messenger contacts. See help channels for more information about this. Also, you can change how nicknames are generated for your contacts. Like automatically adding a [fb] tag to the nicks of all your Facebook contacts. See help nick_format. When you're already connected to a BitlBee server and you connect from elsewhere, you can take over the old session. Instead of account numbers, accounts now also get tags. These are automatically generated but can be changed (help set tag). You can now use them instead of accounts numbers. (Example: acc gtalk on) Last of all: You can finally change your nickname and shorten root commands (try acc li instead of account list). New stuff in BitlBee 3.0.5 OAuth2 support in Jabber module (see help set oauth). For better password security when using Google Talk, Facebook XMPP, or for using MSN Messenger via XMPP. Especially recommended on public servers. Starting quick groupchats on Jabber is easier now (using the chat with command, or /join + /invite). SSL certificate verification. Works only with GnuTLS, and needs to be enabled by updating your bitlbee.conf. New stuff in BitlBee 3.2 Upgradeed to Twitter API version 1.1. This is necessary because previous versions will stop working from March 2013. At the same time, BitlBee now supports the streaming API and incoming direct messages. bitlbee-3.2.1/doc/user-guide/user-guide.html0000644000175000017500000043656112245477432020311 0ustar wilmerwilmer BitlBee User Guide

BitlBee User Guide

Jelmer Vernooij

Wilmer van der Gaast

Sjoerd Hemminga

This is the BitlBee User Guide. For now, the on-line help is the most up-to-date documentation. Although this document shares some parts with the on-line help system, other parts might be very outdated.


Table of Contents

1. Installation
Downloading the package
Compiling
Configuration
2. Usage
Connecting to the server
The &bitlbee control channel
Talking to people
3. Support
Disclaimer
Support channels
The World Wide Web
IRC
Mailinglists
4. Quickstart
Add and Connect To your IM Account(s)
Step Four: Managing Contact Lists: Add, Remove and Rename
Chatting
Further Resources
5. Bitlbee commands
account - IM-account list maintenance
account add
account del
account on
account off
account list
account set
channel - Channel list maintenance
channel del
channel list
channel set
chat - Chatroom list maintenance
chat add
chat with
add - Add a buddy to your contact list
info - Request user information
remove - Remove a buddy from your contact list
block - Block someone
allow - Unblock someone
otr - Off-the-Record encryption control
otr connect
otr disconnect
otr reconnect
otr smp
otr smpq
otr trust
otr info
otr keygen
otr forget
set - Miscellaneous settings
help - BitlBee help system
save - Save your account data
account
allow_takeover
auto_connect
auto_join
auto_reconnect
auto_reconnect_delay
auto_reply_timeout
away
away_devoice
away_reply_timeout
base_url
charset
color_encrypted
chat_type
commands
debug
default_target
display_name
display_namechanges
display_timestamps
fill_by
group
handle_unknown
ignore_auth_requests
lcnicks
local_display_name
mail_notifications
message_length
stream
target_url_length
mode
mobile_is_away
nick
nick_format
nick_source
oauth
ops
otr_policy
password
paste_buffer
paste_buffer_delay
port
priority
private
protocol
query_order
resource
resource_select
root_nick
save_on_quit
server
show_ids
show_offline
show_users
simulate_netsplit
ssl
status
strip_html
strip_newlines
show_old_mentions
switchboard_keepalives
tag
timezone
tls
tls_verify
to_char
translate_to_nicks
type
typing_notice
user_agent
utf8_nicks
web_aware
xmlconsole
rename - Rename (renick) a buddy
yes - Accept a request
no - Deny a request
qlist - List all the unanswered questions root asked
register - Register yourself
identify - Identify yourself with your password
drop - Drop your account
blist - List all the buddies in the current channel
group - Contact group management
transfer - Monitor, cancel, or reject file transfers
transfer cancel - Cancels the file transfer with the given id
transfer reject - Rejects all incoming transfers
6. Misc Stuff
Smileys
Groupchats
Creating groupchats
Away states
Changing your nickname
Dealing with channels
Creating a channel
Configuring a control channel
Nickname formatting
Nickname formatting - modifiers
New stuff in BitlBee 1.2.6
New stuff in BitlBee 1.3dev
New stuff in BitlBee 3.0
New stuff in BitlBee 1.3dev (details)
New stuff in BitlBee 3.0.5
New stuff in BitlBee 3.2

Chapter 1. Installation

Downloading the package

The latest BitlBee release is always available from http://www.bitlbee.org/. Download the package with your favorite program and unpack it: tar xvfz bitlbee-<version>.tar.gz where <version> is to be replaced by the version number of the BitlBee you downloaded (e.g. 0.91).

Compiling

BitlBee's build system has to be configured before compiling. The configure script will do this for you. Just run it, it'll set up with nice and hopefully well-working defaults. If you want to change some settings, just try ./configure --help and see what you can do.

Some variables that might be of interest to the normal user:

  • prefix, bindir, etcdir, mandir, datadir - The place where all the BitlBee program files will be put. There's usually no reason to specify them all separately, just specifying prefix (or keeping the default /usr/local/) should be okay.

  • config - The place where BitlBee will save all the per-user settings and buddy information. /var/lib/bitlbee/ is the default value.

  • msn, jabber, oscar, yahoo - By default, support for all these IM-protocols (OSCAR is the protocol used by both ICQ and AIM) will be compiled in. To make the binary a bit smaller, you can use these options to leave out support for protocols you're not planning to use.

  • debug - Generate an unoptimized binary with debugging symbols, mainly useful if you want to do some debugging or help us to track down a problem.

  • strip - By default, unnecessary parts of the generated binary will be stripped out to make it as small as possible. If you don't want this (because it might cause problems on some platforms), set this to 0.

  • flood - To secure your BitlBee server against flooding attacks, you can use this option. It's not compiled in by default because it needs more testing first.

  • ssl - The MSN and Jabber modules require an SSL library for some of their tasks. BitlBee can use three different SSL libraries: GnuTLS, mozilla-nss and OpenSSL. (OpenSSL is, however, a bit troublesome because of licensing issues, so don't forget to read the information configure will give you when you try to use OpenSSL!) By default, configure will try to detect GnuTLS or mozilla-nss. If none of them can be found, it'll give up. If you want BitlBee to use OpenSSL, you have to explicitly specify that.

After running configure, you should run make. After that, run make install as root.

Configuration

By default, BitlBee runs as the user nobody. You might want to run it as a seperate user (some computers run named or apache as nobody).

Since BitlBee uses inetd, you should add the following line to /etc/inetd.conf:

6667    stream  tcp     nowait nobody /usr/local/sbin/bitlbee bitlbee

Inetd has to be restarted after changing the configuration. Either killall -HUP inetd or /etc/init.d/inetd restart should do the job on most systems.

You might be one of the.. ehr, lucky people running an xinetd-powered distro. xinetd is quite different and they seem to be proud of that.. ;-) Anyway, if you want BitlBee to work with xinetd, just copy the bitlbee.xinetd file to your /etc/xinetd.d/ directory (and probably edit it to suit your needs).

You should create a directory where BitlBee can store it's data files. This should be the directory named after the value 'CONFIG' in Makefile.settings. The default is /var/lib/bitlbee, which can be created with the command mkdir -p /var/lib/bitlbee. This directory has to be owned by the user that runs bitlbee. To make 'nobody' owner of this directory, run chown nobody /var/lib/bitlbee. Because things like passwords are saved in this directory, it's probably a good idea to make this directory owner-read-/writable only.

Chapter 2. Usage

Connecting to the server

Since BitlBee acts just like any other irc daemon, you can connect to it with your favorite irc client. Launch it and connect to localhost port 6667 (or whatever host/port you are running bitlbee on).

The &bitlbee control channel

Once you are connected to the BitlBee server, you are automatically joined to &bitlbee on that server. This channel acts like the 'buddy list' you have on the various other chat networks.

The user 'root' always hangs around in &bitlbee and acts as your interface to bitlbee. All commands you give on &bitlbee are 'answered' by root.

You might be slightly confused by the & in the channel name. This is, however, completely allowed by the IRC standards. Just try it on a regular IRC server, it should work. The difference between the standard #channels and &channels is that the #channels are distributed over all the servers on the IRC network, while &channels are local to one server. Because the BitlBee control channel is local to one server (and in fact, to one person), this name seems more suitable. Also, with this name, it's harder to confuse the control channel with the #bitlbee channel on OFTC.

Talking to people

You can talk to by starting a query with them. In most irc clients, this can be done with either /msg <nick> <text> or /query <nick>.

To keep the number of open query windows limited, you can also talk to people in the control channel, like <nick>: <text>.

Chapter 3. Support

Disclaimer

BitlBee doesn't come with a warranty and is still (and will probably always be) under development. That means it can crash at any time, corrupt your data or whatever. Don't use it in any production environment and don't rely on it, or at least don't blame us if things blow up. :-)

Support channels

The World Wide Web

http://www.bitlbee.org/ is the homepage of bitlbee and contains the most recent news on bitlbee and the latest releases.

IRC

BitlBee is discussed on #bitlbee on the OFTC IRC network (server: irc.oftc.net).

Mailinglists

BitlBee doesn't have any mailinglists.

Chapter 4. Quickstart

Welcome to BitlBee, your IRC gateway to ICQ, MSN, AOL, Jabber, Yahoo! and Twitter.

The center of BitlBee is the control channel, &bitlbee. Two users will always be there, you (where "you" is the nickname you are using) and the system user, root.

You need register so that all your IM settings (passwords, contacts, etc) can be saved on the BitlBee server. It's important that you pick a good password so no one else can access your account. Register with this password using the register command: register <password> (without the brackets!).

Be sure to remember your password. The next time you connect to the BitlBee server you will need to identify <password> so that you will be recognised and logged in to all the IM services automatically.

When finished, type help quickstart2 to continue.

Add and Connect To your IM Account(s)

Step Two: Add and Connect To your IM Account(s).

To add an account to the account list you will need to use the account add command: account add <protocol> <username> <password> [<server>].

For instance, suppose you have a Jabber account at jabber.org with handle bitlbee@jabber.org with password QuickStart, you would:

< you> account add jabber bitlbee@jabber.org QuickStart
< root> Account successfully added

Other available IM protocols are msn, oscar, yahoo and twitter. OSCAR is the protocol used by ICQ and AOL. For more information about the account add command, see help account add.

When you are finished adding your account(s) use the account on command to enable all your accounts, type help quickstart3 to continue.

Step Four: Managing Contact Lists: Add, Remove and Rename

Now you might want to add some contacts, to do this we will use the add command. It needs two arguments: a connection ID (which can be a number (try account list), protocol name or (part of) the screenname) and the user's handle. It is used in the following way: add <connection> <handle>

< you> add 0 r2d2@example.com
 * r2d2 has joined &bitlbee

In this case r2d2 is online, since he/she joins the channel immediately. If the user is not online you will not see them join until they log on.

Lets say you accidentally added r2d3@example.com rather than r2d2@example.com, or maybe you just want to remove a user from your list because you never talk to them. To remove a name you will want to use the remove command: remove r2d3

Finally, if you have multiple users with similar names you may use the rename command to make it easier to remember: rename r2d2_ r2d2_aim

When finished, type help quickstart4 to continue.

Chatting

Step Five: Chatting.

First of all, a person must be on your contact list for you to chat with them (unless it's a group chat, help groupchats for more). If someone not on your contact list sends you a message, simply add them to the proper account with the add command. Once they are on your list and online, you can chat with them in &bitlbee:

< you> tux: hey, how's the weather down there?
< tux> you: a bit chilly!

Note that, although all contacts are in the &bitlbee channel, only tux will actually receive this message. The &bitlbee channel shouldn't be confused with a real IRC channel.

If you prefer chatting in a separate window, use the /msg or /query command, just like on real IRC. BitlBee will remember how you talk to someone and show his/her responses the same way. If you want to change the default behaviour (for people you haven't talked to yet), see help set private.

You know the basics. If you want to know about some of the neat features BitlBee offers, please type help quickstart5.

Further Resources

So you want more than just chatting? Or maybe you're just looking for more features?

With multiple channel support you can have contacts for specific protocols in their own channels, for instance, if you /join &msn you will join a channel that only contains your MSN contacts.

Account tagging allows you to use the given account name rather than a number when referencing your account. If you wish to turn off your gtalk account, you may account gtalk off rather than account 3 off where "3" is the account number.

You can type help set to learn more about the possible BitlBee user settings. Among these user settings you will find options for common issues, such as changing the charset, HTML stripping and automatic connecting (simply type set to see current user settings).

For more subjects (like groupchats and away states), please type help index.

If you're still looking for something, please visit us in #bitlbee on the OFTC network (you can connect via irc.bitlbee.org), or mail us your problem/suggestion. Good luck and enjoy the Bee!

Chapter 5. Bitlbee commands

Table of Contents

account - IM-account list maintenance
account add
account del
account on
account off
account list
account set
channel - Channel list maintenance
channel del
channel list
channel set
chat - Chatroom list maintenance
chat add
chat with
add - Add a buddy to your contact list
info - Request user information
remove - Remove a buddy from your contact list
block - Block someone
allow - Unblock someone
otr - Off-the-Record encryption control
otr connect
otr disconnect
otr reconnect
otr smp
otr smpq
otr trust
otr info
otr keygen
otr forget
set - Miscellaneous settings
help - BitlBee help system
save - Save your account data
account
allow_takeover
auto_connect
auto_join
auto_reconnect
auto_reconnect_delay
auto_reply_timeout
away
away_devoice
away_reply_timeout
base_url
charset
color_encrypted
chat_type
commands
debug
default_target
display_name
display_namechanges
display_timestamps
fill_by
group
handle_unknown
ignore_auth_requests
lcnicks
local_display_name
mail_notifications
message_length
stream
target_url_length
mode
mobile_is_away
nick
nick_format
nick_source
oauth
ops
otr_policy
password
paste_buffer
paste_buffer_delay
port
priority
private
protocol
query_order
resource
resource_select
root_nick
save_on_quit
server
show_ids
show_offline
show_users
simulate_netsplit
ssl
status
strip_html
strip_newlines
show_old_mentions
switchboard_keepalives
tag
timezone
tls
tls_verify
to_char
translate_to_nicks
type
typing_notice
user_agent
utf8_nicks
web_aware
xmlconsole
rename - Rename (renick) a buddy
yes - Accept a request
no - Deny a request
qlist - List all the unanswered questions root asked
register - Register yourself
identify - Identify yourself with your password
drop - Drop your account
blist - List all the buddies in the current channel
group - Contact group management
transfer - Monitor, cancel, or reject file transfers
transfer cancel - Cancels the file transfer with the given id
transfer reject - Rejects all incoming transfers

account - IM-account list maintenance

Syntax: 

account [<account id>] <action> [<arguments>]

Available actions: add, del, list, on, off and set. See help account <action> for more information.

account add

Syntax: 

account add <protocol> <username> [<password>]

Adds an account on the given server with the specified protocol, username and password to the account list. Supported protocols right now are: Jabber, MSN, OSCAR (AIM/ICQ), Yahoo and Twitter. For more information about adding an account, see help account add <protocol>.

You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs.

account add jabber

Syntax: 

account add jabber <handle@server.tld> [<password>]

The handle should be a full handle, including the domain name. You can specify a servername if necessary. Normally BitlBee doesn't need this though, since it's able to find out the server by doing DNS SRV lookups.

In previous versions it was also possible to specify port numbers and/or SSL in the server tag. This is deprecated and should now be done using the account set command. This also applies to specifying a resource in the handle (like wilmer@bitlbee.org/work).

account add msn

Syntax: 

account add msn <handle@server.tld> [<password>]

For MSN connections there are no special arguments.

account add oscar

Syntax: 

account add oscar <handle> [<password>]

OSCAR is the protocol used to connect to AIM and/or ICQ. The servers will automatically detect if you're using a numeric or non-numeric username so there's no need to tell which network you want to connect to.

< wilmer> account add oscar 72696705 hobbelmeeuw
< root> Account successfully added

account add twitter

Syntax: 

account add twitter <handle>

This module gives you simple access to Twitter and Twitter API compatible services.

By default all your Twitter contacts will appear in a new channel called #twitter_yourusername. You can change this behaviour using the mode setting (see help set mode).

To send tweets yourself, send them to the twitter_(yourusername) contact, or just write in the groupchat channel if you enabled that option.

Since Twitter now requires OAuth authentication, you should not enter your Twitter password into BitlBee. Just type a bogus password. The first time you log in, BitlBee will start OAuth authentication. (See help set oauth.)

To use a non-Twitter service, change the base_url setting. For identi.ca, you can simply use account add identica.

account add identica

Syntax: 

account add identica <handle>

Same protocol as twitter, but defaults to a base_url pointing at identi.ca. It also works with OAuth (so don't specify your password).

account add yahoo

Syntax: 

account add yahoo <handle> [<password>]

For Yahoo! connections there are no special arguments.

account del

Syntax: 

account <account id> del

This commands deletes an account from your account list. You should signoff the account before deleting it.

The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection.

account on

Syntax: 

account [<account id>] on

This command will try to log into the specified account. If no account is specified, BitlBee will log into all the accounts that have the auto_connect flag set.

The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection.

account off

Syntax: 

account [<account id>] off

This command disconnects the connection for the specified account. If no account is specified, BitlBee will deactivate all active accounts and cancel all pending reconnects.

The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection.

account list

Syntax: 

account list

This command gives you a list of all the accounts known by BitlBee.

account set

Syntax: 

account <account id> set
account <account id> set <setting>
account <account id> set <setting> <value>
account <account id> set -del <setting>

This command can be used to change various settings for IM accounts. For all protocols, this command can be used to change the handle or the password BitlBee uses to log in and if it should be logged in automatically. Some protocols have additional settings. You can see the settings available for a connection by typing account <account id> set.

For more infomation about a setting, see help set <setting>.

The account ID can be a number/tag (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection.

channel - Channel list maintenance

Syntax: 

channel [<account id>] <action> [<arguments>]

Available actions: del, list, set. See help channel <action> for more information.

There is no channel add command. To create a new channel, just use the IRC /join command. See also help channels and help groupchats.

channel del

Syntax: 

channel <channel id> del

Remove a channel and forget all its settings. You can only remove channels you're not currently in, and can't remove the main control channel. (You can, however, leave it.)

channel list

Syntax: 

channel list

This command gives you a list of all the channels you configured.

channel set

Syntax: 

channel [<channel id>] set
channel [<channel id>] set <setting>
channel [<channel id>] set <setting> <value>
channel [<channel id>] set -del <setting>

This command can be used to change various settings for channels. Different channel types support different settings. You can see the settings available for a channel by typing channel <channel id> set.

For more infomation about a setting, see help set <setting>.

The channel ID can be a number (see channel list), or (part of) its name, as long as it matches only one channel. If you want to change settings of the current channel, you can omit the channel ID.

chat - Chatroom list maintenance

Syntax: 

chat <action> [<arguments>]

Available actions: add, with. See help chat <action> for more information.

chat add

Syntax: 

chat add <account id> <room> [<channel>]

Add a chatroom to the list of chatrooms you're interested in. BitlBee needs this list to map room names to a proper IRC channel name.

After adding a room to your list, you can simply use the IRC /join command to enter the room. Also, you can tell BitlBee to automatically join the room when you log in. (See chat set)

Password-protected rooms work exactly like on IRC, by passing the password as an extra argument to /join.

chat with

Syntax: 

chat with <nickname>

While most chat subcommands are about named chatrooms, this command can be used to open an unnamed groupchat with one or more persons. This command is what /join #nickname used to do in older BitlBee versions.

add - Add a buddy to your contact list

Syntax: 

add <account id> <handle> [<nick>]
add -tmp <account id> <handle> [<nick>]

Adds the given buddy at the specified connection to your buddy list. The account ID can be a number (see account list), the protocol name or (part of) the screenname, as long as it matches only one connection.

If you want, you can also tell BitlBee what nick to give the new contact. The -tmp option adds the buddy to the internal BitlBee structures only, not to the real contact list (like done by set handle_unknown add). This allows you to talk to people who are not in your contact list. This normally won't show you any presence notifications.

If you use this command in a control channel containing people from only one group, the new contact will be added to that group automatically.

< ctrlsoft> add 3 gryp@jabber.org grijp
 * grijp has joined &bitlbee

info - Request user information

Syntax: 

info <connection> <handle>
info <nick>

Requests IM-network-specific information about the specified user. The amount of information you'll get differs per protocol. For some protocols (ATM Yahoo! and MSN) it'll give you an URL which you can visit with a normal web browser to get the information.

< ctrlsoft> info 0 72696705
< root> User info - UIN: 72696705 Nick: Lintux First/Last name: Wilmer van der Gaast E-mail: lintux@lintux.cx

remove - Remove a buddy from your contact list

Syntax: 

remove <nick>

Removes the specified nick from your buddy list.

< ctrlsoft> remove gryp
 * gryp has quit [Leaving...]

block - Block someone

Syntax: 

block <nick>
block <connection> <handle>
block <connection>

Puts the specified user on your ignore list. Either specify the user's nick when you have him/her in your contact list or a connection number and a user handle.

When called with only a connection specification as an argument, the command displays the current block list for that connection.

allow - Unblock someone

Syntax: 

allow <nick>
allow <connection> <handle>

Reverse of block. Unignores the specified user or user handle on specified connection.

When called with only a connection specification as an argument, the command displays the current allow list for that connection.

otr - Off-the-Record encryption control

Syntax: 

otr <subcommand> [<arguments>]

Available subcommands: connect, disconnect, reconnect, smp, smpq, trust, info, keygen, and forget. See help otr <subcommand> for more information.

otr connect

Syntax: 

otr connect <nick>

Attempts to establish an encrypted connection with the specified user by sending a magic string.

otr disconnect

Syntax: 

otr disconnect <nick>

Resets the connection with the specified user to cleartext.

otr reconnect

Syntax: 

otr reconnect <nick>

Breaks and re-establishes the encrypted connection with the specified user. Useful if something got desynced.

Equivalent to otr disconnect followed by otr connect.

otr smp

Syntax: 

otr smp <nick> <secret>

Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol.

If an SMP challenge has been received from the given user, responds with the specified secret/answer. Otherwise, sends a challenge for the given secret.

Note that there are two flavors of SMP challenges: "shared-secret" and "question & answer". This command is used to respond to both of them, or to initiate a shared-secret style exchange. Use the otr smpq command to initiate a "Q&A" session.

When responding to a "Q&A" challenge, the local trust value is not altered. Only the asking party sets trust in the case of success. Use otr smpq to pose your challenge. In a shared-secret exchange, both parties set their trust according to the outcome.

otr smpq

Syntax: 

otr smpq <nick> <question> <answer>

Attempts to authenticate the given user's active fingerprint via the Socialist Millionaires' Protocol, Q&A style.

Initiates an SMP session in "question & answer" style. The question is transmitted with the initial SMP packet and used to prompt the other party. You must be confident that only they know the answer. If the protocol succeeds (i.e. they answer correctly), the fingerprint will be trusted. Note that the answer must be entered exactly, case and punctuation count!

Note that this style of SMP only affects the trust setting on your side. Expect your opponent to send you their own challenge. Alternatively, if you and the other party have a shared secret, use the otr smp command.

otr trust

Syntax: 

otr trust <nick> <fp1> <fp2> <fp3> <fp4> <fp5>

Manually affirms trust in the specified fingerprint, given as five blocks of precisely eight (hexadecimal) digits each.

otr info

Syntax: 

otr info
otr info <nick>

Shows information about the OTR state. The first form lists our private keys and current OTR contexts. The second form displays information about the connection with a given user, including the list of their known fingerprints.

otr keygen

Syntax: 

otr keygen <account-no>

Generates a new OTR private key for the given account.

otr forget

Syntax: 

otr forget <thing> <arguments>

Forgets some part of our OTR userstate. Available things: fingerprint, context, and key. See help otr forget <thing> for more information.

otr forget fingerprint

Syntax: 

otr forget fingerprint <nick> <fingerprint>

Drops the specified fingerprint from the given user's OTR connection context. It is allowed to specify only a (unique) prefix of the desired fingerprint.

otr forget context

Syntax: 

otr forget context <nick>

Forgets the entire OTR context associated with the given user. This includes current message and protocol states, as well as any fingerprints for that user.

otr forget key

Syntax: 

otr forget key <fingerprint>

Forgets an OTR private key matching the specified fingerprint. It is allowed to specify only a (unique) prefix of the fingerprint.

set - Miscellaneous settings

Syntax: 

set
set <variable>
set <variable> <value>
set -del <variable>

Without any arguments, this command lists all the set variables. You can also specify a single argument, a variable name, to get that variable's value. To change this value, specify the new value as the second argument. With -del you can reset a setting to its default value.

To get more help information about a setting, try:

< ctrlsoft> help set private

help - BitlBee help system

Syntax: 

help [subject]

This command gives you the help information you're reading right now. If you don't give any arguments, it'll give a short help index.

save - Save your account data

Syntax: 

save

This command saves all your nicks and accounts immediately. Handy if you have the autosave functionality disabled, or if you don't trust the program's stability... ;-)

account

Type: string

For control channels with fill_by set to account: Set this setting to the account id (numeric, or part of the username) of the account containing the contacts you want to see in this channel.

allow_takeover

Type: boolean

When you're already connected to a BitlBee server and you connect (and identify) again, BitlBee will offer to migrate your existing session to the new connection. If for whatever reason you don't want this, you can disable this setting.

auto_connect

Type: boolean

With this option enabled, when you identify BitlBee will automatically connect to your accounts, with this disabled it will not do this.

This setting can also be changed for specific accounts using the account set command. (However, these values will be ignored if the global auto_connect setting is disabled!)

auto_join

Type: boolean

With this option enabled, BitlBee will automatically join this channel when you log in.

auto_reconnect

Type: boolean

If an IM-connections breaks, you're supposed to bring it back up yourself. Having BitlBee do this automatically might not always be a good idea, for several reasons. If you want the connections to be restored automatically, you can enable this setting.

See also the auto_reconnect_delay setting.

This setting can also be changed for specific accounts using the account set command. (However, these values will be ignored if the global auto_reconnect setting is disabled!)

auto_reconnect_delay

Type: string

Tell BitlBee after how many seconds it should attempt to bring a broken IM-connection back up.

This can be one integer, for a constant delay. One can also set it to something like "10*10", which means wait for ten seconds on the first reconnect, multiply it by ten on every failure. Once successfully connected, this delay is re-set to the initial value. With < you can give a maximum delay.

See also the auto_reconnect setting.

auto_reply_timeout

Type: integer

For Twitter accounts: If you respond to Tweets IRC-style (like "nickname: reply"), this will automatically be converted to the usual Twitter format ("@screenname reply").

By default, BitlBee will then also add a reference to that person's most recent Tweet, unless that message is older than the value of this setting in seconds.

If you want to disable this feature, just set this to 0. Alternatively, if you want to write a message once that is not a reply, use the Twitter reply syntax (@screenname).

away

Type: string

To mark yourself as away, it is recommended to just use /away, like on normal IRC networks. If you want to mark yourself as away on only one IM network, you can use this per-account setting.

You can set it to any value and BitlBee will try to map it to the most appropriate away state for every open IM connection, or set it as a free-form away message where possible.

Any per-account away setting will override globally set away states. To un-set the setting, use set -del away.

away_devoice

Type: boolean

With this option enabled, the root user devoices people when they go away (just away, not offline) and gives the voice back when they come back. You might dislike the voice-floods you'll get if your contact list is huge, so this option can be disabled.

Replaced with the show_users setting. See help show_users.

away_reply_timeout

Type: integer

Most IRC servers send a user's away message every time s/he gets a private message, to inform the sender that they may not get a response immediately. With this setting set to 0, BitlBee will also behave like this.

Since not all IRC clients do an excellent job at suppressing these messages, this setting lets BitlBee do it instead. BitlBee will wait this many seconds (or until the away state/message changes) before re-informing you that the person's away.

base_url

Type: string

There are more services that understand the Twitter API than just Twitter.com. BitlBee can connect to all Twitter API implementations.

For example, set this setting to http://identi.ca/api to use Identi.ca.

Keep two things in mind: When not using Twitter, you must also disable the oauth setting as it currently only works with Twitter. If you're still having issues, make sure there is no slash at the end of the URL you enter here.

charset

Type: string

This setting tells BitlBee what your IRC client sends and expects. It should be equal to the charset setting of your IRC client if you want to be able to send and receive non-ASCII text properly.

Most systems use UTF-8 these days. On older systems, an iso8859 charset may work better. For example, iso8859-1 is the best choice for most Western countries. You can try to find what works best for you on http://www.unicodecharacter.com/charsets/iso8859.html

color_encrypted

Type: boolean

If set to true, BitlBee will color incoming encrypted messages according to their fingerprint trust level: untrusted=red, trusted=green.

chat_type

Type: string

There are two kinds of chat channels: simple groupchats (basically normal IM chats with more than two participants) and names chatrooms, more similar to IRC channels.

BitlBee supports both types. With this setting set to groupchat (the default), you can just invite people into the room and start talking.

For setting up named chatrooms, it's currently easier to just use the chat add command.

commands

Type: boolean

With this setting enabled, you can use some commands in your Twitter channel/query. The commands are simple and not documented in too much detail:

Anything that doesn't look like a command will be treated as a tweet. Watch out for typos, or to avoid this behaviour, you can set this setting to strict, which causes the post command to become mandatory for posting a tweet.

debug

Type: boolean

Some debugging messages can be logged if you wish. They're probably not really useful for you, unless you're doing some development on BitlBee.

This feature is not currently used for anything so don't expect this to generate any output.

default_target

Type: string

With this value set to root, lines written in a control channel without any nickname in front of them will be interpreted as commands. If you want BitlBee to send those lines to the last person you addressed in that control channel, set this to last.

display_name

Type: string

Currently only available for MSN connections. This setting allows you to read and change your "friendly name" for this connection. Since this is a server-side setting, it can't be changed when the account is off-line.

display_namechanges

Type: boolean

With this option enabled, root will inform you when someone in your buddy list changes his/her "friendly name".

display_timestamps

Type: boolean

When incoming messages are old (i.e. offline messages and channel backlogs), BitlBee will prepend them with a timestamp. If you find them ugly or useless, you can use this setting to hide them.

fill_by

Type: string

For control channels only: This setting determines which contacts the channel gets populated with.

By default, control channels will contain all your contacts. You instead select contacts by buddy group, IM account or IM protocol.

Change this setting and the corresponding account/group/protocol setting to set up this selection.

With a ! prefix an inverted channel can be created, for example with this setting set to !group you can create a channel with all users not in that group.

Note that, when creating a new channel, BitlBee will try to preconfigure the channel for you, based on the channel name. See help channels.

group

Type: string

For control channels with fill_by set to group: Set this setting to the name of the group containing the contacts you want to see in this channel.

handle_unknown

Type: string

By default, messages from people who aren't in your contact list are shown in a control channel instead of as a private message.

If you prefer to ignore messages from people you don't know, you can set this one to "ignore". "add_private" and "add_channel" are like add, but you can use them to make messages from unknown buddies appear in the channel instead of a query window.

ignore_auth_requests

Type: boolean

Only supported by OSCAR so far, you can use this setting to ignore ICQ authorization requests, which are hardly used for legitimate (i.e. non-spam) reasons anymore.

lcnicks

Type: boolean

Hereby you can change whether you want all lower case nick names or leave the case as it intended by your peer.

local_display_name

Type: boolean

Mostly meant to work around a bug in MSN servers (forgetting the display name set by the user), this setting tells BitlBee to store your display name locally and set this name on the MSN servers when connecting.

mail_notifications

Type: boolean

Some protocols (MSN, Yahoo!) can notify via IM about new e-mail. Since most people use their Hotmail/Yahoo! addresses as a spam-box, this is disabled default. If you want these notifications, you can enable this setting.

message_length

Type: integer

Since Twitter rejects messages longer than 140 characters, BitlBee can count message length and emit a warning instead of waiting for Twitter to reject it.

You can change this limit here but this won't disable length checks on Twitter's side. You can also set it to 0 to disable the check in case you believe BitlBee doesn't count the characters correctly.

stream

Type: boolean

For Twitter accounts, this setting enables use of the Streaming API. This automatically gives you incoming DMs as well.

For other Twitter-like services, this setting is not supported.

target_url_length

Type: integer

Twitter replaces every URL with fixed-length t.co URLs. BitlBee is able to take t.co urls into account when calculating message_length replacing the actual URL length with target_url_length. Setting target_url_length to 0 disables this feature.

This setting is disabled for identica accounts by default and will not affect anything other than message safety checks (i.e. Twitter will still replace your URLs with t.co links, even if that makes them longer).

mode

Type: string

By default, BitlBee will create a separate channel (called #twitter_yourusername) for all your Twitter contacts/messages.

If you don't want an extra channel, you can set this setting to "one" (everything will come from one nick, twitter_yourusername), or to "many" (individual nicks for everyone).

With modes "chat" and "many", you can send direct messages by /msg'ing your contacts directly. Note, however, that incoming DMs are not fetched yet.

With modes "many" and "one", you can post tweets by /msg'ing the twitter_yourusername contact. In mode "chat", messages posted in the Twitter channel will also be posted as tweets.

mobile_is_away

Type: boolean

Most IM networks have a mobile version of their client. People who use these may not be paying that much attention to messages coming in. By enabling this setting, people using mobile clients will always be shown as away.

nick

Type: string

You can use this option to set your nickname in a chatroom. You won't see this nickname yourself, but other people in the room will. By default, BitlBee will use your username as the chatroom nickname.

nick_format

Type: string

By default, BitlBee tries to derive sensible nicknames for all your contacts from their IM handles. In some cases, IM modules (ICQ for example) will provide a nickname suggestion, which will then be used instead. This setting lets you change this behaviour.

Whenever this setting is set for an account, it will be used for all its contacts. If it's not set, the global value will be used.

It's easier to describe this setting using a few examples:

FB-%full_name will make all nicknames start with "FB-", followed by the person's full name. For example you can set this format for your Facebook account so all Facebook contacts are clearly marked.

[%group]%-@nick will make all nicknames start with the group the contact is in between square brackets, followed by the nickname suggestions from the IM module if available, or otherwise the handle. Because of the "-@" part, everything from the first @ will be stripped.

See help nick_format for more information.

nick_source

Type: string

By default, BitlBee generates a nickname for every contact by taking its handle and chopping off everything after the @. In some cases, this gives very inconvenient nicknames. The Facebook XMPP server is a good example, as all Facebook XMPP handles are numeric.

With this setting set to full_name, the person's full name is used to generate a nickname. Or if you don't like long nicknames, set this setting to first_name instead and only the first word will be used. Note that the full name can be full of non-ASCII characters which will be stripped off.

oauth

Type: boolean

This enables OAuth authentication for an IM account; right now the Twitter (working for Twitter only) and Jabber (for Google Talk, Facebook and MSN Messenger) module support it.

With OAuth enabled, you shouldn't tell BitlBee your account password. Just add your account with a bogus password and type account on. BitlBee will then give you a URL to authenticate with the service. If this succeeds, you will get a PIN code which you can give back to BitlBee to finish the process.

The resulting access token will be saved permanently, so you have to do this only once. If for any reason you want to/have to reauthenticate, you can use account set to reset the account password to something random.

ops

Type: string

Some people prefer themself and root to have operator status in &bitlbee, other people don't. You can change these states using this setting.

The value "both" means both user and root get ops. "root" means, well, just root. "user" means just the user. "none" means nobody will get operator status.

otr_policy

Type: string

This setting controls the policy for establishing Off-the-Record connections.

A value of "never" effectively disables the OTR subsystem. In "opportunistic" mode, a magic whitespace pattern will be appended to the first message sent to any user. If the peer is also running opportunistic OTR, an encrypted connection will be set up automatically. On "manual", on the other hand, OTR connections must be established explicitly using otr connect. Finally, the setting "always" enforces encrypted communication by causing BitlBee to refuse to send any cleartext messages at all.

password

Type: string

Use this global setting to change your "NickServ" password.

This setting is also available for all IM accounts to change the password BitlBee uses to connect to the service.

Note that BitlBee will always say this setting is empty. This doesn't mean there is no password, it just means that, for security reasons, BitlBee stores passwords somewhere else so they can't just be retrieved in plain text.

paste_buffer

Type: boolean

By default, when you send a message to someone, BitlBee forwards this message to the user immediately. When you paste a large number of lines, the lines will be sent in separate messages, which might not be very nice to read. If you enable this setting, BitlBee will buffer your messages and wait for more data.

Using the paste_buffer_delay setting you can specify the number of seconds BitlBee should wait for more data before the complete message is sent.

Please note that if you remove a buddy from your list (or if the connection to that user drops) and there's still data in the buffer, this data will be lost. BitlBee will not try to send the message to the user in those cases.

paste_buffer_delay

Type: integer

Tell BitlBee after how many (mili)seconds a buffered message should be sent. Values greater than 5 will be interpreted as miliseconds, 5 and lower as seconds.

See also the paste_buffer setting.

port

Type: integer

Currently only available for Jabber connections. Specifies the port number to connect to. Usually this should be set to 5222, or 5223 for SSL-connections.

priority

Type: integer

Can be set for Jabber connections. When connecting to one account from multiple places, this priority value will help the server to determine where to deliver incoming messages (that aren't addressed to a specific resource already).

According to RFC 3921 servers will always deliver messages to the server with the highest priority value. Mmessages will not be delivered to resources with a negative priority setting (and should be saved as an off-line message if all available resources have a negative priority value).

private

Type: boolean

If value is true, messages from users will appear in separate query windows. If false, messages from users will appear in a control channel.

This setting is remembered (during one session) per-user, this setting only changes the default state. This option takes effect as soon as you reconnect.

protocol

Type: string

For control channels with fill_by set to protocol: Set this setting to the name of the IM protocol of all contacts you want to see in this channel.

query_order

Type: string

This changes the order in which the questions from root (usually authorization requests from buddies) should be answered. When set to lifo, BitlBee immediately displays all new questions and they should be answered in reverse order. When this is set to fifo, BitlBee displays the first question which comes in and caches all the others until you answer the first one.

Although the fifo setting might sound more logical (and used to be the default behaviour in older BitlBee versions), it turned out not to be very convenient for many users when they missed the first question (and never received the next ones).

resource

Type: string

Can be set for Jabber connections. You can use this to connect to your Jabber account from multiple clients at once, with every client using a different resource string.

resource_select

Type: string

Because the IRC interface makes it pretty hard to specify the resource to talk to (when a buddy is online through different resources), this setting was added.

Normally it's set to priority which means messages will always be delivered to the buddy's resource with the highest priority. If the setting is set to activity, messages will be delivered to the resource that was last used to send you a message (or the resource that most recently connected).

root_nick

Type: string

Normally the "bot" that takes all your BitlBee commands is called "root". If you don't like this name, you can rename it to anything else using the rename command, or by changing this setting.

save_on_quit

Type: boolean

If enabled causes BitlBee to save all current settings and account details when user disconnects. This is enabled by default, and these days there's not really a reason to have it disabled anymore.

server

Type: string

Can be set for Jabber- and OSCAR-connections. For Jabber, you might have to set this if the servername isn't equal to the part after the @ in the Jabber handle. For OSCAR this shouldn't be necessary anymore in recent BitlBee versions.

show_ids

Type: boolean

Enable this setting on a Twitter account to have BitlBee include a two-digit "id" in front of every message. This id can then be used for replies and retweets.

show_offline

Type: boolean

If enabled causes BitlBee to also show offline users in Channel. Online-users will get op, away-users voice and offline users none of both. This option takes effect as soon as you reconnect.

Replaced with the show_users setting. See help show_users.

show_users

Type: string

Comma-separated list of statuses of users you want in the channel, and any modes they should have. The following statuses are currently recognised: online (i.e. available, not away), away, and offline.

If a status is followed by a valid channel mode character (@, % or +), it will be given to users with that status. For example, online@,away+,offline will show all users in the channel. Online people will have +o, people who are online but away will have +v, and others will have no special modes.

simulate_netsplit

Type: boolean

Some IRC clients parse quit messages sent by the IRC server to see if someone really left or just disappeared because of a netsplit. By default, BitlBee tries to simulate netsplit-like quit messages to keep the control channels window clean. If you don't like this (or if your IRC client doesn't support this) you can disable this setting.

ssl

Type: boolean

Currently only available for Jabber connections. Set this to true if you want to connect to the server on an SSL-enabled port (usually 5223).

Please note that this method of establishing a secure connection to the server has long been deprecated. You are encouraged to look at the tls setting instead.

status

Type: string

Most IM protocols support status messages, similar to away messages. They can be used to indicate things like your location or activity, without showing up as away/busy.

This setting can be used to set such a message. It will be available as a per-account setting for protocols that support it, and also as a global setting (which will then automatically be used for all protocols that support it).

Away states set using /away or the away setting will override this setting. To clear the setting, use set -del status.

strip_html

Type: boolean

Determines what BitlBee should do with HTML in messages. Normally this is turned on and HTML will be stripped from messages, if BitlBee thinks there is HTML.

If BitlBee fails to detect this sometimes (most likely in AIM messages over an ICQ connection), you can set this setting to always, but this might sometimes accidentally strip non-HTML things too.

strip_newlines

Type: boolean

Turn on this flag to prevent tweets from spanning over multiple lines.

show_old_mentions

Type: integer

This setting specifies the number of old mentions to fetch on connection. Must be less or equal to 200. Setting it to 0 disables this feature.

switchboard_keepalives

Type: boolean

Turn on this flag if you have difficulties talking to offline/invisible contacts.

With this setting enabled, BitlBee will send keepalives to MSN switchboards with offline/invisible contacts every twenty seconds. This should keep the server and client on the other side from shutting it down.

This is useful because BitlBee doesn't support MSN offline messages yet and the MSN servers won't let the user reopen switchboards to offline users. Once offline messaging is supported, this flag might be removed.

tag

Type: string

For every account you have, you can set a tag you can use to uniquely identify that account. This tag can be used instead of the account number (or protocol name, or part of the screenname) when using commands like account, add, etc. You can't have two accounts with one and the same account tag.

By default, it will be set to the name of the IM protocol. Once you add a second account on an IM network, a numeric suffix will be added, starting with 2.

timezone

Type: string

If message timestamps are available for offline messages or chatroom backlogs, BitlBee will display them as part of the message. By default it will use the local timezone. If you're not in the same timezone as the BitlBee server, you can adjust the timestamps using this setting.

Values local/utc/gmt should be self-explanatory. timezone-spec is a time offset in hours:minutes, for example: -8 for Pacific Standard Time, +2 for Central European Summer Time, +5:30 for Indian Standard Time.

tls

Type: boolean

By default (with this setting enabled), BitlBee will require Jabber servers to offer encryption via StartTLS and refuse to connect if they don't.

If you set this to "try", BitlBee will use StartTLS only if it's offered. With the setting disabled, StartTLS support will be ignored and avoided entirely.

tls_verify

Type: boolean

Currently only available for Jabber connections in combination with the tls setting. Set this to true if you want BitlBee to strictly verify the server's certificate against a list of trusted certificate authorities.

The hostname used in the certificate verification is the value of the server setting if the latter is nonempty and the domain of the username else. If you get a hostname related error when connecting to Google Talk with a username from the gmail.com or googlemail.com domain, please try to empty the server setting.

Please note that no certificate verification is performed when the ssl setting is used, or when the CAfile setting in bitlbee.conf is not set.

to_char

Type: string

It's customary that messages meant for one specific person on an IRC channel are prepended by his/her alias followed by a colon ':'. BitlBee does this by default. If you prefer a different character, you can set it using set to_char.

Please note that this setting is only used for incoming messages. For outgoing messages you can use ':' (colon) or ',' to separate the destination nick from the message, and this is not configurable.

translate_to_nicks

Type: boolean

IRC's nickname namespace is quite limited compared to most IM protocols. Not any non-ASCII characters are allowed, in fact nicknames have to be mostly alpha-numeric. Also, BitlBee has to add underscores sometimes to avoid nickname collisions.

While normally the BitlBee user is the only one seeing these names, they may be exposed to other chatroom participants for example when addressing someone in the channel (with or without tab completion). By default BitlBee will translate these stripped nicknames back to the original nick. If you don't want this, disable this setting.

type

Type: string

BitlBee supports two kinds of channels: control channels (usually with a name starting with a &) and chatroom channels (name usually starts with a #).

See help channels for a full description of channel types in BitlBee.

typing_notice

Type: boolean

Sends you a /notice when a user starts typing a message (if supported by the IM protocol and the user's client). To use this, you most likely want to use a script in your IRC client to show this information in a more sensible way.

user_agent

Type: string

Some Jabber servers are configured to only allow a few (or even just one) kinds of XMPP clients to connect to them.

You can change this setting to make BitlBee present itself as a different client, so that you can still connect to these servers.

utf8_nicks

Type: boolean

Officially, IRC nicknames are restricted to ASCII. Recently some clients and servers started supporting Unicode nicknames though. To enable UTF-8 nickname support (contacts only) in BitlBee, enable this setting.

To avoid confusing old clients, this setting is disabled by default. Be careful when you try it, and be prepared to be locked out of your BitlBee in case your client interacts poorly with UTF-8 nicknames.

web_aware

Type: string

ICQ allows people to see if you're on-line via a CGI-script. (http://status.icq.com/online.gif?icq=UIN) This can be nice to put on your website, but it seems that spammers also use it to see if you're online without having to add you to their contact list. So to prevent ICQ spamming, recent versions of BitlBee disable this feature by default.

Unless you really intend to use this feature somewhere (on forums or maybe a website), it's probably better to keep this setting disabled.

xmlconsole

Type: boolean

The Jabber module allows you to add a buddy xmlconsole to your contact list, which will then show you the raw XMPP stream between you and the server. You can also send XMPP packets to this buddy, which will then be sent to the server.

If you want to enable this XML console permanently (and at login time already), you can set this setting.

rename - Rename (renick) a buddy

Syntax: 

rename <oldnick> <newnick>
rename -del <oldnick>

Renick a user in your buddy list. Very useful, in fact just very important, if you got a lot of people with stupid account names (or hard ICQ numbers).

rename -del can be used to erase your manually set nickname for a contact and reset it to what was automatically generated.

< itsme> rename itsme_ you
 * itsme_ is now known as you

yes - Accept a request

Syntax: 

yes [<number>]

Sometimes an IM-module might want to ask you a question. (Accept this user as your buddy or not?) To accept a question, use the yes command.

By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the qlist command for a list of questions.

no - Deny a request

Syntax: 

no [<number>]

Sometimes an IM-module might want to ask you a question. (Accept this user as your buddy or not?) To reject a question, use the no command.

By default, this answers the first unanswered question. You can also specify a different question as an argument. You can use the qlist command for a list of questions.

qlist - List all the unanswered questions root asked

Syntax: 

qlist

This gives you a list of all the unanswered questions from root.

register - Register yourself

Syntax: 

register [<password>]

BitlBee can save your settings so you won't have to enter all your IM passwords every time you log in. If you want the Bee to save your settings, use the register command.

Please do pick a secure password, don't just use your nick as your password. Please note that IRC is not an encrypted protocol, so the passwords still go over the network in plaintext. Evil people with evil sniffers will read it all. (So don't use your root password.. ;-)

To identify yourself in later sessions, you can use the identify command. To change your password later, you can use the set password command.

You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs.

identify - Identify yourself with your password

Syntax: 

identify [-noload|-force] [<password>]

BitlBee saves all your settings (contacts, accounts, passwords) on-server. To prevent other users from just logging in as you and getting this information, you'll have to identify yourself with your password. You can register this password using the register command.

Once you're registered, you can change your password using set password <password>.

The -noload and -force flags can be used to identify when you're logged into some IM accounts already. -force will let you identify yourself and load all saved accounts (and keep the accounts you're logged into already).

-noload will log you in but not load any accounts and settings saved under your current nickname. These will be overwritten once you save your settings (i.e. when you disconnect).

You can omit the password and enter it separately using the IRC /OPER command. This lets you enter your password without your IRC client echoing it on screen or recording it in logs.

drop - Drop your account

Syntax: 

drop <password>

Drop your BitlBee registration. Your account files will be removed and your password will be forgotten. For obvious security reasons, you have to specify your NickServ password to make this command work.

blist - List all the buddies in the current channel

Syntax: 

blist [all|online|offline|away]

You can get a more readable buddy list using the blist command. If you want a complete list (including the offline users) you can use the all argument.

group - Contact group management

Syntax: 

group [ list | info <group> ]

The group list command shows a list of all groups defined so far.

The group info command shows a list of all members of a the group <group>.

If you want to move contacts between groups, you can use the IRC /invite command. Also, if you use the add command in a control channel configured to show just one group, the new contact will automatically be added to that group.

transfer - Monitor, cancel, or reject file transfers

Syntax: 

transfer [<cancel> id | <reject>]

Without parameters the currently pending file transfers and their status will be listed. Available actions are cancel and reject. See help transfer <action> for more information.

transfer cancel - Cancels the file transfer with the given id

Syntax: 

transfer <cancel> id

Cancels the file transfer with the given id

< ulim> transfer cancel 1
< root> Canceling file transfer for test

transfer reject - Rejects all incoming transfers

Syntax: 

transfer <reject>

Rejects all incoming (not already transferring) file transfers. Since you probably have only one incoming transfer at a time, no id is neccessary. Or is it?

< ulim> transfer reject

Chapter 6. Misc Stuff

Smileys

All MSN smileys (except one) are case insensitive and work without the nose too.

(Y)

Thumbs up

(N)

Thumbs down

(B)

Beer mug

(D)

Martini glass

(X)

Girl

(Z)

Boy

(6)

Devil smiley

:-[

Vampire bat

(})

Right hug

({)

Left hug

(M)

MSN Messenger or Windows Messenger icon (think a BitlBee logo here ;)

:-S

Crooked smiley (Confused smiley)

:-$

Embarrassed smiley

(H)

Smiley with sunglasses

:-@

Angry smiley

(A)

Angel smiley

(L)

Red heart (Love)

(U)

Broken heart

(K)

Red lips (Kiss)

(G)

Gift with bow

(F)

Red rose

(W)

Wilted rose

(P)

Camera

(~)

Film strip

(T)

Telephone receiver

(@)

Cat face

(&)

Dog's head

(C)

Coffee cup

(I)

Light bulb

(S)

Half-moon (Case sensitive!)

(*)

Star

(8)

Musical eighth note

(E)

Envelope

(^)

Birthday cake

(O)

Clock

Groupchats

BitlBee now supports groupchats on all IM networks. This text will try to explain you how they work.

As soon as someone invites you into a groupchat, you will be force-joined or invited (depending on the protocol) into a new virtual channel with all the people in there. You can leave the channel at any time, just like you would close the window in regular IM clients. Please note that root-commands don't work in groupchat channels, they only work in control channels (or to root directly).

Of course you can also create your own groupchats. Type help groupchats2 to see how.

Creating groupchats

To open a groupchat, use the chat with command. For example, to start a groupchat with the person lisa_msn in it, just type chat with lisa_msn. BitlBee will create a new virtual channel with root, you and lisa_msn in it.

Then, just use the ordinary IRC /invite command to invite more people. Please do keep in mind that all the people have to be on the same network and contact list! You can't invite Yahoo! buddies into an MSN groupchat.

Some protocols (like Jabber) also support named groupchats. BitlBee now supports these too. You can use the chat add command to join them. See help chat add for more information.

Away states

To mark yourself as away, you can just use the /away command in your IRC client. BitlBee supports most away-states supported by the protocols.

Away states have different names accross different protocols. BitlBee will try to pick the best available option for every connection:

Away
NA
Busy, DND
BRB
Phone
Lunch, Food
Invisible, Hidden

So /away Food will set your state to "Out to lunch" on your MSN connection, and for most other connections the default, "Away" will be chosen.

You can also add more information to your away message. Setting it to "Busy - Fixing BitlBee bugs" will set your IM-away-states to Busy, but your away message will be more descriptive for people on IRC. Most IM-protocols can also show this additional information to your buddies.

If you want to set an away state for only one of your connections, you can use the per-account away setting. See help set away.

Changing your nickname

BitlBee now allows you to change your nickname. So far this was not possible because it made managing saved accounts more complicated.

The restriction no longer exists now though. When you change your nick (just using the /nick command), your logged-in status will be reset, which means any changes made to your settings/accounts will not be saved.

To restore your logged-in status, you need to either use the register command to create an account under the new nickname, or use identify -noload to re-identify yourself under the new nickname. The -noload flag tells the command to verify your password and log you in, but not load any new settings. See help identify for more information.

Dealing with channels

You can have as many channels in BitlBee as you want. You maintain your channel list using the channel command. You can create new channels by just joining them, like on regular IRC networks.

You can create two kinds of channels. Control channels, and groupchat channels. By default, BitlBee will set up new channels as control channels if their name starts with an &, and as chat channels if it starts with a #.

Control channels are where you see your contacts. By default, you will have one control channel called &bitlbee, containing all your contacts. But you can create more, if you want, and divide your contact list accross several channels.

For example, you can have one channel with all contacts from your MSN Messenger account in it. Or all contacts from the group called "Work".

Type help channels2 to read more.

Creating a channel

When you create a new channel, BitlBee will try to guess from its name which contacts to fill it with. For example, if the channel name (excluding the &) matches the name of a group in which you have one or more contacts, the channel will contain all those contacts.

Any valid account ID (so a number, protocol name or part of screenname, as long as it's unique) can also be used as a channel name. So if you just join &msn, it will contain all your MSN contacts. And if you have a Facebook account set up, you can see its contacts by just joining &facebook.

To start a simple group chat, you simply join a channel which a name starting with #, and invite people into it. All people you invite have to be on the same IM network and contact list.

If you want to configure your own channels, you can use the channel set command. See help channels3 for more information.

Configuring a control channel

The most important setting for a control channel is fill_by. It tells BitlBee what information should be used to decide if someone should be shown in the channel or not. After setting this setting to, for example, account, you also have to set the account setting. Example:

< wilmer> chan &wlm set fill_by account
< root> fill_by = `account'
< wilmer> chan &wlm set account msn
< root> account = `msn'

Also, each channel has a show_users setting which lets you choose, for example, if you want to see only online contacts in a channel, or also/just offline contacts. Example:

< wilmer> chan &offline set show_users offline
< root> show_users = `offline'

See the help information for all these settings for more information.

Nickname formatting

The nick_format setting can be set globally using the set command, or per account using account set (so that you can set a per-account suffix/prefix or have nicknames generated from full names for certain accounts).

The setting is basically some kind of format string. It can contain normal text that will be copied to the nick, combined with several variables:

%nick

Nickname suggested for this contact by the IM protocol, or just the handle if no nickname was suggested.

%handle

The handle/screenname of the contact.

%full_name

The full name of the contact.

%first_name

The first name of the contact (the full name up to the first space).

%group

The name of the group this contact is a member of

%account

Account tag of the contact

Invalid characters (like spaces) will always be stripped. Depending on your locale settings, characters with accents will be converted to ASCII.

See help nick_format2 for some more information.

Nickname formatting - modifiers

Two modifiers are currently available: You can include only the first few characters of a variable by putting a number right after the %. For example, [%3group]%-@nick will include only the first three characters of the group name in the nick.

Also, you can truncate variables from a certain character using the - modifier. For example, you may want to leave out everything after the @. %-@handle will expand to everything in the handle up to the first @.

New stuff in BitlBee 1.2.6

Twitter support. See help account add twitter.

New stuff in BitlBee 1.3dev

Support for multiple configurable control channels, each with a subset of your contact list. See help channels for more information.

File transfer support for some protocols (more if you use libpurple). Just /DCC SEND stuff. Incoming files also become DCC transfers.

Only if you run your own BitlBee instance: You can build a BitlBee that uses libpurple for connecting to IM networks instead of its own code, adding support for some of the more obscure IM protocols and features.

Many more things, briefly described in help news1.3.

New stuff in BitlBee 3.0

BitlBee can be compiled with support for OTR message encryption (not available on public servers since encryption should be end-to-end).

The MSN module was heavily updated to support features added to MSN Messenger over the recent years. You can now see/set status messages, send offline messages, and many strange issues caused by Microsoft breaking old-protocol compatibility should now be resolved.

Twitter extended: IRC-style replies ("BitlBee:") now get converted to proper Twitter replies ("@BitlBee") and get a reference to the original message (see help set auto_reply_timeout). Retweets and some other stuff is also supported now (see help set commands).

New stuff in BitlBee 1.3dev (details)

Most of the core of BitlBee was rewritten since the last release. This entry should sum up the majority of the changes.

First of all, you can now have as many control channels as you want. Or you can have none, it's finally possible to leave &bitlbee and still talk to all your contacts. Or you can have a &work with all your work-related contacts, or a &msn with all your MSN Messenger contacts. See help channels for more information about this.

Also, you can change how nicknames are generated for your contacts. Like automatically adding a [fb] tag to the nicks of all your Facebook contacts. See help nick_format.

When you're already connected to a BitlBee server and you connect from elsewhere, you can take over the old session.

Instead of account numbers, accounts now also get tags. These are automatically generated but can be changed (help set tag). You can now use them instead of accounts numbers. (Example: acc gtalk on)

Last of all: You can finally change your nickname and shorten root commands (try acc li instead of account list).

New stuff in BitlBee 3.0.5

OAuth2 support in Jabber module (see help set oauth). For better password security when using Google Talk, Facebook XMPP, or for using MSN Messenger via XMPP. Especially recommended on public servers.

Starting quick groupchats on Jabber is easier now (using the chat with command, or /join + /invite).

SSL certificate verification. Works only with GnuTLS, and needs to be enabled by updating your bitlbee.conf.

New stuff in BitlBee 3.2

Upgradeed to Twitter API version 1.1. This is necessary because previous versions will stop working from March 2013. At the same time, BitlBee now supports the streaming API and incoming direct messages.

bitlbee-3.2.1/doc/example_plugin.c0000644000175000017500000000054712245474076016446 0ustar wilmerwilmer/* * This is the most simple possible BitlBee plugin. To use, compile it as * a shared library and place it in the plugin directory: * * gcc -o example.so -shared example.c `pkg-config --cflags bitlbee` * cp example.so /usr/local/lib/bitlbee */ #include #include void init_plugin(void) { printf("I am a BitlBee plugin!\n"); } bitlbee-3.2.1/doc/BUILD.win320000644000175000017500000000043012245474076015003 0ustar wilmerwilmerInstructions for building BitlBee for Windows ============================================= 1) Install the mingw32 compiler 2) Compile GLib2 for the target i586-mingw32msvc 3) Cross-compile BitlBee: $ ./configure --target=i586-mingw32msvc --ssl=bogus --arch=Windows bitlbee-3.2.1/doc/CHANGES0000644000175000017500000014566512245474076014277 0ustar wilmerwilmerThis ChangeLog mostly lists changes relevant to users. A full log can be found in the bzr commit logs, for example you can try: http://bugs.bitlbee.org/bitlbee/timeline?daysback=90&changeset=on Version 3.2.1: - Most important change: http_client updated to use HTTP/1.1, now required by Twitter. - fill_by setting can now be used to fill a channel contacts *not* in a certain group/on a certain account/etc. See "help set fill_by" - Added utf8_nicks setting which lets you use non-ASCII nicknames for your contacts. Might not work with all IRC clients, use at your own risk! - Lots of bugfixes. Finished 27 Nov 2013 Version 3.2: - By far the most important change, a thorough update to the Twitter module: * Now using Twitter API 1.1, * which means it's now using JSON instead of XML, * which means access to the streaming API (Twitter only, other Twitter API services don't seem to have it). No more 60-second polls, #twitter looks even more like real IRC now! * Also, the streaming API means nice things like receiving DMs. * show_ids, already enabled by default for a while, now uses hexa- decimal numbers, which means a 256-entry backlog instead of just 100. * Added a mode=strict setting which requires everything to be a command. The "post" command should then be used to post a Tweet. - Jabber module bugfix that fixes connection issues with at least Google Talk but reportedly some other servers (OpenFire?) as well. - SSL modules improved a little bit. GnuTLS module now supports SNI and session caching. Shouldn't change much, but hopefully reduces latency and bandwidth usage a little bit. - A bunch of other fixes/improvements here and there. Finished 6 Jan 2013 Version 3.0.6: - Updated MSN module to speak MSNP18: * Biggest change is that this brings MPOP support (you can sign in to one account from multiple locations). * Restored support for *sending* offline messages. * Some support for federated (i.e. Yahoo!) contacts. (Only messages might work, you won't see them online.) - Twitter: * Work-around for stalls that are still happening sometimes. * Added "favourite" command. * "show_ids" enabled by default. - Handle see-other-host Jabber messages which should fix support for MSN-XMPP. - Misc. fixes and improvements. Finished 14 Oct 2012 Version 3.0.5: - SSL certificate verification (edit your bitlbee.conf to enable it). Works only with GnuTLS! - OAuth2 support in Jabber module (works with Google Talk, Facebook and MSN Messenger). - Support for ad-hoc Jabber groupchats. Just create a channel and start inviting people, similar to how this works on other IM networks. Works fine with GTalk, depends on a conference server being set up on other networks. - Allow old-style Jabber login (non-SASL), this solves problems with some old/buggy Jabber servers. (acc jabber set sasl off) - Use HTTPS for OAuth1 authentication traffic. - Awareness of Twitter's t.co URL lengt^Wshortening when checking message length. - Fixed identi.ca OAuth support. OAuth will now always be used for both Twitter and identi.ca accounts. - Fix nick_format=%full_name with libpurple. - Instead of "protocol(handle)", use the account tags introduced in 3.0 when root wants to refer to an account (in log messages, queries, etc.) - Many small bugfixes, improvements, etc. Finished 18 Feb 2012 Version 3.0.4: - Merged Skype support. This used to be a separate plugin, and it still is, but by including it with BitlBee by default it will be easier to keep it in sync with changes to BitlBee. - Fixed a file descriptor leak bug that may have caused strange behaviour in BitlBee sessions running for a long time. - Now fetches Twitter mentions as well if the "fetch_mentions" account setting is enabled. - With t.co now all over Twitter, show the original (but truncated) URL between . - Fixed MSN Messenger login issues ("timeout" while fetching buddy list). - Another (related) GnuTLS compatibility fix (now 2.13+?). Finished 4 Dec 2011 (Exactly 6 years since 1.0!) Version 3.0.3: - Fixed Twitter compatibility. (The API call used to get the following list was deprecated.) - Twitter: Enable the show_ids setting to assign a two-digit short ID to recent tweets to use for retweets and replies (so you can RT/reply to more than just a person's last message). - Some other Twitter fixes/improvements. - "otr reconnect" command and some other fixes. - GnuTLS 2.12 compatibility fix. - Include "FLOOD=0/9999" in the 005/ISUPPORT line at login to hint the IRC client that rate limiting is not required. (Next step: Get IRC clients to parse it.) - Other stuff too small to mention. Finished 12 Jun 2011 Version 3.0.2: - Fixed MSN login issues with @msn.com accounts. - /CTCP support: You can CTCP VERSION Jabber contacts, and CTCP NUDGE MSN contacts. More may come later, ideas are welcome. - By default, leave Twitter turned on for libpurple builds. - Allow using /OPER to identify/register as well. (Password security hack.) - Fixed proxy support with libpurple. - Some minor changes/fixes. Finished 7 Mar 2011 Version 3.0.1: - Fixed some bugs that were found only after releasing 3.0, including Jabber contacts never going offline, MSN login issues, compilation issues on some non-Linux systems, a fairly big memory leak in the MSN SOAP code, etc... - Better handling of multiple control channels and set private=false. - Using login.icq.com for ICQ logins again since AOL sold ICQ so it doesn't live on the AIM servers anymore. - Fixed ability to join password-protected Jabber rooms. - Time out if logging into an account takes too long. - Fixed NSS SSL module. - Support for server-side Gadu-Gadu contact lists (via libpurple, there's still no native gg support). - Allow omitting password when using "account add", the password can be entered using /OPER to prevent echoing to screen and logs. Finished 24 Nov 2010 Version 3.0: - Important: This version drops backward compatibility with the file format used for user settings in versions before 1.2. If you're upgrading from very old BitlBee versions (like 1.0.x), you'll have to recreate your BitlBee account - or use an 1.2.x BitlBee once to do the conversion. - Rewrote the IRC core, which brings: * Support for multiple (control) channels, so you can have one channel per buddy group, per IM account/protocol, or for example a &offline with all offline contacts. See "help channels" for information on how to use this. * You can also leave and rejoin all channels. Being in &bitlbee is no longer required. * Now you can start groupchats by just joining a new channel and inviting contacts. (The "chat with" command still works as well.) * You can change your nickname, whenever you want. * Root commands: + To hopefully resolve confusion about the "account set" syntax, the ordering was changed slightly: "account set acc/setting value" becomes "account acc set setting value". Obviously the same order should now be used for other account subcommands. + Shortcuts: Instead of "account list" you can say "acc li". * /whois shows idle/login times of your contacts when available. * paste_buffer (previously known as buddy_sendbuffer) now works for chats as well. * The nick_source setting was replaced with a nick_format setting, which looks more or less like a format string, and lets you tweak how nicknames for contacts are generated in more detail. It also tries to convert non- ASCII characters (i.e. accents) properly. * A per-channel show_users setting lets you configure exactly which kinds of contacts (online/away/offline) should show up in a channel and what modes (none/voice/etc) they should have. Like show_offline, but together with other new features it can for example create an &offline channel with just offline contacts. * If you connect (and identify) to a BitlBee server you're already connected to, you can take over the existing session instead of starting a new one. * More correct validation of channel names: They can contain pretty much any character, unlike nicknames. - Support for using libpurple instead of BitlBee's built-in IM protocol modules. This can be enabled by passing --purple=1 to the configure script. * This adds support for many more IM protocols to BitlBee. * And new functionality to existing protocols. * This is and will always be optional and using it on public servers is *not* recommended. It should be pretty stable, but costs more RAM/etc. * Switching to libpurple should be pretty transparent. See "help purple" for a list of supported protocols (works only in libpurple-enabled binaries). - Rewritten MSN module, implementing MSNP15 instead of the old MSNP8: * MSNP8 support from MSN was getting pretty unreliable. There were issues with remembering display names and adding contacts/auth requests (or even contacts silently getting blocked!). This upgrade should fix all of that. * Support for sending offline messages. * Support for setting and reading status messages. - Integrated the bitlbee-otr branch in a mostly non-intrusive way. Since this is not end-to-end it should *not* be enabled on public servers. Distro packagers are requested to offer OTR as a separately installable plugin. (Compile with --otr=plugin) Please do not enable it by default as there is no easy way to disable OTR once the plugin is loaded/built in. - Support for file transfers, in and out. /DCC SEND a file to a contact and it becomes a file transfer, and incoming file transfers become /DCC SENDs to you. Note that this is mostly useful when combined with libpurple, as only the Jabber native protocol module currently supports file transfers. - Updated Yahoo! module to be in sync again with libyahoo2. This mostly fixes issues with authorization requests. - Show if a contact is mobile or not. See "help set mobile_is_away". - Easier handling of XMPP chatroom invitations. - The chatroom mode of the Twitter module is now enabled by default, since this was by far the most popular. To disable it, see "help set mode". - Added some Twitter-specific commands that can only be used in the Twitter window. Most important addition: Retweets. See "help set commands". - Removed some ancient account/nick migration scripts and added one for easier switching from Pidgin and other libpurple-based clients to BitlBee. - Many bug fixes in both the core and IM modules, small feature enhancements and other minor changes. Finished 22 Oct 2010 Version 1.2.8: - Now always using the AIM-style authentication method for OSCAR connections, even when connecting to ICQ. This solves login issues some people were having. (If you have problems, try changing the old_icq_auth setting.) - Twitter: * Allow changing the Twitter API base URL so the module can also be used for identi.ca or any other compatible network. * Fetch the full list of Twitter contacts instead of slowly adding all contacts as they post a message. * Fixed message length counting. * Allow following/unfollowing people using the usual add/remove commands. * Better error reporting. - Added a user_agent setting to the Jabber module to get around artificial client restrictions. - Allow nick changes (although only before register/identify). - Some more minor bugfixes/etc. Finished 4 Jul 2010 Version 1.2.7: - Fixed problems with MSN Messenger authentication. ("Could not parse Passport server response") - Fixed broken typing notifications when talking to GTalk contacts. - Fixed an issue with non-anonymous Jabber chatrooms polluting the nick namespace, sometimes generating odd warning messages. - Restored ability to start groupchats on ICQ. - Added show_offline setting that will also show offline contacts in the control channel. - OAuth support for Twitter: This means the module will keep working after June (this also changes "via API" under your tweets into "via BitlBee"). Finished 15 May 2010 Version 1.2.6a: - Fixed a typo that renders the Twitter groupchat mode unusable. A last- minute change that came a few minutes late. Finished 19 Apr 2010 Version 1.2.6: - Native (very basic) support for Twitter, implemented by Geert Mulders. Currently supported are posting tweets, reading the ones of people you follow, and sending (not yet receiving!) direct messages. - Fixed format of status messages in /WHOIS to improve IRC client compatibility. - Show timestamps of offline messages/channel backlogs. - Allow saving MSN display names locally since sometimes this stuff breaks server-side. (Use the local_display_name per-account setting.) - Suppress empty "Headline:" messages for certain new XMPP broadcast messages. - Better handling of XMPP contacts with multiple resources on-line. Default behaviour now is to write to wherever the last message came from, or to the bare JID (usually becomes a broadcast) if there wasn't any recent msg. - Added a switchboard_keepalives setting which should solve some issues with talking to offline MSN contacts. (Although full support for offline messages is not ready yet!) - The usual misc. bug fixes. Finished 19 Apr 2010 Version 1.2.5: - Many bug fixes, including a fix for MSN login issues, Jabber login timing issues, Yahoo! crashes at login time with huge contact lists, - Avoid linking in a static version of libresolv now that glibc has all relevant functions available in the dynamic version. - Improved away state code and added the ability to set (non-away) status messages using "set status" (also possible per account) and see them in blist and /whois output. - Added a post-1.2 equivalent of encode/decode to quickly encrypt/decrypt passwords in a way that BitlBee can read them. - Allow using the full name for generating nicknames, instead of just the handle. This is especially useful when using the Facebook XMPP server. - Auto reconnect is now enabled by default since all protocols can properly detect cases where auto reconnect should be avoided (i.e. concurrent logins). - Changed the default resource_select setting which should reduce message routing issues on Jabber (i.e. messages going someone's phone instead of the main client). Finished 17 Mar 2010 Version 1.2.4: - Most important change (and main reason for releasing now): Upgraded Yahoo! module to a newer version to get it working again. - join_chat command replaced with the much better chat command: * Similar to how account replaced login/slist/logout. Add a chatroom once, then just /join it like any other room. Also automatic joining at login time is now possible. * Note that the old way of starting groupchats (/join #nickname) is now also deprecated, use "chat with" instead. * See "help chat" and "help chat add" for more information. - Rewrote bitlbee.conf parser to be less dumb. - Fixed compatibility (hopefully) with AIM mobile messages, certain kinds of Google Talk chatrooms. - Fixed numerous stability/reliability bugs over the last year. Finished 17 Oct 2009 Version 1.2.3: - Fixed one more flaw similar to the previous hijacking bug, caused by incon- sistent handling of the USTATUS_IDENTIFIED state. All code touching these variables was reviewed and should be correct now. Finished 7 Sep 2008 Version 1.2.2: - Security bugfix: It was possible to hijack accounts (without gaining access to the old account, it's simply an overwrite) - Some more stability improvements. - Fixed bug where people with non-lowercase nicks couldn't drop their account. - Easier upgrades of non-forking daemon mode servers (using the DEAF command). - Can be cross-compiled for Win32 now! (No support for SSL yet though, which makes it less useful for now.) - Exponential backoff on auto-reconnect. - Changing passwords gives less confusing feedback ("password is empty") now. Finished 26 Aug 2008 Version 1.2.1: - Fixed proxy support. - Fixed stalling issues while connecting to Jabber when using the OpenSSL module. - Fixed problem with GLib and ForkDaemon where processes didn't die when the client disconnects. - Fixed handling of "set charset none". (Which pretty much breaks the account completely in 1.2.) - You can now automatically identify yourself to BitlBee by setting a server password in your IRC client. - Compatible with all crazy kinds of line endings that clients can send. - Changed root nicknames are now saved. - Added ClientInterface setting to bind() outgoing connections to a specific network interface. - Support for receiving Jabber chatroom invitations. - Relaxed port restriction of the Jabber module: added ports 80 and 443. - Preserving case in Jabber resources of buddies, since these should officially be treated as case sensitive. - Fully stripping spaces from AIM screennames, this didn't happen completely which severly breaks the IRC protocol. - Removed all the yellow tape around daemon mode, it's pretty mature by now: testing.bitlbee.org serves all (~30) SSL users from one daemon mode process without any serious stability issues. - Fixed GLib <2.6 compatibility issue. - Misc. memory leak/crash fixes. Finished 24 Jun 2008 Version 1.2: - Added ForkDaemon mode next to the existing Daemon- and inetd modes. With ForkDaemon you can run BitlBee as a stand-alone daemon and every connection will run in its own process. No more need to configure inetd, and still you don't get the stability problems BitlBee unfortunately still has in ordinary (one-process) daemon mode. - Added inter-process/connection communication. This made it possible to implement some IRC operator features like WALLOPs, KILL, DIE, REHASH and more. - Added hooks for using libevent instead of GLib for event handling. This should improve scalability, although this won't really be useful yet because the one-process daemon mode is not reliable enough. - BitlBee now makes the buddy quits when doing "account off" look like a netsplit. Modern IRC clients show this in a different, more compact way. (This can be disabled if your client doesn't support this.) - GLib 1.x compatibility was dropped. BitlBee now requires GLib 2.4 or newer. This allows us to use more GLib features (like the XML parser). By now GLib 1.x is so old that supporting it really isn't necessary anymore. - Many, many, MANY little changes, improvements, fixes. Using non-blocking I/O as much as possible, replaced the Gaim (0.59, IOW heavily outdated) API, fixed lots of little bugs (including bugs that affected daemon mode stability). See the bzr logs for more information. - One of the user-visible changes from the API change: You can finally see all away states/messages properly. - Added units tests. Test coverage is very minimal for now. - Better charset handling: Everything is just converted from/to UTF-8 right in the IRC core, and charset mismatches are detected (if possible) and the user is asked to resolve this before continuing. Also, UTF-8 is the default setting now, since that's how the world seems to work these days. - One can now keep hashed passwords in bitlbee.conf instead of the cleartext version. - Most important change: New file format for user data (accounts, nicks and settings). Migration to the new format should happen transparently, BitlBee will read the old files and once you quit/save it will save in the new format. It is recommended to delete the old files (BitlBee doesn't do this automatically, it will just ignore them) since they won't be used anymore (and since the old file format is a security risk). Some advantages of this file format switch: * Safer format, since the identify-password is now salted before generating a checksum. This way one can't use MD5 reverse lookup databases to crack passwords. Also, the IM-account passwords are encrypted using RC4 instead of the simple obfuscation scheme which BitlBee used so far. * Easier to extend than the previous format (at least the .nicks format was horribly limited). * Nicknames for buddies are now saved per-account instead of per-protocol. So far having one buddy on multiple accounts of the same protocol was a problem because the nicks generated for the two "instances" of this buddy were very unpredictable. NOTE: This also means that "account del" removes not just the account, BUT ALSO ALL NICKNAMES! If you're changing IM accounts and don't want to lose the nicknames, you can now use "account set" to change the username and password for the existing connection. * Per-account settings (see the new "account set" command). - A brand new Jabber module. Besides the major code cleanup, it also has has these new features: * Pretty complete XMPP support: RFC3920, RFC3921 plus a number of XEPs including XEP45, XEP73 and XEP85. (See http://www.xmpp.org/ for what all these things mean exactly.) Privacy lists are not supported for obvious reasons. * This complete support also includes TLS and SASL support and SRV record lookup. This means that specifying a server tag for connections should (almost) never be necessary anymore, BitlBee can find the server and can automatically convert plaintext connections to TLS-encrypted ones. * XEP45: Jabber chatroom support! * XEP85 means typing notifications. The older XEP22 (still used by some clients including Gaim <2.0) is not supported. * Better handling of buddies who have more than one resource on-line. As long as one resource is on-line (and visible), BitlBee will show this. (The previous module didn't keep track of resources and sent an offline event as soon as any resource disappears.) * You can now set your resource priority. * The info command now gives away state/message information for all resources available for that buddy. (Of course this only works if the buddy is in your contact list.) * An XML console (add xmlconsole to your contact list or see "help set xmlconsole" if you want it permanently). - The Yahoo! module now says it supports YMSG protocol version 12, which will hopefully keep the Yahoo module working after 2008-04-02 (when Yahoo! is dropping support for version 6.x of their client). - MSN switchboard handling changes. Hopefully less messages will get lost now, although things are still not perfect. Finished 17 Mar 2008 Version 1.0.4: - Removed sethostent(), which causes problems for many people, especially on *BSD. This is basically the reason for this release. - "allow" command actually displays the allow list, not the block list. - Yahoo away state/msg fix. - Don't display "Gender: Male" by default if nothing's filled in (OSCAR "info" command) - Fixed account cleanup (possible infinite loop) in irc_free(). - Fixed configdir error message to not always display the compile-time setting. Finished 20 Aug 2007 Version 1.0.3: - Fixed ugliness in block/allow list commands (still not perfect though, the list is empty or not up-to-date for most protocols). - OSCAR module doesn't send the ICQ web-aware flag anymore, which seems to get rid of a lot of ICQ spam. - added show_got_added(), BitlBee asks you, after authorizing someone, if you want to add him/her to your list too. - add -tmp, mainly convenient if you want to talk to people who are not in your list. - Fixed ISON command, should work better with irssi now. - Fixed compilation with tcc. - Fixed xinetd-file. - Misc. (crash)bug fixes, including one in the root command parsing that caused mysterious error messages sometimes. Finished 24 Jun 2006 (Happy 4th birthday, BitlBee!) Version 1.0.2: - Pieces of code cleanup, fixes for possible problems in error checking. - Fixed an auto-reconnect cleanup problem that caused crashes in daemon mode. - /AWAY in daemon mode now doesn't set the away state for every connection anymore. - Fixed a crash-bug on empty help subjects. - Jabber now correctly sets the current away state when connecting. - Added Invisible and Hidden to the away state alias list, invisible mode should be pretty usable now. - Fixed handling of iconv(): It's now done for everything that goes between BitlBee and the IRC client, instead of doing it (almost) every time something goes to or come from the IM-modules. Should've thought about that before. :-) - When cleaning up MSN switchboards with unsent msgs, it now also says which contact those messages were meant for. - You can now use the block and allow commands to see your current block/ allow list. Finished 1 Apr 2006 Version 1.0.1: - Support for AIM groupchats. - Improved typing notification support for at least AIM. - BitlBee sends a 005 reply when logging in, this informs modern IRC clients of some of BitlBee's capabilities. This might also solve problems some people were having with the new control channel name. - MSN switchboards are now properly reset when talking to a person who is offline. This fixes problems with messages to MSN people that sometimes didn't arrive. - Fixed one of the problems that made BitlBee show online Jabber people as offline. - Fixed problems with commas in MSN passwords. - Added some consts for read-only data, which should make the BitlBee per- process memory footprint a bit smaller. - Other bits of code cleanup. Finished 14 Jan 2006 Version 1.0: - Removed some crashy debugging code. - QUIT command now works before logging in. (Mainly an RFC-compliancy fix.) - Hopefully slightly clearer handling of buddy add requests. - set buddy_sendbuffer_delay now also supports milisecond precision. - Renamed #bitlbee to &bitlbee to avoid confusion with the channel on OFTC. - Reviewed the xinetd file and installation instructions. - HTML stripping is configurable again. - Quit messages (at least on crashes) should appear again. - Cleaned up some unnecessary code in the Jabber module, and implemented handlers for headline messages (which allows you to use RSS-to-Jabber gateways). - Lowered the line splitting limit a bit to fix data loss issues. - The $proto($handle) format used for messages specific to one IM-connection now only include the ($handle) part when there's more than one $proto- connection. - Fix for a crash-bug on broken Jabber/SSL connections. - Incoming typing notifications now also come in as CTCP TYPING messages, for better consistency. Don't forget to update your scripts! - AIM typing notifications are supported now. - Jabber module only accepts ports 5220-5229 now, to prevent people from abusing it as a port scanner. We aren't aware of any Jabber server that runs on other ports than those. If you are, please warn us. - Send flood protection can't be enabled anymore. It was disabled by default for a good reason for some time already, but some package maintainers turned it back on while it's way too unreliable and trigger-happy to be used. - Removed TODO file, the current to-do list is always in the on-line bug tracking system. - Fixed a potential DoS bug in input handling. Finished 4 Dec 2005 Version 0.99: - Fixed memory initialization bug in OSCAR module that caused crashes on closing the connection. - Most likely fixed the bug that caused BitlBee to use 100% CPU time in some situations. - Outgoing MSN typing notifications are now understood correctly by the orignal MS Mac/Windows clients (again). - Added "account add $protocol" to the documentation, got rid of a lot of over-markup (i.e. overuse of bold-tags), reviewed some other parts. - Small changes to help.xsl to fix small flaws in the help.txt formatting. - Messaging yourself now doesn't make irssi or mIRC crash anymore. Finished 3 Nov 2005 Version 0.93: - " is now correctly converted to " instead of '. - Code cleanup in OSCAR module. - Some changes for better RFC-compliancy. - It's now possible to specify an alternate Jabber server. - bitlbee_save() now also checks the return value of fclose(), which might solve more problems with lost account data. - Fixed a file descriptor leak in bitlbee.c. - Signedness fixes (mainly to keep GCC4 happy). - Away messages are now sent correctly to most ICQ clients. - BitlBee knows now which connection sends HTML, so it will strip HTML automatically, "set html strip" is no longer necessary. Also, outgoing HTML is escaped correctly. - info-command works for AIM-connections too now. - /notices to users will be sent as auto-away replies. - Messages about a connection now also mention a handle, for people who have multiple accounts in use of the same protocol. - Examples are back in help.txt. Finished 31 Oct 2005 Version 0.92: - Fixed some formatting issues with the help.txt XSL-sheet. - Moved the polling of the IRC connections to glib instead of a separate select(). - Added ctags generation to the Makefiles. - Sending a CTCP TYPING message to a user in your buddy list now sends a typing notification to that person, if the protocol supports it. You probably want to write/use a script for your IRC client to do this. - A dash is no longer considered a lowercase underscore in nicknames. - CTCP's to buddies no longer alter their private/non-private state. - Clean shutdown (with saving settings) on SIGTERM. - Fixed a crash on connecting to unreachable Jabber/SSL servers. - On ICQ, offline messages are now requested after receiving the buddy list. This should get rid of the "Message from unknown OSCAR handle" messages on login. - The linked list of buddies/nicks is now always sorted, which makes the blist output a bit more useful. - Fixed a crash on renaming NickServ. (There's no reason to do this, but still crashing isn't really how BitlBee should tell you that. ;-) - Now the message about the "new" ICQ server-side buddy lists is only displayed when the server-side list is still empty. - The Yahoo! markup language stripper is now less likely to strip things that shouldn't be stripped. - Working around a shortcoming in the Yahoo! cleanup code that didn't cause any serious problems in previous versions, but got pretty nasty (100% CPU time usage) now with everything in the glib main loop. - Fixed a bug in http_encode that might have caused buffer overflows (although not likely to be exploitable) when trying to encode strings with non-ASCII characters. - Fixed AIM screenname handling on ICQ connections. - When blocking someone, the MSN module now closes the conversation you're having with him/her (if any) so he/she can't continue talking to you (for as long as the conversation lasts). - Away messages are only sent when messaging someone outside the control channel. (And now also when the user is offline instead of just away.) - Moved charset conversion in serv_buddy_rename() to the right place so bogus changes are always detected now. - iso8859-1 is now the default charset because -15 caused some problems and because -1 is enough for most people anyway. - Fixed crashes on attempting to add a buddy (or do other things) on connections that are still initializing. - Added support for server-side notifies using the WATCH command. - IRC_MAX_ARGS is dead, at least for IRC commands. - Fixed a bug that made BitlBee crash on failing fresh MSN switchboard connections. - Fixed an invisibility bug in the MSN code that handled transfers to other servers in the middle of a session. - Newline stripping added to prevent newline-in-friendlyname attacks. (Which allowed remote people to make BitlBee send raw custom IRC lines.) Finished 23 Feb 2005 Version 0.91: - Full support for ICQ server-side contact lists! NOTE: BitlBee now ignores your client-side contact list. If you want to import your ICQ contact list, use the import_buddies command. - Added the import_buddies command for upgrading purposes. - Added support for OpenSSL. - Fixed one major problem with the daemon mode by getting rid of the global IRC structure. - Documentation fixes. help.txt is now generated from XML. Also updated the installation manual. - Made the quickstart up-to-date. (By Elizabeth Krumbach) - Some bitlbeed additions. (By Marcus Dennis) - info-command support for Jabber, MSN, Yahoo! and a more verbose info-reply for ICQ. (By Frank Thieme) - Support for Jabber over SSL. - nick_get() appends a _ on duplicates instead of chopping off the last character. - Got rid of an unused piece of code that caused crashes. (oscar.c:gaim_parse_clientauto) - When splitting long messages into 450-char chunks, BitlBee now tries not to cut off in the middle of a line. - Added a warning when the user specifies an unknown OSCAR server. - Removed GLib 2.x dependency, BitlBee will work with GLib 1.x again. - Improved strip_html(), now less likely to strip non-HTML things. - An invalid account-command will now display an error message. - Fixed a bug that caused crashes when /CTCP'ing a groupchat channel. - Hopefully better Unicode/non-ASCII support for ICQ. - Fixed MSN connection crash on non-ASCII characters in screenname. - Added some missing charset conversion calls. (serv_got_crap, serv_buddy_rename) - "account off" without extra arguments now logs off all connections. - Fixed a crash-bug on disconnecting Yahoo! connections before they're fully connected. - Fixed a bug in helpfile handling which sometimes caused crashes in daemon mode. - block and allow commands work with just a nick as an argument again. - Working around a crash on some probably invalid Yahoo! packets. - Fixed a possible double free() on shutdown in irc_free(). - Talking to ICQ people on AIM and vice versa and talking to people with @mac.com accounts now possible. - Low ASCII chars are now stripped from away-messages so the Jabber module won't barf on BitchX and lame-script away messages anymore. Finished 25 Sep 2004 Version 0.90a: - Fixed the Yahoo! authentication code. Finished 28 Jun 2004 Version 0.90: - A complete rewrite of the MSN module. This gives BitlBee the following new features/improvements: * You can now start groupchats with MSN people without having to send them a bogus message first. * People who are in your MSN block/allow list, but not in your contact list, shouldn't show up in your BitlBee buddy lists anymore. * Passport authentication shouldn't lock up the whole program for a couple of seconds anymore. Additionally, it should also work behind proxies now. * Better recognition of incoming file transfers; they're now recognized when coming from non-English MS Messenger clients too. * Fixed a problem with MSN passwords with non-alphanumeric characters. * Mail notification support (also for Yahoo!)... * Parsing of maintenance messages (ie "Server is going down in x minutes"). * Hopefully more stability. - Changes in the OSCAR module: * Better reading of ICQ-away-states. * Lots of cleanups. - Yahoo! module: * Fixed authentication on 64-bit machines. (Patch from Matt Rogers) * Better stripping of markup tags. - Lots of cleanup in all IM-modules. - Added support for using libnss instead of libgnutls. - Reverse hostname lookups work on IPv6 sockets too now. (And don't crash the program anymore.) - Fixed a little problem with identifying right after registering a nick. - Restored complete proxy support and added a proxy setting to the conffile. - BitlBee can now tell you when people in your buddy list change their "friendly name". - Instead of an account number, you can also specify the protocol name or (part of) the username as an account identification with commands like "account on", "add", etc. - BitlBee remembers what connection a question (i.e. authorization request) belongs to and cleans it up when the connection goes down; this fixes (one of) the last known crash bugs. - Plus some other changes in question management. (The query_order setting is one of them. The default behaviour changed a bit, for more information please read "help set query_order".) - Also fixed a memory management bug in the question code which caused some crashes. - Optimized some nick handling functions and added a hash of all the users to speed up user_find() a bit (especially good for people with large buddy and notify lists). - Lots of changes for the Win32 port (see http://jelmer.vernstok.nl/). - Added the drop-command. - Fixed small problem with versions of sed which don't support the + "operator" (the BSD version, for example, even though the operator is documented properly in the re_format manpage...). - Added the default_target setting. - Added a CenterICQ2BitlBee conversion script. - Put back the evaluator for "set charset" (which got lost somewhere between 0.84 and 0.85), so an incorrect charset will be rejected again. - ISON now (A) gives one single reply and (B) also replies when none of the persons asked for are on-line. - Using GConv instead of iconv now. - Incoming messages larger than 450 characters are now split into parts before sending them to the user. - Fixed a bug in irc_exec() which could crash the program when some commands were called with too little arguments. - Fixed a dumb NULL pointer dereference in the JOIN command. - Added rate limiting to bitlbeed. (Against server hammering) - Added handling of CTCP PINGs (yet another self-ping used by some IRC clients...) - Added bitlbee_tab_completion.pl. - Removed the vCard handling code from Jabber because it's (A) not used and (B) had a possible format string vulnerability. - Got rid of strcpy() in account.c. (Thanks to NETRIC for reporting these two problems.) - ISO8859-15 is now the default charset. Finished 21 May 2004 Version 0.85a: - Fixed an authentication problem with logging into some MSN accounts. - Removed a non-critical warning message from the ICQ servers when logging in with an empty contact list. - Fixed reading the [defaults] section of bitlbee.conf. - The last newline in outgoing messages when using the buddy_sendbuffer is now removed correctly. - Yahoo! colour/font tag stripping now actually works. - Fixed compilation on *BSD and some Linux architectures. Finished 24 Mar 2004 Version 0.85: - Users can specify alternate configuration files/directories at runtime now. - Rename now doesn't choke on name changes with only case changes anymore. - Imported the daemon patch into the main source tree. The daemon mode is still experimental, this just eases maintenance. This daemon patch brings a lot of features, including (as the name says) a real daemon mode and also buffering of the data sent to the user, and flood protection. - Strips font and colour codes from Yahoo! messages. - Support for groupchats on Yahoo! - Fixed removing Yahoo! buddies from a different group than "Buddies". - Jabber presence packets with error messages are interpreted correctly now. (They used to be parsed as a signin.) - bitlbee_save() checks return values from fprintf() and writes to tempfiles first to make sure no old files get lost when there's a write error. - ICQ buddies are added all at once instead of once at a time. This should solve problems with huge buddy lists. - Made the client pinging timings configurable. (Without requiring recompilation) - MSN and Yahoo flag the connection as "wants_to_die" when they're logged off because of a concurrent login. This means reconnection will be disabled on concurrent logins. - BitlBee can now buffer the messages sent to someone before they're actually sent, and wait for a specified number of seconds for more lines to be added to the buffer before the message will really be sent. - Renamed the reconnect_delay setting to auto_reconnect_delay. - Unknown settings aren't saved anymore. Finished 13 Mar 2004 Version 0.84: - Removed the libsoup dependency. - Fixed AuthMode=Registered: It will now restore your accounts when identifying. - Fixed Yahoo! support. - Fixed a little memory leak in user.c. - Fixed a security bug in unused code in proxy.c, only people who use the HTTP proxy support and an untrusted proxy might need this. We haven't done an emergency release for this fix because proxy support is disabled by default. - Fixed some memory leaks in IM-code. Finished 13 Feb 2004 Version 0.83: - Fixed a crash bug on connecting to unsupported accounts. - Fixed a problem with connecting to MSN accounts with empty buddy lists. - Fixed another inifite-loop bug in nick_get() and added a piece of code which detects the infinite loop and asks the user to send a bug report. - Fixed iconv-Solaris linking issues. - Fixed all the problems with spaces in AIM screennames now, we hope. - Fixed a buffer overflow in the nick handling code. Buffers are overflowed with static data (nulls), so we don't think it's exploitable. - Added server-client pinging, useful for remote servers. - Added the hostname setting. - Some bitlbeed changes. - Added a little part to the on-line quickstart about the settings and other help topics, this hopefully answers a lot of FAQ's. - Fixed the signal handler to re-raise the signal after the handler quits. This makes sure the default handler is called after our handler sends the user a bye-message, so core dumps are created and the parent will get more useful information back from wait(). - Added support for ICQ URL messages. - Fixed strip_html() behaviour on unknown &entities;. - Fixed a possible problem with Yahoo! - Fixed a problem with logging into more than one MSN account at once. Finished 31 Dec 2003 Version 0.82: - Fixed a little bug in nick.c which could cause a complete hang for some tricky buddylists. (Thanks to Geert Hauwaerts for helping with fixing this bug) - Fixed MSN support. (Lots of thanks to Wouter Paesen!) - Removed the old login/logout commands. - Added the qlist command. - Fixed a buffer overflow in the nick checking code. (Thanks to Jon Åslund for pointing us at this problem) - Adds the add_private and add_channel options for set handle_unknown. - Some documentation updates. - Added two small utilities to encode/decode BitlCrypted files. Finished 31 Oct 2003 Version 0.81a: - This version just fixes some small things we should've fixed before releasing 0.81: - Fixed a small bug in the auto-reconnect cleanup code. - Fixed the Makefile: Now it doesn't just overwrite your etc files when installing. - Fixed the Makefile: $prefix/etc/bitlbee/ is the default etcdir now. - Disabling MSN by default, now that it doesn't work. It'll be back on as soon as we get the module working again. Finished 16 Oct 2003 Version 0.81: - Added a configuration file. - Added support for the PASS command to restrict access to the daemon to only the people who know this password. - Only allowing registered people to use the daemon is possible too. - In case you, at some time, need to check the currently running BitlBee version, just CTCP-VERSION someone in the channel. - Added the auto_connect setting for people who don't want the program to connect to IM-networks automatically. - Extended the blist command. - Applied the auto-reconnect patch from G-Lite. - Applied the iconv patch from Yuri Pimenov. - Imported the MSN-friendlyname patch from Wouter Paesen. - Away-message aliasing code now just parses the beginning of the string, not the whole string. This allows you to have a more descriptive away message like "Busy - Fixing bugs in BitlBee" and all the IM connections will have a busy-like away-state. - Added some information about away states to the help system. - MSN file transfers aren't silently ignored anymore. - Integrated the Yahoo protocol fix from Cerulean Studios (Trillian). (Thanks to Tony Perrie!) - Made all protocol plugins optional. (Patch from Andrej Kacian/Ticho) Finished 15 Oct 2003 Version 0.80: - Fixed a very stupid bug in the MSN away-state reading. - nick_cmp() now actually works, RFC-compliant. - Fixed and cleaned up the away-state handling, there were some very weird things in the original Gaim code base which broke this completely all the time. - The daemon prevents you from using root/NickServ as your nick now, previous versions crashed on that. - At last ... GROUP CHAT SUPPORT! :-D - People who are *not* away get mode +v in #bitlbee now, so you can see in /names who's away and who's not. - Crashing BitlBee by using the NICKSERV command without any arguments is impossible now. - Added some notes about Darwin/OSX portability. - Separated connections from accounts. This means restoring a lost connection can be done using a simple "account on " command. See "help account" for more information. *** For now this won't cause problems when upgrading because the login command still exists (as an alias for "account add"). This alias will not stay forever, though. - irc_process() now makes sure it reads the whole available buffer before executing the commands. Previous versions were very bad at handling long floods from non-floodprotected clients. The code is still not perfect, but good enough for local links. - Allow/Deny questions from msn.c now also mention your own screenname. This is useful for people who run two (or even more) MSN accounts in one BitlBee. - Fixed a little bug in the helpfile-changed-check code. - A little trick in "make install" makes sure the help function in running sessions doesn't break while upgrading. - Added a nifty (and editable) MOTD. - Added IRIX to the compatibility list. - Added support for Cygwin. - Better HTML-stripping, it also handles &entities; correctly now. - Fixed some problems to make it compile on Solaris. - Added support for messages from Miranda/Mac ICQ. (Code port from Gaim 0.59) - Fixed the crash problem when removing yahoo buddies. - Added the handle_unknown setting. - Did some editing on a few docs. - Added a FAQ. - Added the daemon-patch by Maurits Dijkstra which adds stand-alone daemon support to BitlBee. - Jabber now doesn't barf on high ASCII characters in away messages anymore. (Thanks to Christian Häggström ) Finished 24 Jun 2003 Version 0.74a: - The music-festivals-are-bad-for-your-mind release. - This one actually contains the fix for the bug 0.74 claimed to have. Finished 11 Jun 2003 Version 0.74: - Fixed a security leak, where using a / in the nickname causes the saved settings and account information to be stored in undesirable places. Finished 10 Jun 2003 Version 0.73: - Fixed the unable-to-remove-icq-users (actually users from any *local* contact list) bug. - Fixed away bug in aim protocol. - Fixed the 'statistics' under the blist command output. - Removed the commands from the XML documentation because they're 'on-line' already. - Added some signal handling; ignoring SIGPIPE should als get rid of some crashes (for some weird reason this has to be done). Also, crashes because of things like segfaults are a bit more verbose now. ;-) - Changed the select() timeout in main(), this might improve some latencies. (At leasts it speeds up signing on (especially for ICQ) a lot!) - Made the own-QUIT messages more compliant, probably. - Fixed some memory-bugs, thanks to valgrind. - irc_write() now checks the write() return value, and tries to send the rest of the string, if it could not write it completely the first time. - Hostname lookups also work on NetBSD now. (Thanks to David.Edmondson*sun*com (hi spambot)) - At last, a new protocol. Welcome to ... YAHOO! - Documentation and code cleanup. Somehow the helpfile documented register and identify twice, now that's what I call over-documenting.. :-/ - Added the rename command to the helpfile, somehow I forgot that one. - Been a bit pedantic about compiler warnings. They're all dead now. - Fixed a small Makefile problem which gave an error when a clean tree was "made distclean" - Fixed a (possible) memory leak in nogaim.c:proto_away() - Fixed the way proto_away() calls proto_away_alias_find(), now it gives the *whole* list of away states - proto_away() doesn't give a NULL message anymore because AIM didn't like that - Got rid of the last goto in my code (without ruining the code) - Created a more samba-like compiling process (without the complete command lines, just a simple echo) - "help set ops" works now too, without quoting the "set ops" - Trying to log in with a protocol without a prpl (ICQ and TOC, for example) made previous versions crash Finished 13 Apr 2003 Version 0.72: - Updated the documentation. - Improved the MSN friendlyname code. (Although it doesn't seem to be perfect yet..) - info-command added to get more information about ICQ users. - blist-command added to get a complete buddy list. - Fixed a bug which broke the AIM code when adding a screenname with a space in it. - Added the NS alias for the NICKSERV command (Tony Vroon). - Fixed the USERHOST command implementation (Tony Vroon). - /me from IM-networks is now converted to a CTCP ACTION correctly. - Added an utils/ directory with some misc toys and handy things. - Added a /notice to the on_typing event. Don't use it though, the /notice flood will just be a big annoyance. ;-) - Some people like root and themself to be ops, some don't. Now it's configurable. (set ops) - Now the umode stuff actually works. Next step: Use those modes... (How?) Finished 19 Dec 2002 Version 0.71: - Fixed the help command output layout (alignment) - Added a sample xinetd file - Cleaned up, 0.70 released with a build-stamp and DEADJOE file (oops).. - Messages can be sent like ', ' in the control channel now, instead of just ': ' - Added a debug setting boolean: Set it to true for some debugging crap which used to be on by default.. - Changed the /whois reply: In the server section you now see the connection this user belongs to. - Added some root/permission checks. - configure script works correctly when the terminating / is forgotten for a directory. - Fixed format string bug in protocols/oscar/meta.c (Hmm, what's the use of that file?) - Added '#include "crypting.h"' to commands.c to get rid of stupid warnings - Fixed crash-bug (SIGSEGV when adding an @-less contact to MSN/Jabber) - Added to_char setting - Fixed bug in set.c: It ignored the new value returned by the evaluator :-( - Removed protocol tag from 'hostname' in user hostmask because this info is in /whois' server section now - Added the GPL. Somehow 0.7 released without a COPYING file.. :-/ - Enhanced the root_command() parser, you can 'quote' "arguments" now so arguments can be strings with spaces - Debugging versions have True as the default value for set debug - NICKSERV is now an alternative for PRIVMSG root. This does not affect functionality of current NICKSERV commands, but does allow people to just do identify in channel. - NICKSERV REGISTER now doesn't try to log you in (to check if the user exists) but checks for the existence of the user-configuration files. - NICKSERV SET password now works (as does set password in channel). This makes changing your password possible. - NICKSERV password now stored in irc_t. - ./configure now only bugs you about possible problems with strip if it's actually going to strip (wooohoooo! _sexy_ :) - Fixed a load of warnings in oscar.c, irc.c, nick.c and set.c - Split up root_command() into a version which eats raw strings and one which eats split strings - New help system: Help available for most (all?) commands, all read from an external help-file. - Changed the maximum message length in irc_usermsg() from IRC_MAX_LINE to 1024 (for loooong help messages..). - Only allow user to set supported umodes. - Fixed a memory leak in crypting.c (Thanks to Erik Hensema.) - Added a send_handler callback to user_t. Needed for the following entry: - Added the NickServ user as a root-clone. - Disabled tcpd by default because it's just a PITA for a lot of systems and because you can use /usr/sbin/tcpd as well. - The root user can be renamed now. Finished 16 Sep 2002 bitlbee-3.2.1/doc/comic_3.0.png0000644000175000017500000006007612245474076015454 0ustar wilmerwilmer‰PNG  IHDRîÅЙš¿ pHYsaa¨?§iiCCPPhotoshop ICC profilexÚc``žàèâäÊ$ÀÀPPTRää¥À~ž™!1¹¸À1 À‡!/?/•020|»ÆÀÈÀÀÀpY×ÑÅÉ•4Àš\PTÂÀÀp€Á(%µ8™á CzyIA c ƒHRvA cƒHvH3c OIjE ƒs~AeQfzF‰‚¡¥¥¥‚cJ~RªBpeqIjn±‚g^r~QA~QbIj Ô^—ü÷ÄÌ<#U*ƒˆÈ( >1H.-*ƒ%ƒƒƒƒCC"C=Æ£ oÅ]KW0Þcc bšÀtY˜9’y!óK––[¬z¬­¬÷Ø,Ù¦±}cgßÍ¡ÄÑÅñ…3‘ó—#×nMî4}2ýüêë‚ïá?~úÓúÏñÿ 4ú–ñ] cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF\ IDATxÚìg`TÅׯÛÒ{$@`轤)RDE•&"(`AQÿ¨ ¨€ EEŽ€ôÞ;!”’@é=›-çýpw7›B|q¾dsïܹóÜigÎyÎZó¥öè%¸ZèÓæ»o ZhñÊ#×q j©ôÜÿàþ÷?¸ÿÁýn‰‚{ãPìá}Ñwõhb“‰ÿ:¸Ú‚ÿ¦u=\k\Ë¿|ôÍCõÿíp× *jmƒõþDnŽOºšS®}Û^bûûãºÇ—à‰¾*s|Ù¢JßÖ¡“¿ÝÇtÓè¯&Ed÷*óPðêøŸä§3ðØˆ Ï4äJöðq†ª–|õíyâ%’Ûœ§²M½yl§ˆH‡"")o½p,¿°C5à4UD¶6nÐàñf•œ†Š\œÝÃ`°{úBp£`¸ˆÈæjð½Ì…=ÍÀåñ^P)Í’çOgº¤Ë Ôçå+ ôl³dè˜)"2ÚÊZb™Âb’Ý-ßlbB€µW]/ pA‰s'Øô3œN˜^Df©˜lÍ4OË Ï¡ŸD»PÁ–ÉM8+"b‡ÎúU>Vá±äÊ w%oP¾AX›÷>8rÀmÜ7 W甸«¡›òë+8ù-\c¹ÐR¹Ö\ß„D˨Ï\*O©¬Y„*wâUìR~í„Ò»DäjoÍQS~0+—­¯YJúB QypU~u€ó98–É#€Ñ$î¶æz±9xï,þ5ƒkTZ¢K\vž¦ÊÒž¤*¿–á»ÿq üo¹õSN‘jÈ>’<ûÃóH‰Yw}ÁIùåÆ4œÝ ™­¹TïÀ¨ ~71Z<Ëâ$[9¬ÏïcFˆuò÷41ÎݵÑO–<Þª’×]M–µº”×Sž,ê*ª€2$ز=Ñ‚åp’5vO´µ˜7×0Åâµ#ŠÌ<ÈCÝ €’K̺ë`&Vùõ=5ZlÄä`ζ×â•ÇÒ¨LZãm{•ë9qS~ú­4â¾¶¦Ú<öz…5Ð[s”€Öuñ&€…[ù‚9iiîÈ›TËÏ€ÉÐàÓ‰‹ÛXzÍ«­Òß]Ù ™¿!£W¬™÷—~lÞÑø–™ÙÀ×Þ²œ6ñ`NaùÙ:±ú<•Ùv ¸„±“ó€øÉŒóQÊ}š9zÜŽ-Þ·P•|V­º ˜Íéáfà’ùáá-¸I]:JfÄ~x¥‰LÅÅP‡""§=xÛ.›Þod´8•½©<ÏÉ0Ü ™ÏRκZ„e""’¢a2¨[unåå‚ö¦|J€<´…¨0Ü ¸«ï³"2ÃkdN?ê‰~ò¥f†}¾6Ìó³€øÆJV#¨Ý'ˆ*—m9Æá»ùúÏuÀm íãÎùMzI{Ã@ÓìÍpÃn‘‘ø}ðjÔ/ð¨á²ˆ>÷Uï«"Q nJ~ޤPëâ¶÷i¼š—iØcÈVùIõ¾Ä´îÉŽ¡svdÙ]˜ªð–Vd †3{,Ÿ!{ýÈ7fçÚßÓ?íìZ»ß‚È$YYnGþeã&yxY¥3üï;îÝdTnÑ»Ô?™L‹Ìa.J“y/zhÿ2Óã›ÿqùMISý§šûîpÿé6SÕÕ=ªà°WâOÔ²ÿÿϯò¿åò¯Ã[hÝÕG|?¬e…@ –ˆˆyQ¥ÃbŽYüY˜Š@»l9o«€²ßED$sÄcGï¸â¥HIH?Pp!ÒÏß·Á¶Õä8ÃõŒ«Ë´UöõÍó3fõùƒ² öÇ Ëy ~®,©±óÎÔy ú‡«]ûæïáÍý·¯ÿpæŽÑ~I©žÕÞÑ<ÔÖM¯©\j8mѽq3ˆˆTáÕ€ÿKïiûLæ>8~©—+£]K‘zˆ,wŠÈ7 ]ÂîV=ùb¾Q$Í%‰|µ×˜­û?ï܃Ÿ÷éÔêÉõ"’<ëb"s+âÔn,¬‘±t‘Š¼Ù†J{ >wgEëü“DD¤5Ýd“+žC›þœ*¹”ª–o·Pdu9³*8µµNƒGý2^ÊvŸ/Dû©w¿Ì|c{†\+"ò2/ˆˆdøñÑ“<]è¹z| üãéL#s]®Ô„gä#tÇdYkpðACC‘7è#""èÑûdVOºÈ%ß²¥êDÈFÞx8[ÉT±WDžÄÿ“¥+ÛzÀ²É42›wØIÿQj,ÍÝŠJ_­Ý÷e]5Eµ&÷…²„äz¨çˆÈW¨·Å­ùÕ)©Ç""¦²¬x &‰ÈÓt“_¡Î.³Èqwn<$¸é*¶‰Hc¥£9jødø{3-?Ë&üò”_ù꜠‘4‘_^ªtÝÁt£;S¤4ÇDDV˜“]\‘t–t_ZYû?==38¨ðýeE×ÞŸÇ*A| uò³Äࣳjg]R}«âvš7²N+ªËÃt4í9•vS]`=u~û-€ô“‰÷g8ÐGÏÉý8U0>$1C.@kTâêPÏMÚçÔw±—Ä|°ª?î¥xú¼î¾ÀMj¯¡%€ @—ßýr¨å\ú™ v5F‹<ÈÙÏš\Ç4!·eÝ-¥ \\ÈÔ˜.¿  ÂTãL/û,ž]2x‘k©eŒ–S]`OÌTìýß; GúEgYÍžà† ŒPÉ£‰-÷o^q¢Áþ€‡!3_3é&‹]8 ¦YªqS1¤ÎÊ’ªa/\_á7ЉyFH8Ì’?“<¡´;Í(5rÁ–k‰/\á0Í/ûFM'Ÿ¯ýÚCÙ"D¬ëÔB=ÜüÇúˆ8÷F½[Û®¤ÿx½JÇr…;Šñ6ÛÉK‹Ò‚B:œ^1±”½šîtmÊèõYåßþ ‹A5wKëJ^1mÇNZ Ž úV«Øga^±ˆÚ"sñ¤Ú‘£Ìü„ÿ‡®Í(ŽTê¹ÿtUÿÁýî}»_þ¯QвÆ373®<Û¯ˆüé±éÁ:›ù=Ü¡ÒߥaÁ\ç!ƒu%îOW®XªìánOì{ñ%}nRv¸°¨].4 <²»e⹘x7—ZUJ«ÎþøReeµþ¹ƒgÒà ¿ÑÜ~ìýbIÝ’ x]ü\ÕÊ^àÝ®O¨MÄ3o}³’º‚Ô³>R#q}õ_DD>µ±°êÄ‹ˆ´·(¹rgª©? È1\ÉÞiY‚ e‰°ïŠˆlƒ/ìþý1  £Œ*­F}9ï³%ÒíM…cgŸN‰ˆÌBõ«ˆÈz˜0 @ÝyŸHWÕféÅ"GzNÊîÏ–¸‹e¤’LOeF¯Ë’Tµ•ža.=Eä(ìè?¬Løþ×YÏÍWò7Á+Vd¹–†+Ô88"R×XIjê}S‰û\²ý³˜›+"bAS¿Ë‹]©¬I†}"?À!»§/j.Ÿj >_‹*ñÓ¾zבq£)g‘c0I$£"ª%„¬ ì€ªæ“4WyBUY""=•>ÛXQܹŸ‘tøCdüiÿø«¨&©pÙ~ö+œ”Þ5i!º<#Ò>/!PŽÓ/ßâÑãLNöô}#Üpòx¬Y™† ¥Š*c˜p„RP€ä8ÉU¦‰n]ëY4o @ÎUSü ê– 1ÃpO»ƒÖoì¡Îý²þa|iŸ±/r6ùü=öwÍ#8@uûçËŽ^oc\Á5@¹™–—i=U/ApofØ//À«bgx&v̈à pÒ‰}w0[;C(dAA-bO-¾ÓÉΡ¼c•òž£ÇºÃôùoô.)pÏ]M?y¹çÄ[®\yíÿŸ6ã?¸ÿÁýîp ÜýæG îÒ°£Ü D=JpÓ¬®«ÜÓzXüpð{”àÆý±^åX\!¸Éé”{„àÆ‚ÿ#÷AnÜË”~”dæ¥ù#÷"!¸9ç©ÖÝžEû!Ü*÷®§|õ‡UçŒgÛwTgëš—ÒáÎêÚ=uŸŸñD—ŒÛÞ—û÷x*}ï˜á߃ÿ$°ÓÊyÊ´Gpµo$ðÇèBÏäîûé¯íŽ ¸Àö+¤¸[‹¾ £Ié\3ït?«4߈ˆÈ·õ¢°N¢öõÖàU™íœöˆÈ×Tι¹r` Ûò-2õ(³kõ>Øñ~¦ˆ˜fzü&ÑæðÐeæ´…²Æ4‰ 0ß 7ã.R3ºÜñþˆ¹ bÃ*ÿ%"’PFwÐ.ƒò &tjTÊ¿|Ø9‘‹Ó'®1X‰ú”‰•ke`¾LPb3ä»UÍÅ?BÄ|4ÕÏD$FEEƒÍ÷ô” ‚LOPAò:1TîÜ$´¿]Ê­A“ˆH‚š•]¨."§àË|Šxß “"²Ï:ß¸Ö T^‰ˆÈà9ôIòª´n{ÎÖm‚P|Aã`¨ˆ˜{Á~'pl9ýÏ«Œç4Tý昭»ñÒl†¯ HÃßœ—ð»=aå ¾-€Ph\&-½Ü gÓcê0®‰­NC¿À÷£ÈV±[p|Jcë“›m»l[|'(x<ñ`Jj«²¬•FÏdr¸§úþÀýÆÄ¹3X¸J‘¶H#½,^êfƒ|iÉöI¾ãÔÖ‰§™ˆˆ´à™¤LÕ£õ"Û^:㼜8­P†Žß»Tõ¼ò×¹ O¨ÿ‘¶ u§ë(ñR-\›ƒr+¹ÂS6BwMÞ²þü½Š@uÍ®ä¨t‘ ‡E""ò³ã)‰èï÷ä“_ný‡|æ;¦ìjx$ÜE¾¬juë‚5ø*G‰ˆÈåéOWoßÿ­î´¥bļ-Û–oùH»Ø{×>ЭË/E¼À’ÇpIDÄø xUã?ãƒwþ™iÓÙfM ¬„'&¥Tð®*Æe‹E-P¤‡çÓ> u‹Ë´˜ØêwH™CMºÿâ›þ\c¯p&ÖåQ;a í&ñˆÀ5ÿœò«Š¯+' Þû¯ C6PöÏb´ƒ­\³í¡µî¶¦ÚR¥{^öýÂ~·p owŒ¤Ý¡Å9ÐNÜöÇ]væƒ/œC=vZñ.A …|Š«uSG…£ÒæO‹yÁ §ÂÃhÝ-/FðÏÝ‹yÑÐGò Z÷£)ŠÒímCáY#:Fºû»âFKºùþÛT­pgGœiüyamÖ¶êsѼ~²ÅC‘ îµë €=­¢¿*Ø»sFtˆ¦ÒÞYŽ«—'Ü­4‡-O$Ox©ÀÝà æšÕ¯œhòPšVW-ÿX‘û 7ºRóŸÈúôãsÅøæáÿ1ûaŠBï?…ØÒwG fþ0Ýüö¦‚ùßFÃ3³}š<ïvÿÇ®n)¾¡[Ý)ÿzâ» ô0£ÿCܾxqóA­»×^óZ?û¦~öE¸¼ðžÏCDËóQÝ\C͹ lcöµ%Ù<~+5U^ƒ‚|ÄviǤ}à?áeGþÿ¥BËìÆ5 Çã tèKÿ.æw®.Ôþ ¸†Ån©»6%ø}۫ت)É÷çt¢sóQ…¿wã ¸´®ÞªsqlwÊx|þæ;ö]øÅ°Û†z•£ËŸn#.,HohvŽÜÖøëÝ ´,†?/åÖ sÙ›í®s8q±U»"d‹ž93 \ZWlAßCƒ‰Hf9Æ)ä‰hêjoÇ‹$¼yÀžŠ”¸ñ͗ߨ¿‰ˆ˜ª»müÙ"Ù?¥d8³Uº²RLËü¤…8\’M*íþŠº½"#y5ÅŸÃyY«|ùT:ñ±} R”ë8åGK°‡Çé(UXx%O/sp¸!ò!# ãW§![rOàå‹ÿìyGN'ÜÁ$–{´˜ñ6äÙ´@Þ– ,™Æ«ÒœŠá¬£ô·D˜ÙÿäDZ}øQÄTZ—b‘Át”Ö¹ìÎjI d”ˆ´¢y²„Öà±f<õL×OåÜÉhòKƒr/üb(Æ¥ã;Õ’Î×*MDOÔç.ôL¹ì%s¦k&z¨½ 7Õ€àðÇ·û!¤/Æ^ |hNÌÛ­k.4ÕÙùdNz•«]%—ÛOUÙoùAÐÌblÞÀÑëØ*²†jÒšR]ßè m¥?Èú ‰H-Kî–Á=é*u§d‘kÞTƒ_-tÀá[“ˆ^PNGû®i{7Š%`¿%m4I‚Ó"‹é" qpì|Yšq¼eø%©†[}/èHO©nn1"²ZÏ‹ˆÈ›ŠÞ²Á5ƒe;Oºyç¨(êÖ­Ï|^ŒŽnÕÕêw<؃:Ñ3»]÷›êWsGUÁ½‰˜É<†z¢j³`æí×ü´@÷æ{žøWôûmEÂÑ.{]µ.©¿…ÝYÌø9¬<Ô\PŒƒ·t…kÃÁ 3˜·SYbñ²0Í^š\P)WŠ Õ'&¿Þ^³q•<†XLà=÷)Âý Ê´k÷uÇm'ÇÌÍMÂý/¤ªÕN· c«£k¡GíÀ‚ÓŽ-ÎÞÄ&Í=S(gÞ@¦ ñüÉQùÄ]}L¹µÊÊðˆ¤2hþèºåf!I¢OFeh&Ün¿¥_¿^ÌpCˆ¬…;æN+ì˜çìx[“P¦E»qÍ /¾qÏìÖO”mzÆ>m%@·hZ7\BMSb#§o;3_ÐxÅóÊ;™1"ò"@H¦=ä£çÞK*ó|&"«‰ÓÕ‹–é]‘KV|Øÿ NúÀs9·Ÿ™§óz1ÃÅx1˜<âëGHš}p.Fr(3E$Î’ë×ÉV‚‡'݆äÿ¥Z|öãÙ¼;-Dßn.f¸©+nÃZ8æ`Á «?¹eUÑŸ9e¸mÉyQ¦b àTBÓ¼=þ‹ŠòÜ_2$ükà’ï=šÎssþp³ÆùûV}õVQcÏâu….eÈÿmñ LܾëðUà°aU‘¥?,m†è›…$ŠÙ$"réÍüãˆ3!Éÿˆˆ,?*ò{õ3—zuxnd-h·›#¾þî÷–°šÆ&ì±>¶Òé]1MîXZÔ‘Ô¸/ßû–.¨X%CˆÈmÁXèú¾·•3å òŒyæ'‹¦MS]' ŒT˜Õáù„~¯«Ñ!XDFXþ%®Tgt2\‘Jl·\Ü Ï›Å´×“7dýE~ÅõF%T^õ>9|ÄÀM”'W+ZΙDs9߰倥ëç Fí·/ç "C¬ª8suÓ‹ˆ,ÂÇ\™•"qZ"0Í,"ò®Ëà ú&è`^z˜r¦¤’+Íc ô÷‹¯Q#ûÍ ‘&Õêµ'&ÄzsÞã«¥®†A_ƒöTv½Q¡­¤¬ù WÛ‡¶îÆ@ŒD3‰ÈõŠm¬Z¥5j´Þðj%Ènb”-UõúÎ+JDxÐMäÓRb¼ºóa‘ù°KD$÷÷«R²ÄŒ›ßgÙûòµ8üžúHÅ\‘ÄM§Í"Ùc5êÉ¢x’í`ÉYTÀ1Z‘B×6]˜,"²§Ø‡ò&yén×§žddüü|ÕxìÕ˜x‡¦õϺŸ *ByóÿTW%1ÞîE骴ü¿LªòÿéªþƒûÜ’˜Î¸òž{¨S•µ¯Ä¯haémôi$þúíçŽ9ÎúwÁ½9gí4*Ûî}\k]µñÞìé–6çeH˜òxàÀ¦×³âMŒ²1<³úD›W ðرªSxHRUCûZô‰…«ïÎÇE>Áé’H´+Ÿe°ÒÔ4‹HÌã*`™ÈPÌÅà}Ó+ewÿ8ŒZ·¥ïF«ªZš7âUšœÉPŠÜw \)ô9KlŠØX{·à®s'Lòïj]1™EfÃQ‘1t9O99©æÃ1¨ãe¨7‰xp~A¯¢ýMÄ\žßoÈ‘·ŠP#—ìÖE­‚Êày !oÞ17|k¤Ö|˜?ÂlB£%v6Ÿô†ŒÞ˜ßTëdPæß¸©Á Ôø&܈^ú"†Ü£Œ>q)˜í¤»W§é9£zŽ® ?uûhS´þqpè‡×™EDv¡ÍyÙZï]–Md#UR#§ýš¹"’ÓC ê)ÒÙ{ß¿­3dSÁ¢•Üpµ½’¹‰áw÷³ÃȽq83Ô N«Îm˜ô˜JJÁ¼«Íþ}bD“ãL<=Z—qö>>JEãg½ª¬û(ÊC© Iþ TÍ<¦|™²DfúCÕª»´àøÇaTngþm¢H°Bâüˆf X "káÊKtœòÐIê©S×¹A©""yyµø¢¢ÜïTû±îH–* aùç jÃùHt8½ ¹æH•¶ë®ê$~¶«õ7W» ýø^^øp[W‰Ús®2ñͯÎ4ˆÈTëã¾Ø/’Q±×‘Dµ›I$ãƒNç¡VQöҽļyØp•`O…Bó‹›,n© ’éØ=Õ¢µó„ÚgEDb÷žþgpK„ò¦°±Nõ½õ—'¸ ¶Üïy°t=5Àûß¹ž¨ü/œ™ï&函öélùQ•œþŸÂ-" §R£G®ëèÿtUÿÁýîpÿÿ¦’=3']_‘ò‘ë#÷ìwK¨ûü#Ó™[‚œGhìzAú#÷1õýjTÒ[·"g¥…¨g!¸-a×#·-l„à†ú³éQ"»sqßÿ¸é ™eÜÇ8NOˆ¼¸uÿþ‹tÛä΢‚bÿbð¦:rÉIe -¹p/®9¼[a0¯Y£©Û¢IËÀÛçm½8<îN6’Äšé9î*ÞáÃÐ3Ïö*T ÇA×o›y,¹Sa_YÊø®„Mö¶5µÐ%ý¢:o—½½šî8þá÷”‰8ö+‘SUêð–[-¯.k8¡Ç‹qE?P¾;#ïP 35{zåàëQáž­÷½Õ}ÓgbÇ0¯æLc‘ żüN%R Γ´ªäÁÕ‹¢Z0Ä~­3sòئQE=ÔÅñŽ‚Õ Bá$úJ\óàÿ¥3||àü˜ÆÒ‹#óu6.j·çÚŒmwØ&\¡&;®Ã±3%lf6 î|žzèÍ"ž›ƒ]øª"\.È+4ÐQ7·DÍÌæá‹‹?L¥¡]Æþ¾{ha#â¹"Bfu‚Õ·-õ>U§ÛNÌ)Q­ûÐ区:FÒþ™Ï(ï7PÔòJèmKÆ"1õE·µ™³sƒ!@£¤ó£C5·LÀÊJâQÄQ¨ ûvÅ>ÅL¹ŒH^ Ü”söŽ(`1Àa‹w¢G•f-œ+\¾úÍE€y©aú·¸ÖÅÄ·í™pÀkDÛ6Z€:+ .?s_÷+B¥qâ6p³¢èLå$ÔJÎBd;~øJ4Ô_šòq-÷u•ÞÀÁÁ&_Ýºß ¶1ÍnIGM-¼ØE`Õ»•œQ8ਇ¤¤}{*»µˆ]ßÄ&€®w“Nc±|óºQ… tÔGݦÔ=¸ušÀá;ÆhÐ/\C §ö¡ýu~zÏÁ=Êæ¾ÒìN™ß|’„£©ÿþ„ù–.ç‘p;åúsÆ É„Ýi*Þ…È"óø´¬ê‚ÎßæWíÝ÷'Cô¾!@Ã|lQìƒ0T¿M±åùU¾£¯ˆ¬Ç¿[ã]€!·–Áu:ïþ³bŠ„–µ5áoÄÁNަg¨ì¢Û_ç/6¸×­ý2 ®•z­ƒÕÄ|±îÉ–gà·Ž”§€²’’p*ºÔM¨kqŒP`?-KÜÈüÝ8‘xs× ¯ìÄý&»ë/ŽEHÕ†øÛù\&LGŽ ?L“W ÚÚÈÒ§sbc9U0ÃÀ)•<À¡ð³¸Ü t >ªp&§zh ‚«·Á㓌Ù×\ƒ½K•­¨NpÔ»õæZxaf(J/QôxNt}–cøÃnš—$M¤ýââZ×ǵ¿«£·:'â÷e—u]ô¶ÞOE}‹T{;¾öCËJ\¡¶+lehI‚kpÖ›¦¸£yc²]D׋Tõ*bV/ú öTêûuÕ(ic·æ™ ZèUƒÆ5:É/Á1˧~ƒ¡ÁŒ†·<û;ÎE»ìe„S2“Êz”$¸qçržšv‡|¸%nÄIºyŒÙµË> Š'àUþîz )ÍÚĸ““ˆ~9 _›jº%Æ­’–ÒA ®¥`ïÃ7Ü3׬SlEî8oI–cçB—6þL³ž·Y†ZBtj=G8WI‚«ƒÖø;“|‡ mƒ uæ­Ð|QdæÌýêVp3÷WgÓ/Üt,r`mCØ<…Ò‘ê‚Ç{'uLeZÑòÒ1½w 8KK †»:ª£¸¦ªø€®´¾­²ò †6ÈŸÌ>xñê?½Màè]”ÂqãÕ»;²¸àÚ´*geÖ*z WÛñЗ¿På§ÛÉÂ4®Ò¢²*Ip÷[xú’z»L oÐÛNXxöÄuwÏœo¯©ÄW¤ÁÍùŶ Üö†áq®“í§·ÿÝy:8Êã@ þp–Š” ©jU– Ëœ]·S³º¯â½šKaàé¦J@µ’wu·):‹ôy·éÌémÿàõ¿ãArš_8L}5ÄÞåzÅÓ™3kk–Ï´5j‘f܈Œžñw¾þyÚ«áŽ@ôÝÍTÅÔºn³w¤Î¡©UÚRÚ°Æ~ü·j³œnÀeê‘% .0>½¶ÁèÂ@ÿÌÁÂëí× ’1Ãñö_Än(™š@8í *ú®6÷›âuÎW¼¼&3ý€zv›I}¡Æ³ÄOQgŠHM.‹,¥JÉrWþ+R{þè:*ÀüêÓáv[ÜÇ—£<ôo–¸0WH>ãQNÝI0}ùä¼W–tîýF\ ºŽåu†lTÔW‰ã;ž£ËÑ7ÿn‰h§š³…ŸQ’¤ªcpPós•~q&hú[Ôàˆ… «VÐÝ«Çq房ýÙS£iœå) pJÔØµ¤Ø0à‘”Ùv3i‡˜PÒÔçD¤/+D¤6½Kd¨±û€YàõÊ¥Ú9ê–¿múGÝÅìDã×NѤdufË–n7ÀŽL7pìÕ‹¬ô¬t¿Íen®@8åIÿçK"Ü×é´‰œM–8¬®÷àïvš^Ž€~×S¡S©’53[!ÆŸÜ{I‰Wp2s8— Üí΢x[w55ÛV?ÇáyCﵤØT4UrF]úëq÷:³â›™ca,<.ÜkQ+¬õïvŽ”Ì 0ËÑ äIHïs¯ž}>κ Ïô ‚þÎ\ºëÇŠµ3¯¡¶·7¦À©'·ßÛ‡náDEµÄ/úîÍý¶uiÖcÌ ÛqœÆC †º»>¿×²Ý‚[«ÑY’{ècw‰ ]šåßópYD¤'€WÌ} ·Ìoü ¶ÜZ ØúðCül…*}C`mz ‘íðŒídä{HöGRª ’&3Îíœ7î©n‡‹‘ycL³u>‰a T~‡,Ëc]ç÷g~þ}UÏX°Q›“š ñ_Û¿l•—ÚQ“’`<£'=1<^á|dm+¾©êb,_ùPjc­œù,zá§ðj³aÿŸë2Çv.4Ådþæì{!%Ð58µŒ6+D¼ã£¯é£bõÞ>>Y‰×S³R’´®éME¾m@Qs‹qfÞOù@¥ñï­½pKÐ¥Î$¼õnäø‚Qic;†ßõ«²Çì–©Áò¿ÚìçìVª|™@¯àº‰ƒ÷y#ÜßQÌ=o~‘¾ø- þº•aѾÿtqÄ—åÇÐß…8çh”ª;ç™ðuÉ6éBƒƒ}“6Õ» ¸ø{9z²|êg©u~!ælȼ2Z]²6/Aí•“ål¬`ÇYñ*[„#À™ªb'ì”\#”ÿ†RSDd3ŽÖ3¥Z£¹zÈfØ?tꦈ˜$)ñFìæ¥¹7o$Þ[-ÞÃ+·XfæY„Šø1ʺ犈, ¼õþ¯ð­¬U£¶?GÓäAýûZ‹#°¡X„È""1[é ‚9Då³Ú…0nã1÷±ó…Sð¸?K‚åÔ¸^#냖ªjC:®Ê:@'–×±¦|žäðe>îFz·gmä9CÎímƒ+©“,?Ü"M?˜©ª¼WêÏ¡™¶ÂÌ]„«°W~šÊ·U3ÍK—†ì§Vft·+ÐÀÉÌô$M„®¾?_u‚>Àµœ.Ç9+J›èêmk´ôxcòÈä×ÌtsrªãE¶fºÇÌÜé—¹ß'ÛL}a¾I³?&ÏM]x/áƒÀS·›Ù·ïƒIý´ 9΀Q y×Φ]wHʼ®KrKÕ!kLΖ1nA¥&"I×Σ7ËBôü/©` h¢m¸é7êž`Îâ—nŽß°d;á§à[ÝÜÔš$IWР§ÐŸý»ûGf§VÚaYcMä¨Ã\3J9FÅ&œ×ùjÑ;êñíí¨'Å÷i™äˆƒ§Öp±Q1À5+ÛY›‹j½Mû º${êӺ經þÉ'~:ü¤ÿúûÌðǵjj}Ðd˜¡ô (]×\>P—ík“¾®ªuWª$ff•«m½”crH°N«à[¥ÛÁ5dCë®úè0´3ì:aqx !úIJnö3Qà¶K4°aø–ývÎYdH]½¨Y#µzŽAíá\Ó­»¹M‰7Ÿ³àV*4“XnÚæBçgÀÂ{Ã7¡·(_{—é”…FÓŽ—Í,x}ëÖ,ß~ÓöíT¨~^vçÊ>ñx5evñM¸ñÏ«^MYvŒ~!Zcfð0vöÂF òEÃ-ïö¹ú‘Ñ^ÀÕ“–IƼelcŸÇ_8”ãRæ?¯ƒ@e /mn¼4’Hµ~¸–aFkÞ3¯¬žÐ»ªýÞȰgÁ“Íâ¨!êŸ×ÁÓèüˆ}ðp“¡² Ê_åƒùìµÌ°…’W÷ Ï8ô]w/Ë¿òÄ ×þy”ƒMËîõ à>gƒ}I-ÍQëâJ|’7þ·W!5¶úæ…í >}ÞŸr.iÄ/õnÚÍìÿ¼KgÅÍ ²\ýëÌ Ó¡Úy|@4 î¹#iQŒ|ædN‡ÏÐýÞ÷¯3—z+iGø³úL(]|2àž÷»Õˆ*®-Ð0„Dï¡e‡»è‡l¯txÌ18½ñ„{>‰¨: ÅW7’È=:óÝOÿõßdÎV~äÜ;Z5ú£Å—anÌ+[ž?Þeþt+K²›3‘6žÒ?O•¤ÕèÁÂu«Ë²„±˜^ÉZ—!#’#ó=´á@œ¦†]îS™ô8õàåîGÝB¶ÇQœ­+’ýÕw6,#SàS_ZÞ—O¤Z1GðuùRøºý—R|3¯åS@µü $'Ý¡ Î$µÅ7U)=¨¶BM͉Ë!!ÇI‡½æÝÈ:±”j5ñ²7ùFï!ô Ô…¸¯.K|¦W1õ}j‹*}0­{AúrCËo f˜]nBˆç>È@%H÷*6!²È´?ŽgÂ'8ê¿E=(JAä}P»Éá÷çµ>Ñì!´îOà?ñ|¾¢cg )ÒÎ]²÷çµû`þÃà”tÔµ¿8?-ŸaâÜróD]dÛ¢ä'÷û´Ñ,ˆ#Å?3gv}6vNŽ*¸Ñq;ƒ‹½‹8:Àóý‘"ihçòQ\cרs+ê)ï(_7Jî˜nŽHÏIˆLvtùîÕ…á¾½º ÇŠî¼­Ð-lÛ•KÑ7“®¤k¬::…ö¸é­e…PÝ·º9’™åZÌp¿ÖX6óu[•2æTL>娭®ÙÉԯݤ„·œWöuMŸctöðBà’íköòÕzÅ»áœèc§).¸=N* PçS_{$GML©vêë¸}- =`¾£™—“•L”šâãß¹ïïj[Ìp/‚×ÂeË¿Vèz[fXh<·žYq½(Yò™ËW¯\K¾žPÔ±(Þ¯G8´ñ4v:m—ÐânÝM¦ãß›Þ( ·ìÙÆñ¡fâÜm,ÐøJ—¬èðé²Â 6¢¯³9%@WúRpùÀàJÁ¡jãEµÎlr$êb¤‡s™ìÐä-Ý‹îä×"×÷¯¿îÖ@âåï?q!ãcmák@?ÙYë©IMKJ¸t%Ýl“Šte=*¶ñr лUô-bÕZO ¨m2á_­ø·¯¬Ûôü†AQ/Û«WÑèíO¦õó2…U¬VßÇã½T¹¸àª—>·a1êïîú.sÌe|ˆË®]½a›jV‰èžj\l[ŸÕŸnJÝò®ó5^etÍp ¼¿µ(¾‘vâÄ¿•¿÷Î𽵓ñH¥G ®–zD°^€{wMú7%Õ‡{!¸«ä‘»mª2>”·>Ê­k6€©$Õ.ýÓk÷­,}A¸¹¿¿TεŒW#sÉA›Ðþ­s·Ô:ùoUЫtßësª±‘±‹\ ÉGöäñh‹{4§lÉj\ô­é‡]ËØÿŸ·eѾ8“GãuîºôNÛ\Õîž©qyšÇ,pÙìNÞnÏåg{ÀÞ ï”©Žå[¤Ç—q¶HÛ’t-Ë#§–ÓßE™š“lð &cÊÜlÔ¡ã‡Øë,,n™'ç2·VþåÄïæ\Hÿ³ÿ9sž×â³b3Ó}“#¾VŽ¿¹0Ez6Œ¦ÓWÃιéB^I@½ÿyˆHk÷>„Ÿ è߇ý""3ëDˆÈÊ>àÿÖ‘¨1¡:4º -ÆÝ˜[§²òù'xÐwp¯YDDnÎ>_àÆò7ÖŠÈojùkï<³åxNí—""§_s]ä%íR%s=ší4øðσ}¨# <^RÚÑYùúÕ,1¶x¾/ ê—u±´ZQ…Ô“KðÊŒ×>Ze¡Ê#"É¿m9ž,2u\ZÇ)põœwí84+ŸU]˦}ØnOùü¦6P9KDpn;b•ٮƚ’i•ä^‘é0àrÆ\œÎ‹,v¿óæ`ªšDDÖÃAûê|…ëêL‘ìPÞÙQ@ÒÖŠ$«ïÊŽðzf%@Øÿ£²’íʰs<›Œ—¹Üµr|FOÊi.”Yõ$tîI‹³W®]ú’ Ðö™úãº~Ðç?µÛâS}]D^T~”3-æ‹H‚ð´DBý‰Ÿü¼ÿèM™@©9 c¬ìQSVC¼©08^D:Ѳ@}ÖPQD.vBw5×—A½ë¡zù» ›ÞA,b®HË5zIïBiFÇË©""ò:nƢᎤaAc’QÇI‘¾ô’e”ý­×Ï"óÔø*Á´EšÓǬB¨í:ðýûiëD䄎îCú·@µ¿Ná"oÒô³ba™%÷›4‘ÞT06¡[Î ˜šÑ¶Š\(¬Zäµ$·8-©M#Y„.CDžQŒ¤]Y%"ÙaÌ—±þNsÓü,"Èöš úîúˆ¹4_dkh9ið ¥uK‹ˆÈrCc‘¼KÎ4Í\ç@õ%j~+X›x\ iS=Áá…¼¡´‘…pCä$lQ>ÝŒ7ÁÙ"MÀß?à[±¬Ûn» ¹¬p1;³ÞÒ;TQ¯£2šq"•2òŸÙå§ÿöÃ/7Dd uEDÌNœP&JÕI‘ʼ™­xQDdNz倌 v84tsUŒ,Ô¾™«cvö5‹¤“ÇSYDf¡Õ‹ÌF{SDd4ßžk/¦‹ °.i*j´ìÐ{fÊ-p¿‡KËÏÓòÆÓ¿]WrX#""gU¯¨†\ FSíÃuöϼ xî9Ž!Šot†ÚÏèϪJ`÷ŒM N›ED¾ÃÑhù›oGX/24» ¦]‘ÔFTþw“ȇ¸E:&""Ø$¹Ã¡R’ô¦ñ¬7'œ1;ó¹9vÇ£+ô9ÓÕD‹ÈqœR%„å""ÒŸý©¨Ú|¥œŸdtæhaóÓkûù€ú¹j,9H âD«ü´ëp3Ϻ¸8æ*g €G—Ïöé9>”—DÒÝi]m–“…¶±ˆ?Ñœ™H HޝYV¹•"2Ï™Qò Ó,]Ö•7íËÈ×D¦âQèÔ“¨ÝÁÑ“º‘¿öþÜ­éuœ6[KÈQW-¬Ë }ë-0¯ùTØ@ûÜdòŸQ …$Tž–¥×gpi5Îvú€Øˆ àÞå—[¶j.ž¾XʶðN9S‹ëT†Ìd"6$í¼~é*¼pòË“dâ KF—ßvÓIŸë᪅l—|Å«)ŽôO<¯^;·Ðv Ù5Ñ87ú®\>æ%ÝÒÝÀDN‚E¾¸Œ»KÑ­'Û¹‰.Øü›f’>O«„ˆ>®[kžöÜ’Ü3Ï'µõ±ÐvΣ¢ig· 1""µ¥XÆ'ŠHÞĆ5FäˆÈ™Ï^öúøÁ¡ˆˆœŸØ¬¶UyõíGE3útÛ_X$Z!°i}4Ì‘v×DUwâÇ^xv¶MbPqJÙÓ©Ñ8®§›ñYž M3DÄìÇH›8çjUÌÈ·Ô“)ŒUÔFÃä+ª\Îôfº^r]X”wî}\Ñž•SZÔMUà=Ó èñz¢Nk ®óDä¼d9î‘”#þèÖÌ2}]YßG P:Üky—A¿‹ˆCAåÉ+Y2šî""7†ãügá^QM©ßàͬŸyóÞÓ¼œ¥…É"b*­i *Gh=aYœ}4°×e ³EÄ4ŠÆÒ‰e"ÏÑUä(ê˜|¸ï²T¢›1HÞÂ>¥_¯£"b(MíJ…zƒH,¨vˆˆ|ÀTÉë½Þj0¯§2£hte†›å@S¹¥¢ÁNsBUµš)›ÕâQœO‹HB™jKb_e¢È<%P˜ÑÓy*¥L·†#")>P6UD~âùQt¿ øDŠHYv~ñݲëE©-Íd¢¼Šc×¾UË@¹LWß<1@ Y6 ¾‘÷yU¤È6ÜÒ'¢º"r¶ÐID6Ð9ïcFî®L©«rMMŒ"ò6sd2º™f™…ú•]1)Éy"’è…ÓS§l/ÿiÐA‰Â]/ïSfÏ•hl‘Ì‘÷yUd3Õ/X9c¤ªÊ:4sÛ–\ æó”˜#Û‚¢ÿ9Ä“_Ðüa¥h•)æÒŽi"b*ÃéBp?ÛQž¥v íÔ¢²U¬üŠ&'nmÇS(»$Nå˜#"yõAuIäeÕzsÖJL)Þ’L-ðƒˆ¼Î·»4,‘M8ëóc6¸®} ¯©Íb9çˆuÓ4›!"¶]`yÅgÍÍ>N¶§WU5^®,9̤4ü“¸0\¤ª*RD¤º*¢à‚O¥O2æÓ7(õ™õ[d)Þ–þ£Ãõ–N öà::U^ ërYïÖ)ü0³f~^èMåû01 ä~i꣘Κ8ç ûöóÜgôýÁÏv¯äâ¨;u¦Ü3@µ¶¸o‚;¤Xó8+°Ì…L$¯<ï1úd¨¢ÿÙN5jé´ˆù‡psPž–‚€›8É„Ï|þfK¼#pw lù†;»É$(Ÿa¸71  éa/¼=rý ,ïw#¦Šz½™?ÿÒz©öÙª›_½C:lgˆ2CyÖ9ð ÌNp„³W¹¾Ày–Å{gÍIsw @‹_­^á˜ÁŒÃÇJU;üqMÐõvñ”Gº™ñ¦sSO4dò‡G³ßѤK÷gŸÜ«Ó'äª òg$[UÀ@õKœ/–¨ÕGˆ³©Ž´p€CêØ,6tRiujgÔæ½9[ä)Õz\³ò.¦àw¦~ò²oyI]mÖÒÔpÐÜ2²$gD«#˜j2Q€dÛ¼)RñLF¿Ü$Ý@ý[<réü?€z‡/—w wqÀ7ºÕÇ Jã&`Îð+17Òdxúd‘Mr¶ú¤éº-(…0­JéÚ]æ³LP‡j•ï?L{có*ê¥Q_9|2²™ù@ÆdÎå&Góþišˆäøõ¡‡v\K¼žš°ÅÝl ßO9ò,§nÖô±}b \§4@!Eñêã-Ñ›ºòU|ê6O¢Ô•¸ï?G.—ù˜ºÐ7r•ÄœL¥u³•÷¶âzÄuÛyjŒˆ¾ @Vzz’²|ï¢MwÞþӫ݉¬Ê>A kWìÃoéTÆí#&ï¾D’­£ÆUUÆ«´²…¯×ªh|[©Üm^27¨†¿òÚðã*Ûº¸7d+±„¯£5$XÜ2;T¿0æÌ²ŸÆxRE•x\ Ó«ÀU`WN•‚]'Ïœ©ßìÝíï‚Tr4Çv“–ˆxuXXEßÇÛÆ]!0M]îQíÝÕáûn£²   GÃxî‹“r‚šª³£*ü^[k¼jUÔŒÎkÓ{ÚLù‰´çqÝÊ©0Ñc‚åbÄ2œ!QqXM#=G½æÊ±tï –AV~€âåëüM—Ù³©1‚'Z~ðÚü›d»Oßü¤#G–Ð)Aƒ"š¾<àblÚ—%t‹à½=´”ìÅ ñúU¤<'c9¹·Þ0&ˆÈ*ŒÊsã%³Hü»Ú_Ÿã‘ô,É­ç-"YUì½À,‘ÞÔüîÇž|hÛÜUxú²ÈŒ'“ED†1æ*­ÕåpË6ogUc‹H”¸ÚbEPÏ‘Ê"•ŸÞm{R¾ê’|«èþf×X›jnm¬K{ݸÕwÖE䘺æî<Å -»_KãíFH„L†¦/5u§«é]ë!Ýújœï Ždx†®ùº³©ô¹î Ð-ÿ ã›9"’«l«º/¶ç »Uï–k¼E5ÚTD¤“ï¾B*D£Ø6MU¶Æ|Mäÿ¯­æbe4ߪ<²ãŠèŸ›l{¼+³EDb®1*aˆj]•Q¶šÖ}T¨kwôžJÙÛÁ­Â'¹Eï™~ûTL•«9âW¯KÏÖƒßO»»ä ‘å;þþÞS%X´ô .uvîP»ZÑftÈB#+×$•¯øª džœFU.ØÛÔúuÕ´÷*Îkc¯;T´”†;ê#=¯;ûjŠ]5‘[çï`ŒI ²rèÿìþXWÏ’¬šûñúÜMÊ™ñàà&]{pOîÿÉínݼ-+7çñ§›êµßX´‘§@ ËôgZoéŸþ ô±«Bqµ+<ë«ü­ô*Õ4¹R¾¡)sø “HüŸ‘´‘w€/ER=F˜DdÇ÷–§ç¢‘‰H?6ÄŸ4‹$o\d¯Ü> ÷Ú"ªt‹R ]Xi¾SPÐÿYàôËwÈvÀAaÝx^¡i~@GÛ½T‘=pú^‘Á¼'7=‚EŽºz¿@_‘%´üs8|.’ì V–`…›/ôლøUsÜò5‹ÕÀÔ[ ô7ÑfØÿ5ïçÚVÀáFጿ„œ°þÁÄŸ~¼ã‡¹8l•œ¡ ŠD›Ó€ÚxUd^F¹écQ Šˆ,¥´ˆd;²rMEä7*™¶CÃTs#æœÃß,=9#ò®W%N…ÛU…ÎÚÞwzL¾ŠÈæ4;ÐV|Œ~!p«…6ÕBвYZ$… [26d¤õç`*YÍRšX™D™¢Ðæu§|ú@z‹>LÑ=#ä†bÇÏ,"øq E$ÏŸˆ@ǪÆFƒJ—kôöËó_†çe¨ VŽˆœWˆ VHòø·ùõÊ]ù’ö‹ÔÓ-†ËбáàhºIãd²S¦ξºyò…§Usjþ±±}&¡Ð>a­Û.Ì•4(J?ç3R‹ñ2Ÿ¹æÁ¼üÛø˜Ed_ •ˆÈ@6ÿ'¨Ù*R™«‘*×§[úÄ´ÀË"rM‰ÈW”³–´ àlÁ—§8ª»DÞãh[ljH”'"F WlÙ*ãûêÆl‘¦ »¹±Œâ!|$sëS´…!j¾µ>T)¿ÙíÒû¨ÖÈyuéóæÊÊ>j uöcÈéÍ©o bÜ6…@ú.‹Wòê,--Í"µˆZe¡8evW½‚f›ˆ^ë£À]…³ur9¨öœÝ}Åîåóé#u:PÇ(ÒxÞ,bÔ‘¿¥»5›KŒ¤²í³S,º§ €¬§b G €a° Ác²÷ÌÈ-1¸?âÕøÄ cªž›]nËa ~¿êPð´gT@øV4^‡åW{hr­jÎô Ÿ¼Óå¬{ØÈ´¼ºj&¶6ôÎÔpÎ"D†²ƒ(øê:§¯ñïÍOó²S’Y`1fÈÒß#i<† “0ÚÅaõ~ŒàÎ;PÉUm!$F¸1Î@*9F£ž¤Ú÷%™øº°×'e'Se#iù; ¹î;Z˜OË+ß³±Ï—û›E^ÇW?x·zë‰.~”<ˆ¢"k‰^é[­7bñ>œmùôþHÐ-òŠz­œrv•ˆÌ¤‘„¨uŒ‘v4U†C~ÚE[ÅR©R) æ$ù†¦bl Ÿ”¶g@?Åó–î¤Xï6SË…5ã¡on†šCZ×´§ðŽzŸQ¿Ù„±4­•¾ÿñjVú·î¤ˆ¤;¦x„õ±úö8[­èM8-"_2Dfâø”»j…ÈRínžûŸZL6s ±T6‰ÈбÒ"UmA©n;‡n1Gÿå#E&Äí ˆédj$=±­ ]e5DâÊ[&,kdáÙJgÞé¡ÞêÍ2:‚oátfük0îOˆ¶-Du##/{µ*:eìék)¥Ìc²ìs­9n£^äÜû«j šÍ*Ú¼ô%YÏ“’ÛtsEäfõOžþ_±Žc¹‚*ZDÌÃöž "Œæä28:“ú + <¥è»BplÓΔ…æ}\õ?â-"';p8=•?âqEd ƒM®œS/ð€pZÎy«÷å/ícmôLËà#¯Šˆñ ~¹–—¿E8lçcsü¼ˆÈ%>ÉYõÝéÂsÝ“,µ÷ Z/"bè†ã&ë¡>°W¶iáµ+åºZ,µ,rÕÖR¥Êð…|¿g*&¹\—fvbþXÅ>-’íM礥ºÒÑRŠ-"ÆÑ‹à°È1šµ—áù¾˜=ýƒzìRœpÔO\±u‘¦""Fw퉿$î¸ÍVäè/vZ=…Ár­Ý.ÛŽh*¸Æ‹ü¦©‘»] P¦ÅÓo^·ñt¾÷¿ýy£‚²DD >¼×Õ²P%ô|Õî-S)c)jPá¸V$P±7/ÑPy=ü!"3¤ç{tx&^ü°wŸÏ¶åKÞ×fß1ÝpŸ6’YÑ·R@o”ÖÌ9/[<Ê>õþþ"8Ÿ&Kž clŸ·Šø°Çº~`ý9[ƒÏ©hq ùZµ7ª”ÿ%±°†J[Ð:®*vZ“¢š»ÑÀfFs¸³–á³÷Žþõá‹×¶ôæÌY_A±ùy¨3]lŠ“•?¨Ì‚*U/Uò5‘æóÕÿź¬ÿ¯ÿÁýàû ’Yu/”Þ\Gæ%ë®û9h}êv·NƒråB©ª¢¯E9"$_Ë1>xo'ˆêSþ"òž^.öþÓâs:êF‰|j3m·P^W»Õß7Þê¿{KÊ>ûãØö> ýS$®.P}ä«·åkߨ–^ãOý[¿rñ¯™¼.Q…®œwزAø4Od.[DÄñW•ñVuž"_á2yìÔq½C –QD6{[¹Uùp? ͯì¾\97ºCPÓn ­œƒv™’×VæªÖ«DD.–×,Ÿµ;Vi!]q— †Só•ºMùØÚiy’±o| •Åï9Ú™!ßNw¦\žÄVfÖA“HêЉ""éO”þUD$}R‡.=Ÿž~Ð,áÞ”’öÚ)CÂh(²U‘o%ë5ˆ9àL……I‡ªTÈ̇›¢e†ÈÛãEDÖjfHô3¶žîÙwúw+Ú)²Íæ´S :hà"¹µñ÷Å""Y íä&`¥æmÝø‹IDÖ» ¶«NWE¬ sÁÙßâú‹ˆÈeË¿ƒA©"óᔚÏdŸâÑž^‡ã"+­‡8­‘,Ú¥”É×~÷ HäOeC'™ãi(bªIƒY4×^ˆ å{Ù‰êWÇôm¾@Ù§}øÍÞ9ùeé¢DDö©X$ò5³¿,­(¢^¦É‘¬jLݼٓ]^x*TgDÎxPç·z¸'ȹёŠ.g¸2š,‹¦ÿü¾³?_ú*î®zwf ÁáñJ𜬲|Ãè&ÉZ5ίNóTØÀX9‰*Saº“OÉr?Ñß”ÚEÞÇ+áV·©0æJ$”Šù€OÚ¼TÙ—ˆÄåë9$"ÙO𜈩;D.…à’"QE-›‘BÜÅ‘—ÅМñ²Ð…ú†6éEä5^±¸NÿâÝ÷*óŠÈ»–ãqóòj-“EäJ)‡•Zþó»°a$XtlC®xS߯ÎG›""ò6ª|âü'pV.¢»ºé ?œ†EÒ¼ÕûŠðëÄ\Ér†!"ï24kkÒ.Yý¤&tà–=ӀџL ãéL‘42‹Èa óeO(Ÿ*¾UA¦Ü¸ËÏ(+ñ;*"ëÔ¬o3Dä3ëQDDZ0Id”…DÞvÀ+¦„=5 _¦²ò F­D«ôÕ_i<œ œü-D*úkÇü‹+ \”æžT*ݽð|¢ô—ãžñã¸IwiKf€×¢‰ˆ`×e 9,*Í¡‚ßxVEöõ+¥â¿{~-ÊHw?cù˜É4ZhþSï1­>еÍÖãðÝ( þÖø\‚@Ç¥?Kg8ë‰Ä™³n9„,êþ)Oô?´¡ Z‹pน×È;¡’¬r]àýŠ'âs.S)?‹€?ÎŽz`âÓeðlÌ/rˆÑ©“µ–V"²‰^"’êà9ê©z!ÛDDżBêüꄪ¼ôÎÜ´}ºZ(žS¨Ï£Rt·/1³;ð›Èr+¡IDD±Pd¼­U¤:j΃–o”[z å­ú‘æ¸æZkAã¨|ºÝ²ê†ˆ'UA5ð’ˆl¢æ-j]5`D+ºÙ®'¿r#(…X›Wæ‹•Ç.·ƒZ0i=@D2 '#Ä-¤?æ„O»SìÌA˜wSÑßb¸Ò‚û­e&[Db·'1abb¢^1)Œ>-՜ȋÎwnŽP¥EݧÚ4Gku™ŽÞnfïÓ\ßÒàÇ‚I޲¸ÚÿŒì'Ì¥(™Ù\váUqïe‘¨_;Ú]Ŭ)Ä>ù®Žãß°ÿ˜Q—çŽW>CȈ"¹]ÅA¬|åëÔOæ)Õ‰?Ð’i÷ÞtjA½–ýº/öFc<)M›•.Ì`*O,Àzºø“xàòªN}ÕiF¨¾ûøÊmݨ™‚CFܦ%U°‡«!ÑjÒ?<ý†ÑGFRàÐûä£F¥3÷ä'™ÌRÑ7AM€ˆœä5™e7Ò ¶ËÊJðø Ì&‹¸€j &;«˜â“öˆ1…Jc~êF#ÓT´‹@âÈ‘î½ëzŠå´ôH\cç˜ 5_)Vçã¹j,Az Ù±ÒrˆÀ(eV³IiÀ!YWœCðµã®óæy¥3«pà"Ž8,p6S¸€+Т٫€Üܺ3­79©^1«*4I‘¬häª[/"ò›jW¨\ä‡PÅ ‘áÌÓ†|JÅ”Ë"ë6Y7.[n&vüZ?¦òÞ,òu• 1#<]$²]l“iŠ áýçÇ,øyÛîCöôK=m.™9"æ¯Ý°x‘Š/+BiÜ5ã‡^@µ4‹ö1®&°ÙNª-"OP*›%rÓºÉI1Ÿ³øâí/ 5,–Šã[¹ikÌ1ŠˆXó¶ÅÕ¦X;•Maú±4Ï_/"*Š},¿£åM?²ý¬2]åò#ýlÈä|ÃirWÊäWèè[Æ»*<ã·Åv¥ Ó N¾uî/ H;r"å>úÞÿ ®èèæÏü§‰üO5÷Óÿ !E¯Ø¤¿"IEND®B`‚bitlbee-3.2.1/query.h0000644000175000017500000000336512245474076014043 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ /* Questions to the user (mainly authorization requests from IM) */ /* 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 _QUERY_H #define _QUERY_H typedef void (*query_callback) ( void *data ); typedef struct query { struct im_connection *ic; char *question; query_callback yes, no, free; void *data; struct query *next; } query_t; query_t *query_add( irc_t *irc, struct im_connection *ic, char *question, query_callback yes, query_callback no, query_callback free, void *data ); void query_del( irc_t *irc, query_t *q ); void query_del_by_conn( irc_t *irc, struct im_connection *ic ); void query_answer( irc_t *irc, query_t *q, int ans ); #endif bitlbee-3.2.1/irc_send.c0000644000175000017500000003022412245474076014451 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* The IRC-based UI - Sending responses to commands/etc. */ /* 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" void irc_send_num( irc_t *irc, int code, char *format, ... ) { char text[IRC_MAX_LINE]; va_list params; va_start( params, format ); g_vsnprintf( text, IRC_MAX_LINE, format, params ); va_end( params ); irc_write( irc, ":%s %03d %s %s", irc->root->host, code, irc->user->nick ? : "*", text ); } void irc_send_login( irc_t *irc ) { irc_send_num( irc, 1, ":Welcome to the %s gateway, %s", PACKAGE, irc->user->nick ); irc_send_num( irc, 2, ":Host %s is running %s %s %s/%s.", irc->root->host, PACKAGE, BITLBEE_VERSION, ARCH, CPU ); irc_send_num( irc, 3, ":%s", IRCD_INFO ); irc_send_num( irc, 4, "%s %s %s %s", irc->root->host, BITLBEE_VERSION, UMODES UMODES_PRIV, CMODES ); irc_send_num( irc, 5, "PREFIX=(ohv)@%%+ CHANTYPES=%s CHANMODES=,,,%s NICKLEN=%d CHANNELLEN=%d " "NETWORK=BitlBee SAFELIST CASEMAPPING=rfc1459 MAXTARGETS=1 WATCH=128 " "FLOOD=0/9999 :are supported by this server", CTYPES, CMODES, MAX_NICK_LENGTH - 1, MAX_NICK_LENGTH - 1 ); irc_send_motd( irc ); } void irc_send_motd( irc_t *irc ) { char motd[2048]; size_t len; int fd; fd = open( global.conf->motdfile, O_RDONLY ); if( fd == -1 || ( len = read( fd, motd, sizeof( motd ) - 1 ) ) <= 0 ) { irc_send_num( irc, 422, ":We don't need MOTDs." ); } else { char linebuf[80]; char *add = "", max, *in; in = motd; motd[len] = '\0'; linebuf[79] = len = 0; max = sizeof( linebuf ) - 1; irc_send_num( irc, 375, ":- %s Message Of The Day - ", irc->root->host ); while( ( linebuf[len] = *(in++) ) ) { if( linebuf[len] == '\n' || len == max ) { linebuf[len] = 0; irc_send_num( irc, 372, ":- %s", linebuf ); len = 0; } else if( linebuf[len] == '%' ) { linebuf[len] = *(in++); if( linebuf[len] == 'h' ) add = irc->root->host; else if( linebuf[len] == 'v' ) add = BITLBEE_VERSION; else if( linebuf[len] == 'n' ) add = irc->user->nick; else if( linebuf[len] == '\0' ) in --; else add = "%"; strncpy( linebuf + len, add, max - len ); while( linebuf[++len] ); } else if( len < max ) { len ++; } } irc_send_num( irc, 376, ":End of MOTD" ); } if( fd != -1 ) close( fd ); } /* Used by some funcs that generate PRIVMSGs to figure out if we're talking to this person in /query or in a control channel. WARNING: callers rely on this returning a pointer at irc->user_nick, not a copy of it. */ const char *irc_user_msgdest( irc_user_t *iu ) { irc_t *irc = iu->irc; irc_channel_t *ic = NULL; if( iu->last_channel ) { if( iu->last_channel->flags & IRC_CHANNEL_JOINED ) ic = iu->last_channel; else ic = irc_channel_with_user( irc, iu ); } if( ic ) return ic->name; else return irc->user->nick; } /* cmd = "PRIVMSG" or "NOTICE" */ static void irc_usermsg_( const char *cmd, irc_user_t *iu, const char *format, va_list params ) { char text[2048]; const char *dst; g_vsnprintf( text, sizeof( text ), format, params ); dst = irc_user_msgdest( iu ); irc_send_msg( iu, cmd, dst, text, NULL ); } void irc_usermsg(irc_user_t *iu, char *format, ... ) { va_list params; va_start( params, format ); irc_usermsg_( "PRIVMSG", iu, format, params ); va_end( params ); } void irc_usernotice(irc_user_t *iu, char *format, ... ) { va_list params; va_start( params, format ); irc_usermsg_( "NOTICE", iu, format, params ); va_end( params ); } void irc_rootmsg( irc_t *irc, char *format, ... ) { va_list params; va_start( params, format ); irc_usermsg_( "PRIVMSG", irc->root, format, params ); va_end( params ); } void irc_send_join( irc_channel_t *ic, irc_user_t *iu ) { irc_t *irc = ic->irc; irc_write( irc, ":%s!%s@%s JOIN :%s", iu->nick, iu->user, iu->host, ic->name ); if( iu == irc->user ) { irc_write( irc, ":%s MODE %s +%s", irc->root->host, ic->name, ic->mode ); irc_send_names( ic ); if( ic->topic && *ic->topic ) irc_send_topic( ic, FALSE ); } } void irc_send_part( irc_channel_t *ic, irc_user_t *iu, const char *reason ) { irc_write( ic->irc, ":%s!%s@%s PART %s :%s", iu->nick, iu->user, iu->host, ic->name, reason ? : "" ); } void irc_send_quit( irc_user_t *iu, const char *reason ) { irc_write( iu->irc, ":%s!%s@%s QUIT :%s", iu->nick, iu->user, iu->host, reason ? : "" ); } void irc_send_kick( irc_channel_t *ic, irc_user_t *iu, irc_user_t *kicker, const char *reason ) { irc_write( ic->irc, ":%s!%s@%s KICK %s %s :%s", kicker->nick, kicker->user, kicker->host, ic->name, iu->nick, reason ? : "" ); } void irc_send_names( irc_channel_t *ic ) { GSList *l; char namelist[385] = ""; /* RFCs say there is no error reply allowed on NAMES, so when the channel is invalid, just give an empty reply. */ for( l = ic->users; l; l = l->next ) { irc_channel_user_t *icu = l->data; irc_user_t *iu = icu->iu; if( strlen( namelist ) + strlen( iu->nick ) > sizeof( namelist ) - 4 ) { irc_send_num( ic->irc, 353, "= %s :%s", ic->name, namelist ); *namelist = 0; } if( icu->flags & IRC_CHANNEL_USER_OP ) strcat( namelist, "@" ); else if( icu->flags & IRC_CHANNEL_USER_HALFOP ) strcat( namelist, "%" ); else if( icu->flags & IRC_CHANNEL_USER_VOICE ) strcat( namelist, "+" ); strcat( namelist, iu->nick ); strcat( namelist, " " ); } if( *namelist ) irc_send_num( ic->irc, 353, "= %s :%s", ic->name, namelist ); irc_send_num( ic->irc, 366, "%s :End of /NAMES list", ic->name ); } void irc_send_topic( irc_channel_t *ic, gboolean topic_change ) { if( topic_change && ic->topic_who ) { irc_write( ic->irc, ":%s TOPIC %s :%s", ic->topic_who, ic->name, ic->topic && *ic->topic ? ic->topic : "" ); } else if( ic->topic ) { irc_send_num( ic->irc, 332, "%s :%s", ic->name, ic->topic ); if( ic->topic_who ) irc_send_num( ic->irc, 333, "%s %s %d", ic->name, ic->topic_who, (int) ic->topic_time ); } else irc_send_num( ic->irc, 331, "%s :No topic for this channel", ic->name ); } void irc_send_whois( irc_user_t *iu ) { irc_t *irc = iu->irc; irc_send_num( irc, 311, "%s %s %s * :%s", iu->nick, iu->user, iu->host, iu->fullname ); if( iu->bu ) { bee_user_t *bu = iu->bu; irc_send_num( irc, 312, "%s %s.%s :%s network", iu->nick, bu->ic->acc->user, bu->ic->acc->server && *bu->ic->acc->server ? bu->ic->acc->server : "", bu->ic->acc->prpl->name ); if( ( bu->status && *bu->status ) || ( bu->status_msg && *bu->status_msg ) ) { int num = bu->flags & BEE_USER_AWAY ? 301 : 320; if( bu->status && bu->status_msg ) irc_send_num( irc, num, "%s :%s (%s)", iu->nick, bu->status, bu->status_msg ); else irc_send_num( irc, num, "%s :%s", iu->nick, bu->status ? : bu->status_msg ); } else if( !( bu->flags & BEE_USER_ONLINE ) ) { irc_send_num( irc, 301, "%s :%s", iu->nick, "User is offline" ); } if( bu->idle_time || bu->login_time ) { irc_send_num( irc, 317, "%s %d %d :seconds idle, signon time", iu->nick, bu->idle_time ? (int) ( time( NULL ) - bu->idle_time ) : 0, (int) bu->login_time ); } } else { irc_send_num( irc, 312, "%s %s :%s", iu->nick, irc->root->host, IRCD_INFO ); } irc_send_num( irc, 318, "%s :End of /WHOIS list", iu->nick ); } void irc_send_who( irc_t *irc, GSList *l, const char *channel ) { gboolean is_channel = strchr( CTYPES, channel[0] ) != NULL; while( l ) { irc_user_t *iu = l->data; if( is_channel ) iu = ((irc_channel_user_t*)iu)->iu; /* TODO(wilmer): Restore away/channel information here */ irc_send_num( irc, 352, "%s %s %s %s %s %c :0 %s", is_channel ? channel : "*", iu->user, iu->host, irc->root->host, iu->nick, iu->flags & IRC_USER_AWAY ? 'G' : 'H', iu->fullname ); l = l->next; } irc_send_num( irc, 315, "%s :End of /WHO list", channel ); } void irc_send_msg( irc_user_t *iu, const char *type, const char *dst, const char *msg, const char *prefix ) { char last = 0; const char *s = msg, *line = msg; char raw_msg[strlen(msg)+1024]; while( !last ) { if( *s == '\r' && *(s+1) == '\n' ) s++; if( *s == '\n' ) { last = s[1] == 0; } else { last = s[0] == 0; } if( *s == 0 || *s == '\n' ) { if( g_strncasecmp( line, "/me ", 4 ) == 0 && ( !prefix || !*prefix ) && g_strcasecmp( type, "PRIVMSG" ) == 0 ) { strcpy( raw_msg, "\001ACTION " ); strncat( raw_msg, line + 4, s - line - 4 ); strcat( raw_msg, "\001" ); irc_send_msg_raw( iu, type, dst, raw_msg ); } else { *raw_msg = '\0'; if( prefix && *prefix ) strcpy( raw_msg, prefix ); strncat( raw_msg, line, s - line ); irc_send_msg_raw( iu, type, dst, raw_msg ); } line = s + 1; } s ++; } } void irc_send_msg_raw( irc_user_t *iu, const char *type, const char *dst, const char *msg ) { irc_write( iu->irc, ":%s!%s@%s %s %s :%s", iu->nick, iu->user, iu->host, type, dst, msg && *msg ? msg : " " ); } void irc_send_msg_f( irc_user_t *iu, const char *type, const char *dst, const char *format, ... ) { char text[IRC_MAX_LINE]; va_list params; va_start( params, format ); g_vsnprintf( text, IRC_MAX_LINE, format, params ); va_end( params ); irc_write( iu->irc, ":%s!%s@%s %s %s :%s", iu->nick, iu->user, iu->host, type, dst, text ); } void irc_send_nick( irc_user_t *iu, const char *new ) { irc_write( iu->irc, ":%s!%s@%s NICK %s", iu->nick, iu->user, iu->host, new ); } /* Send an update of a user's mode inside a channel, compared to what it was. */ void irc_send_channel_user_mode_diff( irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t old, irc_channel_user_flags_t new ) { char changes[3*(5+strlen(iu->nick))]; char from[strlen(ic->irc->root->nick)+strlen(ic->irc->root->user)+strlen(ic->irc->root->host)+3]; int n; *changes = '\0'; n = 0; if( ( old & IRC_CHANNEL_USER_OP ) != ( new & IRC_CHANNEL_USER_OP ) ) { n ++; if( new & IRC_CHANNEL_USER_OP ) strcat( changes, "+o" ); else strcat( changes, "-o" ); } if( ( old & IRC_CHANNEL_USER_HALFOP ) != ( new & IRC_CHANNEL_USER_HALFOP ) ) { n ++; if( new & IRC_CHANNEL_USER_HALFOP ) strcat( changes, "+h" ); else strcat( changes, "-h" ); } if( ( old & IRC_CHANNEL_USER_VOICE ) != ( new & IRC_CHANNEL_USER_VOICE ) ) { n ++; if( new & IRC_CHANNEL_USER_VOICE ) strcat( changes, "+v" ); else strcat( changes, "-v" ); } while( n ) { strcat( changes, " " ); strcat( changes, iu->nick ); n --; } if( set_getbool( &ic->irc->b->set, "simulate_netsplit" ) ) g_snprintf( from, sizeof( from ), "%s", ic->irc->root->host ); else g_snprintf( from, sizeof( from ), "%s!%s@%s", ic->irc->root->nick, ic->irc->root->user, ic->irc->root->host ); if( *changes ) irc_write( ic->irc, ":%s MODE %s %s", from, ic->name, changes ); } void irc_send_invite( irc_user_t *iu, irc_channel_t *ic ) { irc_t *irc = iu->irc; irc_write( iu->irc, ":%s!%s@%s INVITE %s :%s", iu->nick, iu->user, iu->host, irc->user->nick, ic->name ); } bitlbee-3.2.1/irc_util.c0000644000175000017500000000637712245474076014511 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2010 Wilmer van der Gaast and others * \********************************************************************/ /* Some stuff that doesn't belong anywhere else. */ /* 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" char *set_eval_timezone( set_t *set, char *value ) { char *s; if( strcmp( value, "local" ) == 0 || strcmp( value, "gmt" ) == 0 || strcmp( value, "utc" ) == 0 ) return value; /* Otherwise: +/- at the beginning optional, then one or more numbers, possibly followed by a colon and more numbers. Don't bother bound- checking them since users are free to shoot themselves in the foot. */ s = value; if( *s == '+' || *s == '-' ) s ++; /* \d+ */ if( !isdigit( *s ) ) return SET_INVALID; while( *s && isdigit( *s ) ) s ++; /* EOS? */ if( *s == '\0' ) return value; /* Otherwise, colon */ if( *s != ':' ) return SET_INVALID; s ++; /* \d+ */ if( !isdigit( *s ) ) return SET_INVALID; while( *s && isdigit( *s ) ) s ++; /* EOS */ return *s == '\0' ? value : SET_INVALID; } char *irc_format_timestamp( irc_t *irc, time_t msg_ts ) { time_t now_ts = time( NULL ); struct tm now, msg; char *set; /* If the timestamp is <= 0 or less than a minute ago, discard it as it doesn't seem to add to much useful info and/or might be noise. */ if( msg_ts <= 0 || msg_ts > now_ts - 60 ) return NULL; set = set_getstr( &irc->b->set, "timezone" ); if( strcmp( set, "local" ) == 0 ) { localtime_r( &now_ts, &now ); localtime_r( &msg_ts, &msg ); } else { int hr, min = 0, sign = 60; if( set[0] == '-' ) { sign *= -1; set ++; } else if( set[0] == '+' ) { set ++; } if( sscanf( set, "%d:%d", &hr, &min ) >= 1 ) { msg_ts += sign * ( hr * 60 + min ); now_ts += sign * ( hr * 60 + min ); } gmtime_r( &now_ts, &now ); gmtime_r( &msg_ts, &msg ); } if( msg.tm_year == now.tm_year && msg.tm_yday == now.tm_yday ) return g_strdup_printf( "\x02[\x02\x02\x02%02d:%02d:%02d\x02]\x02 ", msg.tm_hour, msg.tm_min, msg.tm_sec ); else return g_strdup_printf( "\x02[\x02\x02\x02%04d-%02d-%02d " "%02d:%02d:%02d\x02]\x02 ", msg.tm_year + 1900, msg.tm_mon + 1, msg.tm_mday, msg.tm_hour, msg.tm_min, msg.tm_sec ); } bitlbee-3.2.1/irc.h0000644000175000017500000003017212245474076013447 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 Wilmer van der Gaast and others * \********************************************************************/ /* The IRC-based UI (for now the only one) */ /* 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 _IRC_H #define _IRC_H #define IRC_MAX_LINE 512 #define IRC_MAX_ARGS 16 #define IRC_LOGIN_TIMEOUT 60 #define IRC_PING_STRING "PinglBee" #define UMODES "abisw" /* Allowed umodes (although they mostly do nothing) */ #define UMODES_PRIV "Ro" /* Allowed, but not by user directly */ #define UMODES_KEEP "R" /* Don't allow unsetting using /MODE */ #define CMODES "ntC" /* Allowed modes */ #define CMODE "t" /* Default mode */ #define UMODE "s" /* Default mode */ #define CTYPES "&#" /* Valid channel name prefixes */ typedef enum { USTATUS_OFFLINE = 0, USTATUS_AUTHORIZED = 1, /* Gave the correct server password (PASS). */ USTATUS_LOGGED_IN = 2, /* USER+NICK(+PASS) finished. */ USTATUS_IDENTIFIED = 4, /* To NickServ (root). */ USTATUS_SHUTDOWN = 8, /* Now used to indicate we're shutting down. Currently just blocks irc_vawrite(). */ /* Not really status stuff, but other kinds of flags: For slightly better password security, since the only way to send passwords to the IRC server securely (i.e. not echoing to screen or written to logfiles) is the /OPER command, try to use that command for stuff that matters. */ OPER_HACK_IDENTIFY = 0x100, OPER_HACK_IDENTIFY_NOLOAD = 0x01100, OPER_HACK_IDENTIFY_FORCE = 0x02100, OPER_HACK_REGISTER = 0x200, OPER_HACK_ACCOUNT_ADD = 0x400, OPER_HACK_ANY = 0x3700, /* To check for them all at once. */ IRC_UTF8_NICKS = 0x10000, /* Disable ASCII restrictions on buddy nicks. */ } irc_status_t; struct irc_user; typedef struct irc { int fd; irc_status_t status; double last_pong; int pinging; char *sendbuffer; char *readbuffer; GIConv iconv, oconv; struct irc_user *root; struct irc_user *user; char *password; /* HACK: Used to save the user's password, but before logging in, this may contain a password we should send to identify after USER/NICK are received. */ char umode[8]; struct query *queries; GSList *file_transfers; GSList *users, *channels; struct irc_channel *default_channel; GHashTable *nick_user_hash; GHashTable *watches; /* See irc_cmd_watch() */ gint r_watch_source_id; gint w_watch_source_id; gint ping_source_id; gint login_source_id; /* To slightly delay some events at login time. */ struct otr *otr; /* OTR state and book keeping, used by the OTR plugin. TODO: Some mechanism for plugindata. */ struct bee *b; } irc_t; typedef enum { /* Replaced with iu->last_channel IRC_USER_PRIVATE = 1, */ IRC_USER_AWAY = 2, IRC_USER_OTR_ENCRYPTED = 0x10000, IRC_USER_OTR_TRUSTED = 0x20000, } irc_user_flags_t; typedef struct irc_user { irc_t *irc; char *nick; char *user; char *host; char *fullname; /* Nickname in lowercase for case insensitive searches */ char *key; irc_user_flags_t flags; struct irc_channel *last_channel; GString *pastebuf; /* Paste buffer (combine lines into a multiline msg). */ guint pastebuf_timer; time_t away_reply_timeout; /* Only send a 301 if this time passed. */ struct bee_user *bu; const struct irc_user_funcs *f; } irc_user_t; struct irc_user_funcs { gboolean (*privmsg)( irc_user_t *iu, const char *msg ); gboolean (*ctcp)( irc_user_t *iu, char * const* ctcp ); }; extern const struct irc_user_funcs irc_user_root_funcs; extern const struct irc_user_funcs irc_user_self_funcs; typedef enum { IRC_CHANNEL_JOINED = 1, /* The user is currently in the channel. */ IRC_CHANNEL_TEMP = 2, /* Erase the channel when the user leaves, and don't save it. */ /* Hack: Set this flag right before jumping into IM when we expect a call to imcb_chat_new(). */ IRC_CHANNEL_CHAT_PICKME = 0x10000, } irc_channel_flags_t; typedef struct irc_channel { irc_t *irc; char *name; char mode[8]; int flags; char *topic; char *topic_who; time_t topic_time; GSList *users; /* struct irc_channel_user */ struct irc_user *last_target; struct set *set; GString *pastebuf; /* Paste buffer (combine lines into a multiline msg). */ guint pastebuf_timer; const struct irc_channel_funcs *f; void *data; } irc_channel_t; struct irc_channel_funcs { gboolean (*privmsg)( irc_channel_t *ic, const char *msg ); gboolean (*join)( irc_channel_t *ic ); gboolean (*part)( irc_channel_t *ic, const char *msg ); gboolean (*topic)( irc_channel_t *ic, const char *new_topic ); gboolean (*invite)( irc_channel_t *ic, irc_user_t *iu ); gboolean (*_init)( irc_channel_t *ic ); gboolean (*_free)( irc_channel_t *ic ); }; typedef enum { IRC_CHANNEL_USER_OP = 1, IRC_CHANNEL_USER_HALFOP = 2, IRC_CHANNEL_USER_VOICE = 4, IRC_CHANNEL_USER_NONE = 8, } irc_channel_user_flags_t; typedef struct irc_channel_user { irc_user_t *iu; int flags; } irc_channel_user_t; typedef enum { IRC_CC_TYPE_DEFAULT = 0x00001, IRC_CC_TYPE_REST = 0x00002, /* Still not implemented. */ IRC_CC_TYPE_GROUP = 0x00004, IRC_CC_TYPE_ACCOUNT = 0x00008, IRC_CC_TYPE_PROTOCOL = 0x00010, IRC_CC_TYPE_MASK = 0x000ff, IRC_CC_TYPE_INVERT = 0x00100, } irc_control_channel_type_t; struct irc_control_channel { irc_control_channel_type_t type; struct bee_group *group; struct account *account; struct prpl *protocol; char modes[4]; }; extern const struct bee_ui_funcs irc_ui_funcs; typedef enum { IRC_CDU_SILENT, IRC_CDU_PART, IRC_CDU_KICK, } irc_channel_del_user_type_t; /* These are a glued a little bit to the core/bee layer and a little bit to IRC. The first user is OTR, and I guess at some point we'll get to shape this a little bit more as other uses come up. */ typedef struct irc_plugin { /* Called at the end of irc_new(). Can be used to add settings, etc. */ gboolean (*irc_new)( irc_t *irc ); /* At the end of irc_free(). */ void (*irc_free)( irc_t *irc ); /* Problem with the following two functions is ordering if multiple plugins are handling them. Let's keep fixing that problem for whenever it becomes important. */ /* Called by bee_irc_user_privmsg_cb(). Return NULL if you want to abort sending the msg. */ char* (*filter_msg_out)( irc_user_t *iu, char *msg, int flags ); /* Called by bee_irc_user_msg(). Return NULL if you swallowed the message and don't want anything to go to the user. */ char* (*filter_msg_in)( irc_user_t *iu, char *msg, int flags ); /* From storage.c functions. Ideally these should not be used and instead data should be stored in settings which will get saved automatically. Consider these deprecated! */ void (*storage_load)( irc_t *irc ); void (*storage_save)( irc_t *irc ); void (*storage_remove)( const char *nick ); } irc_plugin_t; extern GSList *irc_plugins; /* struct irc_plugin */ /* irc.c */ extern GSList *irc_connection_list; irc_t *irc_new( int fd ); void irc_abort( irc_t *irc, int immed, char *format, ... ) G_GNUC_PRINTF( 3, 4 ); void irc_free( irc_t *irc ); void irc_setpass (irc_t *irc, const char *pass); void irc_process( irc_t *irc ); char **irc_parse_line( char *line ); char *irc_build_line( char **cmd ); void irc_write( irc_t *irc, char *format, ... ) G_GNUC_PRINTF( 2, 3 ); void irc_write_all( int now, char *format, ... ) G_GNUC_PRINTF( 2, 3 ); void irc_vawrite( irc_t *irc, char *format, va_list params ); void irc_flush( irc_t *irc ); void irc_switch_fd( irc_t *irc, int fd ); void irc_sync( irc_t *irc ); void irc_desync( irc_t *irc ); int irc_check_login( irc_t *irc ); void irc_umode_set( irc_t *irc, const char *s, gboolean allow_priv ); void register_irc_plugin( const struct irc_plugin *p ); /* irc_channel.c */ irc_channel_t *irc_channel_new( irc_t *irc, const char *name ); irc_channel_t *irc_channel_by_name( irc_t *irc, const char *name ); irc_channel_t *irc_channel_get( irc_t *irc, char *id ); int irc_channel_free( irc_channel_t *ic ); void irc_channel_free_soon( irc_channel_t *ic ); int irc_channel_add_user( irc_channel_t *ic, irc_user_t *iu ); int irc_channel_del_user( irc_channel_t *ic, irc_user_t *iu, irc_channel_del_user_type_t type, const char *msg ); irc_channel_user_t *irc_channel_has_user( irc_channel_t *ic, irc_user_t *iu ); struct irc_channel *irc_channel_with_user( irc_t *irc, irc_user_t *iu ); int irc_channel_set_topic( irc_channel_t *ic, const char *topic, const irc_user_t *who ); void irc_channel_user_set_mode( irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t flags ); void irc_channel_set_mode( irc_channel_t *ic, const char *s ); void irc_channel_auto_joins( irc_t *irc, struct account *acc ); void irc_channel_printf( irc_channel_t *ic, char *format, ... ); gboolean irc_channel_name_ok( const char *name ); void irc_channel_name_strip( char *name ); int irc_channel_name_cmp( const char *a_, const char *b_ ); void irc_channel_update_ops( irc_channel_t *ic, char *value ); char *set_eval_irc_channel_ops( struct set *set, char *value ); gboolean irc_channel_wants_user( irc_channel_t *ic, irc_user_t *iu ); /* irc_commands.c */ void irc_exec( irc_t *irc, char **cmd ); /* irc_send.c */ void irc_send_num( irc_t *irc, int code, char *format, ... ) G_GNUC_PRINTF( 3, 4 ); void irc_send_login( irc_t *irc ); void irc_send_motd( irc_t *irc ); const char *irc_user_msgdest( irc_user_t *iu ); void irc_rootmsg( irc_t *irc, char *format, ... ); void irc_usermsg( irc_user_t *iu, char *format, ... ); void irc_usernotice( irc_user_t *iu, char *format, ... ); void irc_send_join( irc_channel_t *ic, irc_user_t *iu ); void irc_send_part( irc_channel_t *ic, irc_user_t *iu, const char *reason ); void irc_send_quit( irc_user_t *iu, const char *reason ); void irc_send_kick( irc_channel_t *ic, irc_user_t *iu, irc_user_t *kicker, const char *reason ); void irc_send_names( irc_channel_t *ic ); void irc_send_topic( irc_channel_t *ic, gboolean topic_change ); void irc_send_whois( irc_user_t *iu ); void irc_send_who( irc_t *irc, GSList *l, const char *channel ); void irc_send_msg( irc_user_t *iu, const char *type, const char *dst, const char *msg, const char *prefix ); void irc_send_msg_raw( irc_user_t *iu, const char *type, const char *dst, const char *msg ); void irc_send_msg_f( irc_user_t *iu, const char *type, const char *dst, const char *format, ... ) G_GNUC_PRINTF( 4, 5 ); void irc_send_nick( irc_user_t *iu, const char *new_nick ); void irc_send_channel_user_mode_diff( irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t old_flags, irc_channel_user_flags_t new_flags ); void irc_send_invite( irc_user_t *iu, irc_channel_t *ic ); /* irc_user.c */ irc_user_t *irc_user_new( irc_t *irc, const char *nick ); int irc_user_free( irc_t *irc, irc_user_t *iu ); irc_user_t *irc_user_by_name( irc_t *irc, const char *nick ); int irc_user_set_nick( irc_user_t *iu, const char *new_nick ); gint irc_user_cmp( gconstpointer a_, gconstpointer b_ ); const char *irc_user_get_away( irc_user_t *iu ); void irc_user_quit( irc_user_t *iu, const char *msg ); /* irc_util.c */ char *set_eval_timezone( struct set *set, char *value ); char *irc_format_timestamp( irc_t *irc, time_t msg_ts ); /* irc_im.c */ void bee_irc_channel_update( irc_t *irc, irc_channel_t *ic, irc_user_t *iu ); void bee_irc_user_nick_reset( irc_user_t *iu ); #endif bitlbee-3.2.1/irc_channel.c0000644000175000017500000004776012245474076015145 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 Wilmer van der Gaast and others * \********************************************************************/ /* The IRC-based UI - Representing (virtual) channels. */ /* 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" static char *set_eval_channel_type( set_t *set, char *value ); static gint irc_channel_user_cmp( gconstpointer a_, gconstpointer b_ ); static const struct irc_channel_funcs control_channel_funcs; extern const struct irc_channel_funcs irc_channel_im_chat_funcs; irc_channel_t *irc_channel_new( irc_t *irc, const char *name ) { irc_channel_t *ic; set_t *s; if( !irc_channel_name_ok( name ) || irc_channel_by_name( irc, name ) ) return NULL; ic = g_new0( irc_channel_t, 1 ); ic->irc = irc; ic->name = g_strdup( name ); strcpy( ic->mode, CMODE ); irc_channel_add_user( ic, irc->root ); irc->channels = g_slist_append( irc->channels, ic ); set_add( &ic->set, "auto_join", "false", set_eval_bool, ic ); s = set_add( &ic->set, "type", "control", set_eval_channel_type, ic ); s->flags |= SET_NOSAVE; /* Layer violation (XML format detail) */ if( name[0] == '&' ) set_setstr( &ic->set, "type", "control" ); else /* if( name[0] == '#' ) */ set_setstr( &ic->set, "type", "chat" ); return ic; } irc_channel_t *irc_channel_by_name( irc_t *irc, const char *name ) { GSList *l; for( l = irc->channels; l; l = l->next ) { irc_channel_t *ic = l->data; if( irc_channel_name_cmp( name, ic->name ) == 0 ) return ic; } return NULL; } irc_channel_t *irc_channel_get( irc_t *irc, char *id ) { irc_channel_t *ic, *ret = NULL; GSList *l; int nr; if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 ) { for( l = irc->channels; l; l = l->next ) { ic = l->data; if( ( nr-- ) == 0 ) return ic; } return NULL; } /* Exact match first: Partial match only sucks if there's a channel #aa and #aabb */ if( ( ret = irc_channel_by_name( irc, id ) ) ) return ret; for( l = irc->channels; l; l = l->next ) { ic = l->data; if( strstr( ic->name, id ) ) { /* Make sure it's a unique match. */ if( !ret ) ret = ic; else return NULL; } } return ret; } int irc_channel_free( irc_channel_t *ic ) { irc_t *irc; GSList *l; if( ic == NULL ) return 0; irc = ic->irc; if( ic->flags & IRC_CHANNEL_JOINED ) irc_channel_del_user( ic, irc->user, IRC_CDU_KICK, "Cleaning up channel" ); if( ic->f->_free ) ic->f->_free( ic ); while( ic->set ) set_del( &ic->set, ic->set->key ); irc->channels = g_slist_remove( irc->channels, ic ); while( ic->users ) { g_free( ic->users->data ); ic->users = g_slist_remove( ic->users, ic->users->data ); } for( l = irc->users; l; l = l->next ) { irc_user_t *iu = l->data; if( iu->last_channel == ic ) iu->last_channel = irc->default_channel; } if( ic->pastebuf_timer ) b_event_remove( ic->pastebuf_timer ); g_free( ic->name ); g_free( ic->topic ); g_free( ic->topic_who ); g_free( ic ); return 1; } struct irc_channel_free_data { irc_t *irc; irc_channel_t *ic; char *name; }; static gboolean irc_channel_free_callback( gpointer data, gint fd, b_input_condition cond ) { struct irc_channel_free_data *d = data; if( g_slist_find( irc_connection_list, d->irc ) && irc_channel_by_name( d->irc, d->name ) == d->ic && !( d->ic->flags & IRC_CHANNEL_JOINED ) ) irc_channel_free( d->ic ); g_free( d->name ); g_free( d ); return FALSE; } /* Free the channel, but via the event loop, so after finishing whatever event we're currently handling. */ void irc_channel_free_soon( irc_channel_t *ic ) { struct irc_channel_free_data *d = g_new0( struct irc_channel_free_data, 1 ); d->irc = ic->irc; d->ic = ic; d->name = g_strdup( ic->name ); b_timeout_add( 0, irc_channel_free_callback, d ); } static char *set_eval_channel_type( set_t *set, char *value ) { struct irc_channel *ic = set->data; const struct irc_channel_funcs *new; if( strcmp( value, "control" ) == 0 ) new = &control_channel_funcs; else if( ic != ic->irc->default_channel && strcmp( value, "chat" ) == 0 ) new = &irc_channel_im_chat_funcs; else return SET_INVALID; /* TODO: Return values. */ if( ic->f && ic->f->_free ) ic->f->_free( ic ); ic->f = new; if( ic->f && ic->f->_init ) ic->f->_init( ic ); return value; } int irc_channel_add_user( irc_channel_t *ic, irc_user_t *iu ) { irc_channel_user_t *icu; if( irc_channel_has_user( ic, iu ) ) return 0; icu = g_new0( irc_channel_user_t, 1 ); icu->iu = iu; ic->users = g_slist_insert_sorted( ic->users, icu, irc_channel_user_cmp ); irc_channel_update_ops( ic, set_getstr( &ic->irc->b->set, "ops" ) ); if( iu == ic->irc->user || ic->flags & IRC_CHANNEL_JOINED ) { ic->flags |= IRC_CHANNEL_JOINED; irc_send_join( ic, iu ); } return 1; } int irc_channel_del_user( irc_channel_t *ic, irc_user_t *iu, irc_channel_del_user_type_t type, const char *msg ) { irc_channel_user_t *icu; if( !( icu = irc_channel_has_user( ic, iu ) ) ) return 0; ic->users = g_slist_remove( ic->users, icu ); g_free( icu ); if( !( ic->flags & IRC_CHANNEL_JOINED ) || type == IRC_CDU_SILENT ) {} /* Do nothing. The caller should promise it won't screw up state of the IRC client. :-) */ else if( type == IRC_CDU_PART ) irc_send_part( ic, iu, msg ); else if( type == IRC_CDU_KICK ) irc_send_kick( ic, iu, ic->irc->root, msg ); if( iu == ic->irc->user ) { ic->flags &= ~IRC_CHANNEL_JOINED; if( ic->irc->status & USTATUS_SHUTDOWN ) { /* Don't do anything fancy when we're shutting down anyway. */ } else if( ic->flags & IRC_CHANNEL_TEMP ) { irc_channel_free_soon( ic ); } else { /* Flush userlist now. The user won't see it anyway. */ while( ic->users ) { g_free( ic->users->data ); ic->users = g_slist_remove( ic->users, ic->users->data ); } irc_channel_add_user( ic, ic->irc->root ); } } return 1; } irc_channel_user_t *irc_channel_has_user( irc_channel_t *ic, irc_user_t *iu ) { GSList *l; for( l = ic->users; l; l = l->next ) { irc_channel_user_t *icu = l->data; if( icu->iu == iu ) return icu; } return NULL; } /* Find a channel we're currently in, that currently has iu in it. */ struct irc_channel *irc_channel_with_user( irc_t *irc, irc_user_t *iu ) { GSList *l; for( l = irc->channels; l; l = l->next ) { irc_channel_t *ic = l->data; if( strcmp( set_getstr( &ic->set, "type" ), "control" ) != 0 ) continue; if( ( ic->flags & IRC_CHANNEL_JOINED ) && irc_channel_has_user( ic, iu ) ) return ic; } /* If there was no match, try once more but just see if the user *would* be in the channel, i.e. if s/he were online. */ if( iu->bu == NULL ) return NULL; for( l = irc->channels; l; l = l->next ) { irc_channel_t *ic = l->data; if( strcmp( set_getstr( &ic->set, "type" ), "control" ) != 0 ) continue; if( ( ic->flags & IRC_CHANNEL_JOINED ) && irc_channel_wants_user( ic, iu ) ) return ic; } return NULL; } int irc_channel_set_topic( irc_channel_t *ic, const char *topic, const irc_user_t *iu ) { g_free( ic->topic ); ic->topic = g_strdup( topic ); g_free( ic->topic_who ); if( iu ) ic->topic_who = g_strdup_printf( "%s!%s@%s", iu->nick, iu->user, iu->host ); else ic->topic_who = NULL; ic->topic_time = time( NULL ); if( ic->flags & IRC_CHANNEL_JOINED ) irc_send_topic( ic, TRUE ); return 1; } void irc_channel_user_set_mode( irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t flags ) { irc_channel_user_t *icu = irc_channel_has_user( ic, iu ); if( !icu || icu->flags == flags ) return; if( ic->flags & IRC_CHANNEL_JOINED ) irc_send_channel_user_mode_diff( ic, iu, icu->flags, flags ); icu->flags = flags; } void irc_channel_set_mode( irc_channel_t *ic, const char *s ) { irc_t *irc = ic->irc; char m[128], st = 1; const char *t; int i; char changes[512], *p, st2 = 2; memset( m, 0, sizeof( m ) ); for( t = ic->mode; *t; t ++ ) if( *t < sizeof( m ) ) m[(int)*t] = 1; p = changes; for( t = s; *t; t ++ ) { if( *t == '+' || *t == '-' ) st = *t == '+'; else if( strchr( CMODES, *t ) ) { if( m[(int)*t] != st) { if( st != st2 ) st2 = st, *p++ = st ? '+' : '-'; *p++ = *t; } m[(int)*t] = st; } } *p = '\0'; memset( ic->mode, 0, sizeof( ic->mode ) ); for( i = 'A'; i <= 'z' && strlen( ic->mode ) < ( sizeof( ic->mode ) - 1 ); i ++ ) if( m[i] ) ic->mode[strlen(ic->mode)] = i; if( *changes && ( ic->flags & IRC_CHANNEL_JOINED ) ) irc_write( irc, ":%s!%s@%s MODE %s :%s", irc->root->nick, irc->root->user, irc->root->host, ic->name, changes ); } void irc_channel_auto_joins( irc_t *irc, account_t *acc ) { GSList *l; for( l = irc->channels; l; l = l->next ) { irc_channel_t *ic = l->data; gboolean aj = set_getbool( &ic->set, "auto_join" ); char *type; if( acc && ( type = set_getstr( &ic->set, "chat_type" ) ) && strcmp( type, "room" ) == 0 ) { /* Bit of an ugly special case: Handle chatrooms here, we can only auto-join them if their account is online. */ char *acc_s; if( !aj && !( ic->flags & IRC_CHANNEL_JOINED ) ) /* Only continue if this one's marked as auto_join or if we're in it already. (Possible if the client auto-rejoined it before identyfing.) */ continue; else if( !( acc_s = set_getstr( &ic->set, "account" ) ) ) continue; else if( account_get( irc->b, acc_s ) != acc ) continue; else if( acc->ic == NULL || !( acc->ic->flags & OPT_LOGGED_IN ) ) continue; else ic->f->join( ic ); } else if( aj ) { irc_channel_add_user( ic, irc->user ); } } } void irc_channel_printf( irc_channel_t *ic, char *format, ... ) { va_list params; char *text; va_start( params, format ); text = g_strdup_vprintf( format, params ); va_end( params ); irc_send_msg( ic->irc->root, "PRIVMSG", ic->name, text, NULL ); g_free( text ); } gboolean irc_channel_name_ok( const char *name_ ) { const unsigned char *name = (unsigned char*) name_; int i; if( name_[0] == '\0' ) return FALSE; /* Check if the first character is in CTYPES (#&) */ if( strchr( CTYPES, name_[0] ) == NULL ) return FALSE; /* RFC 1459 keeps amazing me: While only a "few" chars are allowed in nicknames, channel names can be pretty much anything as long as they start with # or &. I'll be a little bit more strict and disallow all non-printable characters. */ for( i = 1; name[i]; i ++ ) if( name[i] <= ' ' || name[i] == ',' ) return FALSE; return TRUE; } void irc_channel_name_strip( char *name ) { int i, j; for( i = j = 0; name[i]; i ++ ) if( name[i] > ' ' && name[i] != ',' ) name[j++] = name[i]; name[j] = '\0'; } int irc_channel_name_cmp( const char *a_, const char *b_ ) { static unsigned char case_map[256]; const unsigned char *a = (unsigned char*) a_, *b = (unsigned char*) b_; int i; if( case_map['A'] == '\0' ) { for( i = 33; i < 256; i ++ ) if( i != ',' ) case_map[i] = i; for( i = 0; i < 26; i ++ ) case_map['A'+i] = 'a' + i; case_map['['] = '{'; case_map[']'] = '}'; case_map['~'] = '`'; case_map['\\'] = '|'; } if( !irc_channel_name_ok( a_ ) || !irc_channel_name_ok( b_ ) ) return -1; for( i = 0; a[i] && b[i] && case_map[a[i]] && case_map[b[i]]; i ++ ) { if( case_map[a[i]] == case_map[b[i]] ) continue; else return case_map[a[i]] - case_map[b[i]]; } return case_map[a[i]] - case_map[b[i]]; } static gint irc_channel_user_cmp( gconstpointer a_, gconstpointer b_ ) { const irc_channel_user_t *a = a_, *b = b_; return irc_user_cmp( a->iu, b->iu ); } void irc_channel_update_ops( irc_channel_t *ic, char *value ) { irc_channel_user_set_mode( ic, ic->irc->root, ( strcmp( value, "both" ) == 0 || strcmp( value, "root" ) == 0 ) ? IRC_CHANNEL_USER_OP : 0 ); irc_channel_user_set_mode( ic, ic->irc->user, ( strcmp( value, "both" ) == 0 || strcmp( value, "user" ) == 0 ) ? IRC_CHANNEL_USER_OP : 0 ); } char *set_eval_irc_channel_ops( set_t *set, char *value ) { irc_t *irc = set->data; GSList *l; if( strcmp( value, "both" ) != 0 && strcmp( value, "none" ) != 0 && strcmp( value, "user" ) != 0 && strcmp( value, "root" ) != 0 ) return SET_INVALID; for( l = irc->channels; l; l = l->next ) irc_channel_update_ops( l->data, value ); return value; } /* Channel-type dependent functions, for control channels: */ static gboolean control_channel_privmsg( irc_channel_t *ic, const char *msg ) { irc_t *irc = ic->irc; irc_user_t *iu; const char *s; /* Scan for non-whitespace chars followed by a colon: */ for( s = msg; *s && !isspace( *s ) && *s != ':' && *s != ','; s ++ ) {} if( *s == ':' || *s == ',' ) { char to[s-msg+1]; memset( to, 0, sizeof( to ) ); strncpy( to, msg, s - msg ); while( *(++s) && isspace( *s ) ) {} msg = s; if( !( iu = irc_user_by_name( irc, to ) ) ) irc_channel_printf( ic, "User does not exist: %s", to ); else ic->last_target = iu; } else if( g_strcasecmp( set_getstr( &irc->b->set, "default_target" ), "last" ) == 0 && ic->last_target && g_slist_find( irc->users, ic->last_target ) ) iu = ic->last_target; else iu = irc->root; if( iu && iu->f->privmsg ) { iu->last_channel = ic; iu->f->privmsg( iu, msg ); } return TRUE; } static gboolean control_channel_invite( irc_channel_t *ic, irc_user_t *iu ) { struct irc_control_channel *icc = ic->data; bee_user_t *bu = iu->bu; if( bu == NULL ) return FALSE; if( icc->type != IRC_CC_TYPE_GROUP ) { irc_send_num( ic->irc, 482, "%s :Invitations are only possible to fill_by=group channels", ic->name ); return FALSE; } bu->ic->acc->prpl->add_buddy( bu->ic, bu->handle, icc->group ? icc->group->name : NULL ); return TRUE; } static char *set_eval_by_account( set_t *set, char *value ); static char *set_eval_fill_by( set_t *set, char *value ); static char *set_eval_by_group( set_t *set, char *value ); static char *set_eval_by_protocol( set_t *set, char *value ); static char *set_eval_show_users( set_t *set, char *value ); static gboolean control_channel_init( irc_channel_t *ic ) { struct irc_control_channel *icc; set_add( &ic->set, "account", NULL, set_eval_by_account, ic ); set_add( &ic->set, "fill_by", "all", set_eval_fill_by, ic ); set_add( &ic->set, "group", NULL, set_eval_by_group, ic ); set_add( &ic->set, "protocol", NULL, set_eval_by_protocol, ic ); /* When changing the default, also change it below. */ set_add( &ic->set, "show_users", "online+,away", set_eval_show_users, ic ); ic->data = icc = g_new0( struct irc_control_channel, 1 ); icc->type = IRC_CC_TYPE_DEFAULT; /* Have to run the evaluator to initialize icc->modes. */ set_setstr( &ic->set, "show_users", "online+,away" ); /* For scripts that care. */ irc_channel_set_mode( ic, "+C" ); return TRUE; } static gboolean control_channel_join( irc_channel_t *ic ) { bee_irc_channel_update( ic->irc, ic, NULL ); return TRUE; } static char *set_eval_by_account( set_t *set, char *value ) { struct irc_channel *ic = set->data; struct irc_control_channel *icc = ic->data; account_t *acc; if( !( acc = account_get( ic->irc->b, value ) ) ) return SET_INVALID; icc->account = acc; if( ( icc->type & IRC_CC_TYPE_MASK ) == IRC_CC_TYPE_ACCOUNT ) bee_irc_channel_update( ic->irc, ic, NULL ); return g_strdup( acc->tag ); } static char *set_eval_fill_by( set_t *set, char *value ) { struct irc_channel *ic = set->data; struct irc_control_channel *icc = ic->data; char *s; icc->type &= ~( IRC_CC_TYPE_MASK | IRC_CC_TYPE_INVERT ); s = value; if( s[0] == '!' ) { icc->type |= IRC_CC_TYPE_INVERT; s ++; } if( strcmp( s, "all" ) == 0 ) icc->type |= IRC_CC_TYPE_DEFAULT; else if( strcmp( s, "rest" ) == 0 ) icc->type |= IRC_CC_TYPE_REST; else if( strcmp( s, "group" ) == 0 ) icc->type |= IRC_CC_TYPE_GROUP; else if( strcmp( s, "account" ) == 0 ) icc->type |= IRC_CC_TYPE_ACCOUNT; else if( strcmp( s, "protocol" ) == 0 ) icc->type |= IRC_CC_TYPE_PROTOCOL; else return SET_INVALID; bee_irc_channel_update( ic->irc, ic, NULL ); return value; } static char *set_eval_by_group( set_t *set, char *value ) { struct irc_channel *ic = set->data; struct irc_control_channel *icc = ic->data; icc->group = bee_group_by_name( ic->irc->b, value, TRUE ); if( ( icc->type & IRC_CC_TYPE_MASK ) == IRC_CC_TYPE_GROUP ) bee_irc_channel_update( ic->irc, ic, NULL ); return g_strdup( icc->group->name ); } static char *set_eval_by_protocol( set_t *set, char *value ) { struct irc_channel *ic = set->data; struct irc_control_channel *icc = ic->data; struct prpl *prpl; if( !( prpl = find_protocol( value ) ) ) return SET_INVALID; icc->protocol = prpl; if( ( icc->type & IRC_CC_TYPE_MASK ) == IRC_CC_TYPE_PROTOCOL ) bee_irc_channel_update( ic->irc, ic, NULL ); return value; } static char *set_eval_show_users( set_t *set, char *value ) { struct irc_channel *ic = set->data; struct irc_control_channel *icc = ic->data; char **parts = g_strsplit( value, ",", 0 ), **part; char modes[4]; memset( modes, 0, 4 ); for( part = parts; *part; part ++ ) { char last, modechar = IRC_CHANNEL_USER_NONE; if( **part == '\0' ) goto fail; last = (*part)[strlen(*part+1)]; if( last == '+' ) modechar = IRC_CHANNEL_USER_VOICE; else if( last == '%' ) modechar = IRC_CHANNEL_USER_HALFOP; else if( last == '@' ) modechar = IRC_CHANNEL_USER_OP; if( strncmp( *part, "offline", 7 ) == 0 ) modes[0] = modechar; else if( strncmp( *part, "away", 4 ) == 0 ) modes[1] = modechar; else if( strncmp( *part, "online", 6 ) == 0 ) modes[2] = modechar; else goto fail; } memcpy( icc->modes, modes, 4 ); bee_irc_channel_update( ic->irc, ic, NULL ); g_strfreev( parts ); return value; fail: g_strfreev( parts ); return SET_INVALID; } /* Figure out if a channel is supposed to have the user, assuming s/he is online or otherwise also selected by the show_users setting. Only works for control channels, but does *not* check if this channel is of that type. Beware! */ gboolean irc_channel_wants_user( irc_channel_t *ic, irc_user_t *iu ) { struct irc_control_channel *icc = ic->data; gboolean ret = FALSE; if( iu->bu == NULL ) return FALSE; switch( icc->type & IRC_CC_TYPE_MASK ) { case IRC_CC_TYPE_GROUP: ret = iu->bu->group == icc->group; break; case IRC_CC_TYPE_ACCOUNT: ret = iu->bu->ic->acc == icc->account; break; case IRC_CC_TYPE_PROTOCOL: ret = iu->bu->ic->acc->prpl == icc->protocol; break; case IRC_CC_TYPE_DEFAULT: default: ret = TRUE; break; } if( icc->type & IRC_CC_TYPE_INVERT ) ret = !ret; return ret; } static gboolean control_channel_free( irc_channel_t *ic ) { struct irc_control_channel *icc = ic->data; set_del( &ic->set, "account" ); set_del( &ic->set, "fill_by" ); set_del( &ic->set, "group" ); set_del( &ic->set, "protocol" ); set_del( &ic->set, "show_users" ); g_free( icc ); ic->data = NULL; /* For scripts that care. */ irc_channel_set_mode( ic, "-C" ); return TRUE; } static const struct irc_channel_funcs control_channel_funcs = { control_channel_privmsg, control_channel_join, NULL, NULL, control_channel_invite, control_channel_init, control_channel_free, }; bitlbee-3.2.1/dcc.h0000644000175000017500000000760512245474076013430 0ustar wilmerwilmer/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2006 Marijn Kruisselbrink and others * * Copyright 2007 Uli Meis * \********************************************************************/ /* 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 */ /* * DCC SEND * * Historically, DCC means send 1024 Bytes and wait for a 4 byte reply * acknowledging all transferred data. This is ridiculous for two reasons. The * first being that TCP is a stream oriented protocol that doesn't care much * about your idea of a packet. The second reason being that TCP is a reliable * transfer protocol with its own sophisticated ACK mechanism, making DCCs ACK * mechanism look like a joke. For these reasons, DCCs requirements have * (hopefully) been relaxed in most implementations and this implementation * depends upon at least the following: The 1024 bytes need not be transferred * at once, i.e. packets can be smaller. A second relaxation has apparently * gotten the name "DCC SEND ahead" which basically means to not give a damn * about those DCC ACKs and just send data as you please. This behaviour is * enabled by default. Note that this also means that packets may be as large * as the maximum segment size. */ #ifndef _DCC_H #define _DCC_H /* Send an ACK after receiving this amount of data */ #define DCC_PACKET_SIZE 1024 /* Time in seconds that a DCC transfer can be stalled before being aborted. * By handling this here individual protocols don't have to think about this. */ #define DCC_MAX_STALL 120 typedef struct dcc_file_transfer { struct im_connection *ic; /* * Depending in the status of the file transfer, this is either the socket that is * being listened on for connections, or the socket over which the file transfer is * taking place. */ int fd; /* * IDs returned by b_input_add for watch_ing over the above socket. */ gint watch_in; /* readable */ gint watch_out; /* writable */ /* the progress watcher cancels any file transfer if nothing happens within DCC_MAX_STALL */ gint progress_timeout; size_t progress_bytes_last; /* * The total amount of bytes that have been sent to the irc client. */ size_t bytes_sent; /* * Handle the wonderful sadly-not-deprecated ACKs. */ guint32 acked; int acked_len; /* imc's handle */ file_transfer_t *ft; /* if we're receiving, this is the sender's socket address */ struct sockaddr_storage saddr; /* set to true if the protocol has finished * (i.e. called imcb_file_finished) */ int proto_finished; } dcc_file_transfer_t; file_transfer_t *dccs_send_start( struct im_connection *ic, irc_user_t *iu, const char *file_name, size_t file_size ); void dcc_canceled( file_transfer_t *file, char *reason ); gboolean dccs_send_write( file_transfer_t *file, char *data, unsigned int data_size ); file_transfer_t *dcc_request( struct im_connection *ic, char* const* ctcp ); void dcc_finish( file_transfer_t *file ); void dcc_close( file_transfer_t *file ); gboolean dccs_recv_start( file_transfer_t *ft ); #endif bitlbee-3.2.1/nick.h0000644000175000017500000000337712245474076013625 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ /* Some stuff to fetch, save and handle nicknames for your buddies */ /* 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 */ void nick_set_raw( account_t *acc, const char *handle, const char *nick ); void nick_set( bee_user_t *bu, const char *nick ); char *nick_get( bee_user_t *bu ); char *nick_gen( bee_user_t *bu ); void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] ); int nick_saved( bee_user_t *bu ); void nick_del( bee_user_t *bu ); void nick_strip( irc_t *irc, char *nick ); gboolean nick_ok( irc_t *irc, const char *nick ); int nick_lc( irc_t *irc, char *nick ); int nick_uc( irc_t *irc, char *nick ); int nick_cmp( irc_t *irc, const char *a, const char *b ); char *nick_dup( const char *nick ); bitlbee-3.2.1/commands.h0000644000175000017500000000305412245474076014472 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ /* User manager (root) commands */ /* 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 _COMMANDS_H #define _COMMANDS_H #include "bitlbee.h" typedef struct command { char *command; int required_parameters; void (*execute)(irc_t *, char **args); int flags; } command_t; extern command_t root_commands[]; #define IRC_CMD_PRE_LOGIN 1 #define IRC_CMD_LOGGED_IN 2 #define IRC_CMD_OPER_ONLY 4 #define IRC_CMD_TO_MASTER 8 #define IPC_CMD_TO_CHILDREN 1 #endif bitlbee-3.2.1/set.c0000644000175000017500000001357712245474076013472 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 Wilmer van der Gaast and others * \********************************************************************/ /* Some stuff to register, handle and save user preferences */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" /* Used to use NULL for this, but NULL is actually a "valid" value. */ char *SET_INVALID = "nee"; set_t *set_add( set_t **head, const char *key, const char *def, set_eval eval, void *data ) { set_t *s = set_find( head, key ); /* Possibly the setting already exists. If it doesn't exist yet, we create it. If it does, we'll just change the default. */ if( !s ) { if( ( s = *head ) ) { /* Sorted insertion. Special-case insertion at the start. */ if( strcmp( key, s->key ) < 0 ) { s = g_new0( set_t, 1 ); s->next = *head; *head = s; } else { while( s->next && strcmp( key, s->next->key ) > 0 ) s = s->next; set_t *last_next = s->next; s->next = g_new0( set_t, 1 ); s = s->next; s->next = last_next; } } else { s = *head = g_new0( set_t, 1 ); } s->key = g_strdup( key ); } if( s->def ) { g_free( s->def ); s->def = NULL; } if( def ) s->def = g_strdup( def ); s->eval = eval; s->data = data; return s; } set_t *set_find( set_t **head, const char *key ) { set_t *s = *head; while( s ) { if( g_strcasecmp( s->key, key ) == 0 || ( s->old_key && g_strcasecmp( s->old_key, key ) == 0 ) ) break; s = s->next; } return s; } char *set_getstr( set_t **head, const char *key ) { set_t *s = set_find( head, key ); if( !s || ( !s->value && !s->def ) ) return NULL; return set_value( s ); } int set_getint( set_t **head, const char *key ) { char *s = set_getstr( head, key ); int i = 0; if( !s ) return 0; if( sscanf( s, "%d", &i ) != 1 ) return 0; return i; } int set_getbool( set_t **head, const char *key ) { char *s = set_getstr( head, key ); if( !s ) return 0; return bool2int( s ); } int set_isvisible( set_t *set ) { /* the default value is not stored in value, only in def */ return !( ( set->flags & SET_HIDDEN ) || ( ( set->flags & SET_HIDDEN_DEFAULT ) && ( set->value == NULL ) ) ); } int set_setstr( set_t **head, const char *key, char *value ) { set_t *s = set_find( head, key ); char *nv = value; if( !s ) /* Used to do this, but it never really made sense. s = set_add( head, key, NULL, NULL, NULL ); */ return 0; if( value == NULL && ( s->flags & SET_NULL_OK ) == 0 ) return 0; /* Call the evaluator. For invalid values, evaluators should now return SET_INVALID, but previously this was NULL. Try to handle that too if NULL is not an allowed value for this setting. */ if( s->eval && ( ( nv = s->eval( s, value ) ) == SET_INVALID || ( ( s->flags & SET_NULL_OK ) == 0 && nv == NULL ) ) ) return 0; if( s->value ) { g_free( s->value ); s->value = NULL; } /* If there's a default setting and it's equal to what we're trying to set, stick with s->value = NULL. Otherwise, remember the setting. */ if( !s->def || ( strcmp( nv, s->def ) != 0 ) ) s->value = g_strdup( nv ); if( nv != value ) g_free( nv ); return 1; } int set_setint( set_t **head, const char *key, int value ) { char s[24]; /* Not quite 128-bit clean eh? ;-) */ g_snprintf( s, sizeof( s ), "%d", value ); return set_setstr( head, key, s ); } void set_del( set_t **head, const char *key ) { set_t *s = *head, *t = NULL; while( s ) { if( g_strcasecmp( s->key, key ) == 0 ) break; s = (t=s)->next; } if( s ) { if( t ) t->next = s->next; else *head = s->next; g_free( s->key ); g_free( s->old_key ); g_free( s->value ); g_free( s->def ); g_free( s ); } } int set_reset( set_t **head, const char *key ) { set_t *s; s = set_find( head, key ); if( s ) return set_setstr( head, key, s->def ); return 0; } char *set_eval_int( set_t *set, char *value ) { char *s = value; /* Allow a minus at the first position. */ if( *s == '-' ) s ++; for( ; *s; s ++ ) if( !isdigit( *s ) ) return SET_INVALID; return value; } char *set_eval_bool( set_t *set, char *value ) { return is_bool( value ) ? value : SET_INVALID; } char *set_eval_list( set_t *set, char *value ) { GSList *options = set->eval_data, *opt; for( opt = options; opt; opt = opt->next ) if( strcmp( value, opt->data ) == 0 ) return value; /* TODO: It'd be nice to show the user a list of allowed values, but we don't have enough context here to do that. May want to fix that. */ return NULL; } char *set_eval_to_char( set_t *set, char *value ) { char *s = g_new( char, 3 ); if( *value == ' ' ) strcpy( s, " " ); else sprintf( s, "%c ", *value ); return s; } char *set_eval_oauth( set_t *set, char *value ) { account_t *acc = set->data; if( bool2int( value ) && strcmp( acc->pass, PASSWORD_PENDING ) == 0 ) *acc->pass = '\0'; return set_eval_bool( set, value ); } bitlbee-3.2.1/dcc.c0000644000175000017500000003537412245474076013427 0ustar wilmerwilmer/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2007 Uli Meis * \********************************************************************/ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" #include "ft.h" #include "dcc.h" #include #include #include "lib/ftutil.h" /* * Since that might be confusing a note on naming: * * Generic dcc functions start with * * dcc_ * * ,methods specific to DCC SEND start with * * dccs_ * * . Since we can be on both ends of a DCC SEND, * functions specific to one end are called * * dccs_send and dccs_recv * * ,respectively. */ /* * used to generate a unique local transfer id the user * can use to reject/cancel transfers */ unsigned int local_transfer_id=1; /* * just for debugging the nr. of chunks we received from im-protocols and the total data */ unsigned int receivedchunks=0, receiveddata=0; void dcc_finish( file_transfer_t *file ); void dcc_close( file_transfer_t *file ); gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ); int dccs_send_request( struct dcc_file_transfer *df, irc_user_t *iu, struct sockaddr_storage *saddr ); gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond); gboolean dccs_recv_write_request( file_transfer_t *ft ); gboolean dcc_progress( gpointer data, gint fd, b_input_condition cond ); gboolean dcc_abort( dcc_file_transfer_t *df, char *reason, ... ); dcc_file_transfer_t *dcc_alloc_transfer( const char *file_name, size_t file_size, struct im_connection *ic ) { file_transfer_t *file = g_new0( file_transfer_t, 1 ); dcc_file_transfer_t *df = file->priv = g_new0( dcc_file_transfer_t, 1 ); file->file_size = file_size; file->file_name = g_strdup( file_name ); file->local_id = local_transfer_id++; file->ic = df->ic = ic; df->ft = file; return df; } /* This is where the sending magic starts... */ file_transfer_t *dccs_send_start( struct im_connection *ic, irc_user_t *iu, const char *file_name, size_t file_size ) { file_transfer_t *file; dcc_file_transfer_t *df; irc_t *irc = (irc_t *) ic->bee->ui_data; struct sockaddr_storage saddr; char *errmsg; char host[HOST_NAME_MAX]; char port[6]; if( file_size > global.conf->ft_max_size ) return NULL; df = dcc_alloc_transfer( file_name, file_size, ic ); file = df->ft; file->write = dccs_send_write; /* listen and request */ if( ( df->fd = ft_listen( &saddr, host, port, irc->fd, TRUE, &errmsg ) ) == -1 ) { dcc_abort( df, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s", errmsg ); return NULL; } file->status = FT_STATUS_LISTENING; if( !dccs_send_request( df, iu, &saddr ) ) return NULL; /* watch */ df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_send_proto, df ); irc->file_transfers = g_slist_prepend( irc->file_transfers, file ); df->progress_timeout = b_timeout_add( DCC_MAX_STALL * 1000, dcc_progress, df ); imcb_log( ic, "File transfer request from %s for %s (%zd kb).\n" "Accept the file transfer if you'd like the file. If you don't, " "issue the 'transfer reject' command.", iu->nick, file_name, file_size / 1024 ); return file; } /* Used pretty much everywhere in the code to abort a transfer */ gboolean dcc_abort( dcc_file_transfer_t *df, char *reason, ... ) { file_transfer_t *file = df->ft; va_list params; va_start( params, reason ); char *msg = g_strdup_vprintf( reason, params ); va_end( params ); file->status |= FT_STATUS_CANCELED; if( file->canceled ) file->canceled( file, msg ); imcb_log( df->ic, "File %s: DCC transfer aborted: %s", file->file_name, msg ); g_free( msg ); dcc_close( df->ft ); return FALSE; } gboolean dcc_progress( gpointer data, gint fd, b_input_condition cond ) { struct dcc_file_transfer *df = data; if( df->bytes_sent == df->progress_bytes_last ) { /* no progress. cancel */ if( df->bytes_sent == 0 ) return dcc_abort( df, "Couldn't establish transfer within %d seconds", DCC_MAX_STALL ); else return dcc_abort( df, "Transfer stalled for %d seconds at %d kb", DCC_MAX_STALL, df->bytes_sent / 1024 ); } df->progress_bytes_last = df->bytes_sent; return TRUE; } /* used extensively for socket operations */ #define ASSERTSOCKOP(op, msg) \ if( (op) == -1 ) \ return dcc_abort( df , msg ": %s", strerror( errno ) ); /* Creates the "DCC SEND" line and sends it to the server */ int dccs_send_request( struct dcc_file_transfer *df, irc_user_t *iu, struct sockaddr_storage *saddr ) { char ipaddr[INET6_ADDRSTRLEN]; const void *netaddr; int port; char *cmd; if( saddr->ss_family == AF_INET ) { struct sockaddr_in *saddr_ipv4 = ( struct sockaddr_in *) saddr; sprintf( ipaddr, "%d", ntohl( saddr_ipv4->sin_addr.s_addr ) ); port = saddr_ipv4->sin_port; } else { struct sockaddr_in6 *saddr_ipv6 = ( struct sockaddr_in6 *) saddr; netaddr = &saddr_ipv6->sin6_addr.s6_addr; port = saddr_ipv6->sin6_port; /* * Didn't find docs about this, but it seems that's the way irssi does it */ if( !inet_ntop( saddr->ss_family, netaddr, ipaddr, sizeof( ipaddr ) ) ) return dcc_abort( df, "inet_ntop failed: %s", strerror( errno ) ); } port = ntohs( port ); cmd = g_strdup_printf( "\001DCC SEND %s %s %u %zu\001", df->ft->file_name, ipaddr, port, df->ft->file_size ); irc_send_msg_raw( iu, "PRIVMSG", iu->irc->user->nick, cmd ); g_free( cmd ); return TRUE; } /* * After setup, the transfer itself is handled entirely by this function. * There are basically four things to handle: connect, receive, send, and error. */ gboolean dccs_send_proto( gpointer data, gint fd, b_input_condition cond ) { dcc_file_transfer_t *df = data; file_transfer_t *file = df->ft; if( ( cond & B_EV_IO_READ ) && ( file->status & FT_STATUS_LISTENING ) ) { struct sockaddr *clt_addr; socklen_t ssize = sizeof( clt_addr ); /* Connect */ ASSERTSOCKOP( df->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" ); closesocket( fd ); fd = df->fd; file->status = FT_STATUS_TRANSFERRING; sock_make_nonblocking( fd ); /* IM protocol callback */ if( file->accept ) file->accept( file ); /* reschedule for reading on new fd */ df->watch_in = b_input_add( fd, B_EV_IO_READ, dccs_send_proto, df ); return FALSE; } if( cond & B_EV_IO_READ ) { int ret; ASSERTSOCKOP( ret = recv( fd, ( (char*) &df->acked ) + df->acked_len, sizeof( df->acked ) - df->acked_len, 0 ), "Receiving" ); if( ret == 0 ) return dcc_abort( df, "Remote end closed connection" ); /* How likely is it that a 32-bit integer gets split accross packet boundaries? Chances are rarely 0 so let's be sure. */ if( ( df->acked_len = ( df->acked_len + ret ) % 4 ) > 0 ) return TRUE; df->acked = ntohl( df->acked ); /* If any of this is actually happening, the receiver should buy a new IRC client */ if ( df->acked > df->bytes_sent ) return dcc_abort( df, "Receiver magically received more bytes than sent ( %d > %d ) (BUG at receiver?)", df->acked, df->bytes_sent ); if ( df->acked < file->bytes_transferred ) return dcc_abort( df, "Receiver lost bytes? ( has %d, had %d ) (BUG at receiver?)", df->acked, file->bytes_transferred ); file->bytes_transferred = df->acked; if( file->bytes_transferred >= file->file_size ) { if( df->proto_finished ) dcc_finish( file ); return FALSE; } return TRUE; } return TRUE; } gboolean dccs_recv_start( file_transfer_t *ft ) { dcc_file_transfer_t *df = ft->priv; struct sockaddr_storage *saddr = &df->saddr; int fd; char ipaddr[INET6_ADDRSTRLEN]; socklen_t sa_len = saddr->ss_family == AF_INET ? sizeof( struct sockaddr_in ) : sizeof( struct sockaddr_in6 ); if( !ft->write ) return dcc_abort( df, "BUG: protocol didn't register write()" ); ASSERTSOCKOP( fd = df->fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening Socket" ); sock_make_nonblocking( fd ); if( ( connect( fd, (struct sockaddr *)saddr, sa_len ) == -1 ) && ( errno != EINPROGRESS ) ) return dcc_abort( df, "Connecting to %s:%d : %s", inet_ntop( saddr->ss_family, saddr->ss_family == AF_INET ? ( void* ) &( ( struct sockaddr_in *) saddr )->sin_addr.s_addr : ( void* ) &( ( struct sockaddr_in6 *) saddr )->sin6_addr.s6_addr, ipaddr, sizeof( ipaddr ) ), ntohs( saddr->ss_family == AF_INET ? ( ( struct sockaddr_in *) saddr )->sin_port : ( ( struct sockaddr_in6 *) saddr )->sin6_port ), strerror( errno ) ); ft->status = FT_STATUS_CONNECTING; /* watch */ df->watch_out = b_input_add( df->fd, B_EV_IO_WRITE, dccs_recv_proto, df ); ft->write_request = dccs_recv_write_request; df->progress_timeout = b_timeout_add( DCC_MAX_STALL * 1000, dcc_progress, df ); return TRUE; } gboolean dccs_recv_proto( gpointer data, gint fd, b_input_condition cond ) { dcc_file_transfer_t *df = data; file_transfer_t *ft = df->ft; if( ( cond & B_EV_IO_WRITE ) && ( ft->status & FT_STATUS_CONNECTING ) ) { ft->status = FT_STATUS_TRANSFERRING; //df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_recv_proto, df ); df->watch_out = 0; return FALSE; } if( cond & B_EV_IO_READ ) { int ret, done; ASSERTSOCKOP( ret = recv( fd, ft->buffer, sizeof( ft->buffer ), 0 ), "Receiving" ); if( ret == 0 ) return dcc_abort( df, "Remote end closed connection" ); if( !ft->write( df->ft, ft->buffer, ret ) ) return FALSE; df->bytes_sent += ret; done = df->bytes_sent >= ft->file_size; if( ( ( df->bytes_sent - ft->bytes_transferred ) > DCC_PACKET_SIZE ) || done ) { guint32 ack = htonl( ft->bytes_transferred = df->bytes_sent ); int ackret; ASSERTSOCKOP( ackret = send( fd, &ack, 4, 0 ), "Sending DCC ACK" ); if ( ackret != 4 ) return dcc_abort( df, "Error sending DCC ACK, sent %d instead of 4 bytes", ackret ); } if( df->bytes_sent == ret ) ft->started = time( NULL ); if( done ) { if( df->watch_out ) b_event_remove( df->watch_out ); if( df->proto_finished ) dcc_finish( ft ); df->watch_in = 0; return FALSE; } df->watch_in = 0; return FALSE; } return TRUE; } gboolean dccs_recv_write_request( file_transfer_t *ft ) { dcc_file_transfer_t *df = ft->priv; if( df->watch_in ) return dcc_abort( df, "BUG: write_request() called while watching" ); df->watch_in = b_input_add( df->fd, B_EV_IO_READ, dccs_recv_proto, df ); return TRUE; } gboolean dccs_send_can_write( gpointer data, gint fd, b_input_condition cond ) { struct dcc_file_transfer *df = data; df->watch_out = 0; df->ft->write_request( df->ft ); return FALSE; } /* * Incoming data. * */ gboolean dccs_send_write( file_transfer_t *file, char *data, unsigned int data_len ) { dcc_file_transfer_t *df = file->priv; int ret; receivedchunks++; receiveddata += data_len; if( df->watch_out ) return dcc_abort( df, "BUG: write() called while watching" ); ASSERTSOCKOP( ret = send( df->fd, data, data_len, 0 ), "Sending data" ); if( ret == 0 ) return dcc_abort( df, "Remote end closed connection" ); /* TODO: this should really not be fatal */ if( ret < data_len ) return dcc_abort( df, "send() sent %d instead of %d", ret, data_len ); if( df->bytes_sent == 0 ) file->started = time( NULL ); df->bytes_sent += ret; if( df->bytes_sent < df->ft->file_size ) df->watch_out = b_input_add( df->fd, B_EV_IO_WRITE, dccs_send_can_write, df ); return TRUE; } /* * Cleans up after a transfer. */ void dcc_close( file_transfer_t *file ) { dcc_file_transfer_t *df = file->priv; irc_t *irc = (irc_t *) df->ic->bee->ui_data; if( file->free ) file->free( file ); closesocket( df->fd ); if( df->watch_in ) b_event_remove( df->watch_in ); if( df->watch_out ) b_event_remove( df->watch_out ); if( df->progress_timeout ) b_event_remove( df->progress_timeout ); irc->file_transfers = g_slist_remove( irc->file_transfers, file ); g_free( df ); g_free( file->file_name ); g_free( file ); } void dcc_finish( file_transfer_t *file ) { dcc_file_transfer_t *df = file->priv; time_t diff = time( NULL ) - file->started ? : 1; file->status |= FT_STATUS_FINISHED; if( file->finished ) file->finished( file ); imcb_log( df->ic, "File %s transferred successfully at %d kb/s!" , file->file_name, (int) ( file->bytes_transferred / 1024 / diff ) ); dcc_close( file ); } /* * DCC SEND * * filename can be in "" or not. If it is, " can probably be escaped... * IP can be an unsigned int (IPV4) or something else (IPV6) * */ file_transfer_t *dcc_request( struct im_connection *ic, char* const* ctcp ) { irc_t *irc = (irc_t *) ic->bee->ui_data; file_transfer_t *ft; dcc_file_transfer_t *df; int gret; size_t filesize; if( ctcp[5] != NULL && sscanf( ctcp[4], "%zd", &filesize ) == 1 && /* Just int. validation. */ sscanf( ctcp[5], "%zd", &filesize ) == 1 ) { char *filename, *host, *port; struct addrinfo hints, *rp; filename = ctcp[2]; host = ctcp[3]; while( *host && isdigit( *host ) ) host++; /* Just digits? */ if( *host == '\0' ) { struct in_addr ipaddr = { .s_addr = htonl( atoll( ctcp[3] ) ) }; host = inet_ntoa( ipaddr ); } else { /* Contains non-numbers, hopefully an IPV6 address */ host = ctcp[3]; } port = ctcp[4]; filesize = atoll( ctcp[5] ); memset( &hints, 0, sizeof ( struct addrinfo ) ); hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICSERV; if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) ) { imcb_log( ic, "DCC: getaddrinfo() failed with %s " "when parsing incoming 'DCC SEND': " "host %s, port %s", gai_strerror( gret ), host, port ); return NULL; } df = dcc_alloc_transfer( filename, filesize, ic ); ft = df->ft; ft->sending = TRUE; memcpy( &df->saddr, rp->ai_addr, rp->ai_addrlen ); freeaddrinfo( rp ); irc->file_transfers = g_slist_prepend( irc->file_transfers, ft ); return ft; } else imcb_log( ic, "DCC: couldnt parse `DCC SEND' line" ); return NULL; } bitlbee-3.2.1/set.h0000644000175000017500000001127312245474076013466 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2006 Wilmer van der Gaast and others * \********************************************************************/ /* Some stuff to register, handle and save user preferences */ /* 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 __SET_H__ #define __SET_H__ struct set; /* This used to be specific to irc_t structures, but it's more generic now (so it can also be used for account_t structs). It's pretty simple, but so far pretty useful. In short, it just keeps a linked list of settings/variables and it also remembers a default value for every setting. And to prevent the user from setting invalid values, you can write an evaluator function for every setting, which can check a new value and block it by returning NULL, or replace it by returning a new value. See struct set.eval. */ typedef char *(*set_eval) ( struct set *set, char *value ); extern char *SET_INVALID; typedef enum { SET_NOSAVE = 0x0001, /* Don't save this setting (i.e. stored elsewhere). */ SET_NULL_OK = 0x0100, /* set->value == NULL is allowed. */ SET_HIDDEN = 0x0200, /* Don't show up in setting lists. Mostly for internal storage. */ SET_PASSWORD = 0x0400, /* Value shows up in settings list as "********". */ SET_HIDDEN_DEFAULT = 0x0800, /* Hide unless changed from default. */ } set_flags_t; typedef struct set { void *data; /* Here you can save a pointer to the object this settings belongs to. */ char *key; char *old_key; /* Previously known as; for smooth upgrades. */ char *value; char *def; /* Default value. If the set_setstr() function notices a new value is exactly the same as the default, value gets set to NULL. So when you read a setting, don't forget about this! In fact, you should only read values using set_getstr/int(). */ set_flags_t flags; /* Mostly defined per user. */ /* Eval: Returns SET_INVALID if the value is incorrect, exactly the passed value variable, or a corrected value. In case of the latter, set_setstr() will free() the returned string! */ set_eval eval; void *eval_data; struct set *next; } set_t; #define set_value( set ) ((set)->value) ? ((set)->value) : ((set)->def) /* Should be pretty clear. */ set_t *set_add( set_t **head, const char *key, const char *def, set_eval eval, void *data ); /* Returns the raw set_t. Might be useful sometimes. */ set_t *set_find( set_t **head, const char *key ); /* Returns a pointer to the string value of this setting. Don't modify the returned string, and don't free() it! */ G_MODULE_EXPORT char *set_getstr( set_t **head, const char *key ); /* Get an integer. In previous versions set_getint() was also used to read boolean values, but this SHOULD be done with set_getbool() now! */ G_MODULE_EXPORT int set_getint( set_t **head, const char *key ); G_MODULE_EXPORT int set_getbool( set_t **head, const char *key ); /* set_setstr() strdup()s the given value, so after using this function you can free() it, if you want. */ int set_setstr( set_t **head, const char *key, char *value ); int set_setint( set_t **head, const char *key, int value ); void set_del( set_t **head, const char *key ); int set_reset( set_t **head, const char *key ); /* returns true if a setting shall be shown to the user */ int set_isvisible( set_t *set ); /* Two very useful generic evaluators. */ char *set_eval_int( set_t *set, char *value ); char *set_eval_bool( set_t *set, char *value ); /* Another more complicated one. */ char *set_eval_list( set_t *set, char *value ); /* Some not very generic evaluators that really shouldn't be here... */ char *set_eval_to_char( set_t *set, char *value ); char *set_eval_oauth( set_t *set, char *value ); #endif /* __SET_H__ */ bitlbee-3.2.1/storage_xml.c0000644000175000017500000002720412245474076015213 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* Storage backend that uses an XMLish format for all 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 */ #define BITLBEE_CORE #include "bitlbee.h" #include "base64.h" #include "arc.h" #include "md5.h" #include "xmltree.h" #include typedef enum { XML_PASS_CHECK_ONLY = -1, XML_PASS_UNKNOWN = 0, XML_PASS_WRONG, XML_PASS_OK } xml_pass_st; /* To make it easier later when extending the format: */ #define XML_FORMAT_VERSION "1" struct xml_parsedata { irc_t *irc; char given_nick[MAX_NICK_LENGTH+1]; char *given_pass; }; static void xml_init( void ) { if( g_access( global.conf->configdir, F_OK ) != 0 ) log_message( LOGLVL_WARNING, "The configuration directory `%s' does not exist. Configuration won't be saved.", global.conf->configdir ); else if( g_access( global.conf->configdir, F_OK ) != 0 || g_access( global.conf->configdir, W_OK ) != 0 ) log_message( LOGLVL_WARNING, "Permission problem: Can't read/write from/to `%s'.", global.conf->configdir ); } static void handle_settings( struct xt_node *node, set_t **head ) { struct xt_node *c; for( c = node->children; ( c = xt_find_node( c, "setting" ) ); c = c->next ) { char *name = xt_find_attr( c, "name" ); if( !name ) continue; if( strcmp( node->name, "account" ) == 0 ) { set_t *s = set_find( head, name ); if( s && ( s->flags & ACC_SET_ONLINE_ONLY ) ) continue; /* U can't touch this! */ } set_setstr( head, name, c->text ); } } static xt_status handle_account( struct xt_node *node, gpointer data ) { struct xml_parsedata *xd = data; char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag; char *pass_b64 = NULL; unsigned char *pass_cr = NULL; int pass_len; struct prpl *prpl = NULL; account_t *acc; struct xt_node *c; handle = xt_find_attr( node, "handle" ); pass_b64 = xt_find_attr( node, "password" ); server = xt_find_attr( node, "server" ); autoconnect = xt_find_attr( node, "autoconnect" ); tag = xt_find_attr( node, "tag" ); protocol = xt_find_attr( node, "protocol" ); if( protocol ) prpl = find_protocol( protocol ); if( !handle || !pass_b64 || !protocol || !prpl ) return XT_ABORT; else if( ( pass_len = base64_decode( pass_b64, (unsigned char**) &pass_cr ) ) && arc_decode( pass_cr, pass_len, &password, xd->given_pass ) >= 0 ) { acc = account_add( xd->irc->b, prpl, handle, password ); if( server ) set_setstr( &acc->set, "server", server ); if( autoconnect ) set_setstr( &acc->set, "auto_connect", autoconnect ); if( tag ) set_setstr( &acc->set, "tag", tag ); } else return XT_ABORT; g_free( pass_cr ); g_free( password ); handle_settings( node, &acc->set ); for( c = node->children; ( c = xt_find_node( c, "buddy" ) ); c = c->next ) { char *handle, *nick; handle = xt_find_attr( c, "handle" ); nick = xt_find_attr( c, "nick" ); if( handle && nick ) nick_set_raw( acc, handle, nick ); else return XT_ABORT; } return XT_HANDLED; } static xt_status handle_channel( struct xt_node *node, gpointer data ) { struct xml_parsedata *xd = data; irc_channel_t *ic; char *name, *type; name = xt_find_attr( node, "name" ); type = xt_find_attr( node, "type" ); if( !name || !type ) return XT_ABORT; /* The channel may exist already, for example if it's &bitlbee. Also, it's possible that the user just reconnected and the IRC client already rejoined all channels it was in. They should still get the right settings. */ if( ( ic = irc_channel_by_name( xd->irc, name ) ) || ( ic = irc_channel_new( xd->irc, name ) ) ) set_setstr( &ic->set, "type", type ); handle_settings( node, &ic->set ); return XT_HANDLED; } static const struct xt_handler_entry handlers[] = { { "account", "user", handle_account, }, { "channel", "user", handle_channel, }, { NULL, NULL, NULL, }, }; static storage_status_t xml_load_real( irc_t *irc, const char *my_nick, const char *password, xml_pass_st action ) { struct xml_parsedata xd[1]; char *fn, buf[2048]; int fd, st; struct xt_parser *xp = NULL; struct xt_node *node; storage_status_t ret = STORAGE_OTHER_ERROR; xd->irc = irc; strncpy( xd->given_nick, my_nick, MAX_NICK_LENGTH ); xd->given_nick[MAX_NICK_LENGTH] = '\0'; nick_lc( NULL, xd->given_nick ); xd->given_pass = (char*) password; fn = g_strconcat( global.conf->configdir, xd->given_nick, ".xml", NULL ); if( ( fd = open( fn, O_RDONLY ) ) < 0 ) { ret = STORAGE_NO_SUCH_USER; goto error; } xp = xt_new( handlers, xd ); while( ( st = read( fd, buf, sizeof( buf ) ) ) > 0 ) { st = xt_feed( xp, buf, st ); if( st != 1 ) break; } close( fd ); if( st != 0 ) goto error; node = xp->root; if( node == NULL || node->next != NULL || strcmp( node->name, "user" ) != 0 ) goto error; { char *nick = xt_find_attr( node, "nick" ); char *pass = xt_find_attr( node, "password" ); if( !nick || !pass ) { goto error; } else if( ( st = md5_verify_password( xd->given_pass, pass ) ) != 0 ) { ret = STORAGE_INVALID_PASSWORD; goto error; } } if( action == XML_PASS_CHECK_ONLY ) { ret = STORAGE_OK; goto error; } /* DO NOT call xt_handle() before verifying the password! */ if( xt_handle( xp, NULL, 1 ) == XT_HANDLED ) ret = STORAGE_OK; handle_settings( node, &xd->irc->b->set ); error: xt_free( xp ); g_free( fn ); return ret; } static storage_status_t xml_load( irc_t *irc, const char *password ) { return xml_load_real( irc, irc->user->nick, password, XML_PASS_UNKNOWN ); } static storage_status_t xml_check_pass( const char *my_nick, const char *password ) { return xml_load_real( NULL, my_nick, password, XML_PASS_CHECK_ONLY ); } static gboolean xml_generate_nick( gpointer key, gpointer value, gpointer data ); static void xml_generate_settings( struct xt_node *cur, set_t **head ); struct xt_node *xml_generate( irc_t *irc ) { char *pass_buf = NULL; account_t *acc; md5_byte_t pass_md5[21]; md5_state_t md5_state; GSList *l; struct xt_node *root, *cur; /* Generate a salted md5sum of the password. Use 5 bytes for the salt (to prevent dictionary lookups of passwords) to end up with a 21- byte password hash, more convenient for base64 encoding. */ random_bytes( pass_md5 + 16, 5 ); md5_init( &md5_state ); md5_append( &md5_state, (md5_byte_t*) irc->password, strlen( irc->password ) ); md5_append( &md5_state, pass_md5 + 16, 5 ); /* Add the salt. */ md5_finish( &md5_state, pass_md5 ); /* Save the hash in base64-encoded form. */ pass_buf = base64_encode( pass_md5, 21 ); root = cur = xt_new_node( "user", NULL, NULL ); xt_add_attr( cur, "nick", irc->user->nick ); xt_add_attr( cur, "password", pass_buf ); xt_add_attr( cur, "version", XML_FORMAT_VERSION ); g_free( pass_buf ); xml_generate_settings( cur, &irc->b->set ); for( acc = irc->b->accounts; acc; acc = acc->next ) { unsigned char *pass_cr; char *pass_b64; int pass_len; pass_len = arc_encode( acc->pass, strlen( acc->pass ), (unsigned char**) &pass_cr, irc->password, 12 ); pass_b64 = base64_encode( pass_cr, pass_len ); g_free( pass_cr ); cur = xt_new_node( "account", NULL, NULL ); xt_add_attr( cur, "protocol", acc->prpl->name ); xt_add_attr( cur, "handle", acc->user ); xt_add_attr( cur, "password", pass_b64 ); xt_add_attr( cur, "autoconnect", acc->auto_connect ? "true" : "false" ); xt_add_attr( cur, "tag", acc->tag ); if( acc->server && acc->server[0] ) xt_add_attr( cur, "server", acc->server ); g_free( pass_b64 ); /* This probably looks pretty strange. g_hash_table_foreach is quite a PITA already (but it can't get much better in C without using #define, I'm afraid), and it doesn't seem to be possible to abort the foreach on write errors, so instead let's use the _find function and return TRUE on write errors. Which means, if we found something, there was an error. :-) */ g_hash_table_find( acc->nicks, xml_generate_nick, cur ); xml_generate_settings( cur, &acc->set ); xt_add_child( root, cur ); } for( l = irc->channels; l; l = l->next ) { irc_channel_t *ic = l->data; if( ic->flags & IRC_CHANNEL_TEMP ) continue; cur = xt_new_node( "channel", NULL, NULL ); xt_add_attr( cur, "name", ic->name ); xt_add_attr( cur, "type", set_getstr( &ic->set, "type" ) ); xml_generate_settings( cur, &ic->set ); xt_add_child( root, cur ); } return root; } static gboolean xml_generate_nick( gpointer key, gpointer value, gpointer data ) { struct xt_node *node = xt_new_node( "buddy", NULL, NULL ); xt_add_attr( node, "handle", key ); xt_add_attr( node, "nick", value ); xt_add_child( (struct xt_node *) data, node ); return FALSE; } static void xml_generate_settings( struct xt_node *cur, set_t **head ) { set_t *set; for( set = *head; set; set = set->next ) if( set->value && !( set->flags & SET_NOSAVE ) ) { struct xt_node *xset; xt_add_child( cur, xset = xt_new_node( "setting", set->value, NULL ) ); xt_add_attr( xset, "name", set->key ); } } static storage_status_t xml_save( irc_t *irc, int overwrite ) { storage_status_t ret = STORAGE_OK; char path[512], *path2 = NULL, *xml = NULL; struct xt_node *tree = NULL; size_t len; int fd; path2 = g_strdup( irc->user->nick ); nick_lc( NULL, path2 ); g_snprintf( path, sizeof( path ) - 20, "%s%s%s", global.conf->configdir, path2, ".xml" ); g_free( path2 ); if( !overwrite && g_access( path, F_OK ) == 0 ) return STORAGE_ALREADY_EXISTS; strcat( path, ".XXXXXX" ); if( ( fd = mkstemp( path ) ) < 0 ) { irc_rootmsg( irc, "Error while opening configuration file." ); return STORAGE_OTHER_ERROR; } tree = xml_generate( irc ); xml = xt_to_string_i( tree ); len = strlen( xml ); if( write( fd, xml, len ) != len || fsync( fd ) != 0 || /* #559 */ close( fd ) != 0 ) goto error; path2 = g_strndup( path, strlen( path ) - 7 ); if( rename( path, path2 ) != 0 ) { g_free( path2 ); goto error; } g_free( path2 ); goto finish; error: irc_rootmsg( irc, "Write error. Disk full?" ); ret = STORAGE_OTHER_ERROR; finish: close( fd ); unlink( path ); g_free( xml ); xt_free_node( tree ); return ret; } static storage_status_t xml_remove( const char *nick, const char *password ) { char s[512], *lc; storage_status_t status; status = xml_check_pass( nick, password ); if( status != STORAGE_OK ) return status; lc = g_strdup( nick ); nick_lc( NULL, lc ); g_snprintf( s, 511, "%s%s%s", global.conf->configdir, lc, ".xml" ); g_free( lc ); if( unlink( s ) == -1 ) return STORAGE_OTHER_ERROR; return STORAGE_OK; } storage_t storage_xml = { .name = "xml", .init = xml_init, .check_pass = xml_check_pass, .remove = xml_remove, .load = xml_load, .save = xml_save }; bitlbee-3.2.1/help.h0000644000175000017500000000325712245474076013626 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ /* Help file control */ /* 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 _HELP_H #define _HELP_H typedef union { off_t file_offset; char *mem_offset; } help_off_t; typedef struct help { int fd; time_t mtime; char *title; help_off_t offset; int length; struct help *next; } help_t; G_GNUC_MALLOC help_t *help_init( help_t **help, const char *helpfile ); void help_free( help_t **help ); char *help_get( help_t **help, char *title ); int help_add_mem( help_t **help, const char *title, const char *content_ ); char *help_get_whatsnew( help_t **help, int old ); #endif bitlbee-3.2.1/unix.c0000644000175000017500000002313412245474076013650 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* Main file (Unix specific part) */ /* 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 "arc.h" #include "base64.h" #include "commands.h" #include "protocols/nogaim.h" #include "help.h" #include "ipc.h" #include "lib/ssl_client.h" #include "md5.h" #include "misc.h" #include #include #include #include #include #include #include #if defined(OTR_BI) || defined(OTR_PI) #include "otr.h" #endif global_t global; /* Against global namespace pollution */ static void sighandler( int signal ); static int crypt_main( int argc, char *argv[] ); int main( int argc, char *argv[] ) { int i = 0; char *old_cwd = NULL; struct sigaction sig, old; /* Required to make iconv to ASCII//TRANSLIT work. This makes BitlBee system-locale-sensitive. :-( */ setlocale( LC_CTYPE, "" ); if( argc > 1 && strcmp( argv[1], "-x" ) == 0 ) return crypt_main( argc, argv ); log_init(); global.conf_file = g_strdup( CONF_FILE_DEF ); global.conf = conf_load( argc, argv ); if( global.conf == NULL ) return( 1 ); b_main_init(); /* libpurple doesn't like fork()s after initializing itself, so if we use it, do this init a little later (in case we're running in ForkDaemon mode). */ #ifndef WITH_PURPLE nogaim_init(); #endif /* Ugly Note: libotr and gnutls both use libgcrypt. libgcrypt has a process-global config state whose initialization happpens twice if libotr and gnutls are used together. libotr installs custom memory management functions for libgcrypt while our gnutls module uses the defaults. Therefore we initialize OTR after SSL. *sigh* */ ssl_init(); #ifdef OTR_BI otr_init(); #endif /* And in case OTR is loaded as a plugin, it'll also get loaded after this point. */ srand( time( NULL ) ^ getpid() ); global.helpfile = g_strdup( HELP_FILE ); if( help_init( &global.help, global.helpfile ) == NULL ) log_message( LOGLVL_WARNING, "Error opening helpfile %s.", HELP_FILE ); global.storage = storage_init( global.conf->primary_storage, global.conf->migrate_storage ); if( global.storage == NULL ) { log_message( LOGLVL_ERROR, "Unable to load storage backend '%s'", global.conf->primary_storage ); return( 1 ); } if( global.conf->runmode == RUNMODE_INETD ) { log_link( LOGLVL_ERROR, LOGOUTPUT_IRC ); log_link( LOGLVL_WARNING, LOGOUTPUT_IRC ); i = bitlbee_inetd_init(); log_message( LOGLVL_INFO, "%s %s starting in inetd mode.", PACKAGE, BITLBEE_VERSION ); } else if( global.conf->runmode == RUNMODE_DAEMON ) { log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE ); log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE ); i = bitlbee_daemon_init(); log_message( LOGLVL_INFO, "%s %s starting in daemon mode.", PACKAGE, BITLBEE_VERSION ); } else if( global.conf->runmode == RUNMODE_FORKDAEMON ) { log_link( LOGLVL_ERROR, LOGOUTPUT_CONSOLE ); log_link( LOGLVL_WARNING, LOGOUTPUT_CONSOLE ); /* In case the operator requests a restart, we need this. */ old_cwd = g_malloc( 256 ); if( getcwd( old_cwd, 255 ) == NULL ) { log_message( LOGLVL_WARNING, "Could not save current directory: %s", strerror( errno ) ); g_free( old_cwd ); old_cwd = NULL; } i = bitlbee_daemon_init(); log_message( LOGLVL_INFO, "%s %s starting in forking daemon mode.", PACKAGE, BITLBEE_VERSION ); } if( i != 0 ) return( i ); if( ( global.conf->user && *global.conf->user ) && ( global.conf->runmode == RUNMODE_DAEMON || global.conf->runmode == RUNMODE_FORKDAEMON ) && ( !getuid() || !geteuid() ) ) { struct passwd *pw = NULL; pw = getpwnam( global.conf->user ); if( pw ) { initgroups( global.conf->user, pw->pw_gid ); setgid( pw->pw_gid ); setuid( pw->pw_uid ); } else { log_message( LOGLVL_WARNING, "Failed to look up user %s.", global.conf->user ); } } /* Catch some signals to tell the user what's happening before quitting */ memset( &sig, 0, sizeof( sig ) ); sig.sa_handler = sighandler; sigaction( SIGCHLD, &sig, &old ); sigaction( SIGPIPE, &sig, &old ); sig.sa_flags = SA_RESETHAND; sigaction( SIGINT, &sig, &old ); sigaction( SIGILL, &sig, &old ); sigaction( SIGBUS, &sig, &old ); sigaction( SIGFPE, &sig, &old ); sigaction( SIGSEGV, &sig, &old ); sigaction( SIGTERM, &sig, &old ); sigaction( SIGQUIT, &sig, &old ); sigaction( SIGXCPU, &sig, &old ); if( !getuid() || !geteuid() ) log_message( LOGLVL_WARNING, "BitlBee is running with root privileges. Why?" ); b_main_run(); /* Mainly good for restarting, to make sure we close the help.txt fd. */ help_free( &global.help ); if( global.restart ) { char *fn = ipc_master_save_state(); char *env; env = g_strdup_printf( "_BITLBEE_RESTART_STATE=%s", fn ); putenv( env ); g_free( fn ); /* Looks like env should *not* be freed here as putenv doesn't make a copy. Odd. */ i = chdir( old_cwd ); close( global.listen_socket ); if( execv( argv[0], argv ) == -1 ) /* Apparently the execve() failed, so let's just jump back into our own/current main(). */ /* Need more cleanup code to make this work. */ return 1; /* main( argc, argv ); */ } return( 0 ); } static int crypt_main( int argc, char *argv[] ) { int pass_len; unsigned char *pass_cr, *pass_cl; if( argc < 4 || ( strcmp( argv[2], "hash" ) != 0 && strcmp( argv[2], "unhash" ) != 0 && argc < 5 ) ) { printf( "Supported:\n" " %s -x enc \n" " %s -x dec \n" " %s -x hash \n" " %s -x unhash \n" " %s -x chkhash \n", argv[0], argv[0], argv[0], argv[0], argv[0] ); } else if( strcmp( argv[2], "enc" ) == 0 ) { pass_len = arc_encode( argv[4], strlen( argv[4] ), (unsigned char**) &pass_cr, argv[3], 12 ); printf( "%s\n", base64_encode( pass_cr, pass_len ) ); } else if( strcmp( argv[2], "dec" ) == 0 ) { pass_len = base64_decode( argv[4], (unsigned char**) &pass_cr ); arc_decode( pass_cr, pass_len, (char**) &pass_cl, argv[3] ); printf( "%s\n", pass_cl ); } else if( strcmp( argv[2], "hash" ) == 0 ) { md5_byte_t pass_md5[21]; md5_state_t md5_state; random_bytes( pass_md5 + 16, 5 ); md5_init( &md5_state ); md5_append( &md5_state, (md5_byte_t*) argv[3], strlen( argv[3] ) ); md5_append( &md5_state, pass_md5 + 16, 5 ); /* Add the salt. */ md5_finish( &md5_state, pass_md5 ); printf( "%s\n", base64_encode( pass_md5, 21 ) ); } else if( strcmp( argv[2], "unhash" ) == 0 ) { printf( "Hash %s submitted to a massive Beowulf cluster of\n" "overclocked 486s. Expect your answer next year somewhere around this time. :-)\n", argv[3] ); } else if( strcmp( argv[2], "chkhash" ) == 0 ) { char *hash = strncmp( argv[3], "md5:", 4 ) == 0 ? argv[3] + 4 : argv[3]; int st = md5_verify_password( argv[4], hash ); printf( "Hash %s given password.\n", st == 0 ? "matches" : "does not match" ); return st; } return 0; } static void sighandler( int signal ) { /* FIXME: Calling log_message() here is not a very good idea! */ if( signal == SIGTERM || signal == SIGQUIT || signal == SIGINT ) { static int first = 1; if( first ) { /* We don't know what we were doing when this signal came in. It's not safe to touch the user data now (not to mention writing them to disk), so add a timer. */ log_message( LOGLVL_ERROR, "SIGTERM received, cleaning up process." ); b_timeout_add( 1, (b_event_handler) bitlbee_shutdown, NULL ); first = 0; } else { /* Well, actually, for now we'll never need this part because this signal handler will never be called more than once in a session for a non-SIGPIPE signal... But just in case we decide to change that: */ log_message( LOGLVL_ERROR, "SIGTERM received twice, so long for a clean shutdown." ); raise( signal ); } } else if( signal == SIGCHLD ) { pid_t pid; int st; while( ( pid = waitpid( 0, &st, WNOHANG ) ) > 0 ) { if( WIFSIGNALED( st ) ) log_message( LOGLVL_INFO, "Client %d terminated normally. (status = %d)", (int) pid, WEXITSTATUS( st ) ); else if( WIFEXITED( st ) ) log_message( LOGLVL_INFO, "Client %d killed by signal %d.", (int) pid, WTERMSIG( st ) ); } } else if( signal != SIGPIPE ) { log_message( LOGLVL_ERROR, "Fatal signal received: %d. That's probably a bug.", signal ); raise( signal ); } } double gettime() { struct timeval time[1]; gettimeofday( time, 0 ); return( (double) time->tv_sec + (double) time->tv_usec / 1000000 ); } bitlbee-3.2.1/root_commands.c0000644000175000017500000011243112245474076015530 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 Wilmer van der Gaast and others * \********************************************************************/ /* User manager (root) commands */ /* 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 */ #define BITLBEE_CORE #include "commands.h" #include "bitlbee.h" #include "help.h" #include "ipc.h" void root_command_string( irc_t *irc, char *command ) { root_command( irc, split_command_parts( command ) ); } #define MIN_ARGS( x, y... ) \ do \ { \ int blaat; \ for( blaat = 0; blaat <= x; blaat ++ ) \ if( cmd[blaat] == NULL ) \ { \ irc_rootmsg( irc, "Not enough parameters given (need %d).", x ); \ return y; \ } \ } while( 0 ) void root_command( irc_t *irc, char *cmd[] ) { int i, len; if( !cmd[0] ) return; len = strlen( cmd[0] ); for( i = 0; root_commands[i].command; i++ ) if( g_strncasecmp( root_commands[i].command, cmd[0], len ) == 0 ) { if( root_commands[i+1].command && g_strncasecmp( root_commands[i+1].command, cmd[0], len ) == 0 ) /* Only match on the first letters if the match is unique. */ break; MIN_ARGS( root_commands[i].required_parameters ); root_commands[i].execute( irc, cmd ); return; } irc_rootmsg( irc, "Unknown command: %s. Please use \x02help commands\x02 to get a list of available commands.", cmd[0] ); } static void cmd_help( irc_t *irc, char **cmd ) { char param[80]; int i; char *s; memset( param, 0, sizeof(param) ); for ( i = 1; (cmd[i] != NULL && ( strlen(param) < (sizeof(param)-1) ) ); i++ ) { if ( i != 1 ) // prepend space except for the first parameter strcat(param, " "); strncat( param, cmd[i], sizeof(param) - strlen(param) - 1 ); } s = help_get( &(global.help), param ); if( !s ) s = help_get( &(global.help), "" ); if( s ) { irc_rootmsg( irc, "%s", s ); g_free( s ); } else { irc_rootmsg( irc, "Error opening helpfile." ); } } static void cmd_account( irc_t *irc, char **cmd ); static void bitlbee_whatsnew( irc_t *irc ); static void cmd_identify( irc_t *irc, char **cmd ) { storage_status_t status; gboolean load = TRUE; char *password = cmd[1]; if( irc->status & USTATUS_IDENTIFIED ) { irc_rootmsg( irc, "You're already logged in." ); return; } if( cmd[1] == NULL ) { } else if( strncmp( cmd[1], "-no", 3 ) == 0 ) { load = FALSE; password = cmd[2]; if( password == NULL ) irc->status |= OPER_HACK_IDENTIFY_NOLOAD; } else if( strncmp( cmd[1], "-force", 6 ) == 0 ) { password = cmd[2]; if( password == NULL ) irc->status |= OPER_HACK_IDENTIFY_FORCE; } else if( irc->b->accounts != NULL ) { irc_rootmsg( irc, "You're trying to identify yourself, but already have " "at least one IM account set up. " "Use \x02identify -noload\x02 or \x02identify -force\x02 " "instead (see \x02help identify\x02)." ); return; } if( password == NULL ) { irc_rootmsg( irc, "About to identify, use /OPER to enter the password" ); irc->status |= OPER_HACK_IDENTIFY; return; } if( load ) status = storage_load( irc, password ); else status = storage_check_pass( irc->user->nick, password ); switch (status) { case STORAGE_INVALID_PASSWORD: irc_rootmsg( irc, "Incorrect password" ); break; case STORAGE_NO_SUCH_USER: irc_rootmsg( irc, "The nick is (probably) not registered" ); break; case STORAGE_OK: irc_rootmsg( irc, "Password accepted%s", load ? ", settings and accounts loaded" : "" ); irc_setpass( irc, password ); irc->status |= USTATUS_IDENTIFIED; irc_umode_set( irc, "+R", 1 ); bitlbee_whatsnew( irc ); /* The following code is a bit hairy now. With takeover support, we shouldn't immediately auto_connect in case we're going to offer taking over an existing session. Do it in 200ms since that should give the parent process enough time to come back to us. */ if( load ) { irc_channel_auto_joins( irc, NULL ); if( !set_getbool( &irc->default_channel->set, "auto_join" ) ) irc_channel_del_user( irc->default_channel, irc->user, IRC_CDU_PART, "auto_join disabled " "for this channel." ); if( set_getbool( &irc->b->set, "auto_connect" ) ) irc->login_source_id = b_timeout_add( 200, cmd_identify_finish, irc ); } /* If ipc_child_identify() returns FALSE, it means we're already sure that there's no takeover target (only possible in 1-process daemon mode). Start auto_connect immediately. */ if( !ipc_child_identify( irc ) && load ) cmd_identify_finish( irc, 0, 0 ); break; case STORAGE_OTHER_ERROR: default: irc_rootmsg( irc, "Unknown error while loading configuration" ); break; } } gboolean cmd_identify_finish( gpointer data, gint fd, b_input_condition cond ) { char *account_on[] = { "account", "on", NULL }; irc_t *irc = data; if( set_getbool( &irc->b->set, "auto_connect" ) ) cmd_account( irc, account_on ); b_event_remove( irc->login_source_id ); irc->login_source_id = -1; return FALSE; } static void cmd_register( irc_t *irc, char **cmd ) { char s[16]; if( global.conf->authmode == AUTHMODE_REGISTERED ) { irc_rootmsg( irc, "This server does not allow registering new accounts" ); return; } if( cmd[1] == NULL ) { irc_rootmsg( irc, "About to register, use /OPER to enter the password" ); irc->status |= OPER_HACK_REGISTER; return; } switch( storage_save( irc, cmd[1], FALSE ) ) { case STORAGE_ALREADY_EXISTS: irc_rootmsg( irc, "Nick is already registered" ); break; case STORAGE_OK: irc_rootmsg( irc, "Account successfully created" ); irc_setpass( irc, cmd[1] ); irc->status |= USTATUS_IDENTIFIED; irc_umode_set( irc, "+R", 1 ); /* Set this var now, or anyone who logs in to his/her newly created account for the first time gets the whatsnew story. */ g_snprintf( s, sizeof( s ), "%d", BITLBEE_VERSION_CODE ); set_setstr( &irc->b->set, "last_version", s ); break; default: irc_rootmsg( irc, "Error registering" ); break; } } static void cmd_drop( irc_t *irc, char **cmd ) { storage_status_t status; status = storage_remove (irc->user->nick, cmd[1]); switch (status) { case STORAGE_NO_SUCH_USER: irc_rootmsg( irc, "That account does not exist" ); break; case STORAGE_INVALID_PASSWORD: irc_rootmsg( irc, "Password invalid" ); break; case STORAGE_OK: irc_setpass( irc, NULL ); irc->status &= ~USTATUS_IDENTIFIED; irc_umode_set( irc, "-R", 1 ); irc_rootmsg( irc, "Account `%s' removed", irc->user->nick ); break; default: irc_rootmsg( irc, "Error: `%d'", status ); break; } } static void cmd_save( irc_t *irc, char **cmd ) { if( ( irc->status & USTATUS_IDENTIFIED ) == 0 ) irc_rootmsg( irc, "Please create an account first (see \x02help register\x02)" ); else if( storage_save( irc, NULL, TRUE ) == STORAGE_OK ) irc_rootmsg( irc, "Configuration saved" ); else irc_rootmsg( irc, "Configuration could not be saved!" ); } static void cmd_showset( irc_t *irc, set_t **head, char *key ) { set_t *set; char *val; if( ( val = set_getstr( head, key ) ) ) irc_rootmsg( irc, "%s = `%s'", key, val ); else if( !( set = set_find( head, key ) ) ) { irc_rootmsg( irc, "Setting `%s' does not exist.", key ); if( *head == irc->b->set ) irc_rootmsg( irc, "It might be an account or channel setting. " "See \x02help account set\x02 and \x02help channel set\x02." ); } else if( set->flags & SET_PASSWORD ) irc_rootmsg( irc, "%s = `********' (hidden)", key ); else irc_rootmsg( irc, "%s is empty", key ); } typedef set_t** (*cmd_set_findhead)( irc_t*, char* ); typedef int (*cmd_set_checkflags)( irc_t*, set_t *set ); static int cmd_set_real( irc_t *irc, char **cmd, set_t **head, cmd_set_checkflags checkflags ) { char *set_name = NULL, *value = NULL; gboolean del = FALSE; if( cmd[1] && g_strncasecmp( cmd[1], "-del", 4 ) == 0 ) { MIN_ARGS( 2, 0 ); set_name = cmd[2]; del = TRUE; } else { set_name = cmd[1]; value = cmd[2]; } if( set_name && ( value || del ) ) { set_t *s = set_find( head, set_name ); int st; if( s && checkflags && checkflags( irc, s ) == 0 ) return 0; if( del ) st = set_reset( head, set_name ); else st = set_setstr( head, set_name, value ); if( set_getstr( head, set_name ) == NULL && set_find( head, set_name ) ) { /* This happens when changing the passwd, for example. Showing these msgs instead gives slightly clearer feedback. */ if( st ) irc_rootmsg( irc, "Setting changed successfully" ); else irc_rootmsg( irc, "Failed to change setting" ); } else { cmd_showset( irc, head, set_name ); } } else if( set_name ) { cmd_showset( irc, head, set_name ); } else { set_t *s = *head; while( s ) { if( set_isvisible( s ) ) cmd_showset( irc, &s, s->key ); s = s->next; } } return 1; } static int cmd_account_set_checkflags( irc_t *irc, set_t *s ) { account_t *a = s->data; if( a->ic && s && s->flags & ACC_SET_OFFLINE_ONLY ) { irc_rootmsg( irc, "This setting can only be changed when the account is %s-line", "off" ); return 0; } else if( !a->ic && s && s->flags & ACC_SET_ONLINE_ONLY ) { irc_rootmsg( irc, "This setting can only be changed when the account is %s-line", "on" ); return 0; } return 1; } static void cmd_account( irc_t *irc, char **cmd ) { account_t *a; int len; if( global.conf->authmode == AUTHMODE_REGISTERED && !( irc->status & USTATUS_IDENTIFIED ) ) { irc_rootmsg( irc, "This server only accepts registered users" ); return; } len = strlen( cmd[1] ); if( len >= 1 && g_strncasecmp( cmd[1], "add", len ) == 0 ) { struct prpl *prpl; MIN_ARGS( 3 ); if( cmd[4] == NULL ) { for( a = irc->b->accounts; a; a = a->next ) if( strcmp( a->pass, PASSWORD_PENDING ) == 0 ) { irc_rootmsg( irc, "Enter password for account %s " "first (use /OPER)", a->tag ); return; } irc->status |= OPER_HACK_ACCOUNT_ADD; } prpl = find_protocol( cmd[2] ); if( prpl == NULL ) { irc_rootmsg( irc, "Unknown protocol" ); return; } for( a = irc->b->accounts; a; a = a->next ) if( a->prpl == prpl && prpl->handle_cmp( a->user, cmd[3] ) == 0 ) irc_rootmsg( irc, "Warning: You already have an account with " "protocol `%s' and username `%s'. Are you accidentally " "trying to add it twice?", prpl->name, cmd[3] ); a = account_add( irc->b, prpl, cmd[3], cmd[4] ? cmd[4] : PASSWORD_PENDING ); if( cmd[5] ) { irc_rootmsg( irc, "Warning: Passing a servername/other flags to `account add' " "is now deprecated. Use `account set' instead." ); set_setstr( &a->set, "server", cmd[5] ); } irc_rootmsg( irc, "Account successfully added with tag %s", a->tag ); if( cmd[4] == NULL ) { set_t *oauth = set_find( &a->set, "oauth" ); if( oauth && bool2int( set_value( oauth ) ) ) { *a->pass = '\0'; irc_rootmsg( irc, "No need to enter a password for this " "account since it's using OAuth" ); } else { irc_rootmsg( irc, "You can now use the /OPER command to " "enter the password" ); if( oauth ) irc_rootmsg( irc, "Alternatively, enable OAuth if " "the account supports it: account %s " "set oauth on", a->tag ); } } return; } else if( len >= 1 && g_strncasecmp( cmd[1], "list", len ) == 0 ) { int i = 0; if( strchr( irc->umode, 'b' ) ) irc_rootmsg( irc, "Account list:" ); for( a = irc->b->accounts; a; a = a->next ) { char *con; if( a->ic && ( a->ic->flags & OPT_LOGGED_IN ) ) con = " (connected)"; else if( a->ic ) con = " (connecting)"; else if( a->reconnect ) con = " (awaiting reconnect)"; else con = ""; irc_rootmsg( irc, "%2d (%s): %s, %s%s", i, a->tag, a->prpl->name, a->user, con ); i ++; } irc_rootmsg( irc, "End of account list" ); return; } else if( cmd[2] ) { /* Try the following two only if cmd[2] == NULL */ } else if( len >= 2 && g_strncasecmp( cmd[1], "on", len ) == 0 ) { if ( irc->b->accounts ) { irc_rootmsg( irc, "Trying to get all accounts connected..." ); for( a = irc->b->accounts; a; a = a->next ) if( !a->ic && a->auto_connect ) { if( strcmp( a->pass, PASSWORD_PENDING ) == 0 ) irc_rootmsg( irc, "Enter password for account %s " "first (use /OPER)", a->tag ); else account_on( irc->b, a ); } } else { irc_rootmsg( irc, "No accounts known. Use `account add' to add one." ); } return; } else if( len >= 2 && g_strncasecmp( cmd[1], "off", len ) == 0 ) { irc_rootmsg( irc, "Deactivating all active (re)connections..." ); for( a = irc->b->accounts; a; a = a->next ) { if( a->ic ) account_off( irc->b, a ); else if( a->reconnect ) cancel_auto_reconnect( a ); } return; } MIN_ARGS( 2 ); len = strlen( cmd[2] ); /* At least right now, don't accept on/off/set/del as account IDs even if they're a proper match, since people not familiar with the new syntax yet may get a confusing/nasty surprise. */ if( g_strcasecmp( cmd[1], "on" ) == 0 || g_strcasecmp( cmd[1], "off" ) == 0 || g_strcasecmp( cmd[1], "set" ) == 0 || g_strcasecmp( cmd[1], "del" ) == 0 || ( a = account_get( irc->b, cmd[1] ) ) == NULL ) { irc_rootmsg( irc, "Could not find account `%s'.", cmd[1] ); return; } if( len >= 1 && g_strncasecmp( cmd[2], "del", len ) == 0 ) { if( a->ic ) { irc_rootmsg( irc, "Account is still logged in, can't delete" ); } else { account_del( irc->b, a ); irc_rootmsg( irc, "Account deleted" ); } } else if( len >= 2 && g_strncasecmp( cmd[2], "on", len ) == 0 ) { if( a->ic ) irc_rootmsg( irc, "Account already online" ); else if( strcmp( a->pass, PASSWORD_PENDING ) == 0 ) irc_rootmsg( irc, "Enter password for account %s " "first (use /OPER)", a->tag ); else account_on( irc->b, a ); } else if( len >= 2 && g_strncasecmp( cmd[2], "off", len ) == 0 ) { if( a->ic ) { account_off( irc->b, a ); } else if( a->reconnect ) { cancel_auto_reconnect( a ); irc_rootmsg( irc, "Reconnect cancelled" ); } else { irc_rootmsg( irc, "Account already offline" ); } } else if( len >= 1 && g_strncasecmp( cmd[2], "set", len ) == 0 ) { cmd_set_real( irc, cmd + 2, &a->set, cmd_account_set_checkflags ); } else { irc_rootmsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account", cmd[2] ); } } static void cmd_channel( irc_t *irc, char **cmd ) { irc_channel_t *ic; int len; len = strlen( cmd[1] ); if( len >= 1 && g_strncasecmp( cmd[1], "list", len ) == 0 ) { GSList *l; int i = 0; if( strchr( irc->umode, 'b' ) ) irc_rootmsg( irc, "Channel list:" ); for( l = irc->channels; l; l = l->next ) { irc_channel_t *ic = l->data; irc_rootmsg( irc, "%2d. %s, %s channel%s", i, ic->name, set_getstr( &ic->set, "type" ), ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "" ); i ++; } irc_rootmsg( irc, "End of channel list" ); return; } if( ( ic = irc_channel_get( irc, cmd[1] ) ) == NULL ) { /* If this doesn't match any channel, maybe this is the short syntax (only works when used inside a channel). */ if( ( ic = irc->root->last_channel ) && ( len = strlen( cmd[1] ) ) && g_strncasecmp( cmd[1], "set", len ) == 0 ) cmd_set_real( irc, cmd + 1, &ic->set, NULL ); else irc_rootmsg( irc, "Could not find channel `%s'", cmd[1] ); return; } MIN_ARGS( 2 ); len = strlen( cmd[2] ); if( len >= 1 && g_strncasecmp( cmd[2], "set", len ) == 0 ) { cmd_set_real( irc, cmd + 2, &ic->set, NULL ); } else if( len >= 1 && g_strncasecmp( cmd[2], "del", len ) == 0 ) { if( !( ic->flags & IRC_CHANNEL_JOINED ) && ic != ic->irc->default_channel ) { irc_rootmsg( irc, "Channel %s deleted.", ic->name ); irc_channel_free( ic ); } else irc_rootmsg( irc, "Couldn't remove channel (main channel %s or " "channels you're still in cannot be deleted).", irc->default_channel->name ); } else { irc_rootmsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel", cmd[1] ); } } static void cmd_add( irc_t *irc, char **cmd ) { account_t *a; int add_on_server = 1; char *handle = NULL, *s; if( g_strcasecmp( cmd[1], "-tmp" ) == 0 ) { MIN_ARGS( 3 ); add_on_server = 0; cmd ++; } if( !( a = account_get( irc->b, cmd[1] ) ) ) { irc_rootmsg( irc, "Invalid account" ); return; } else if( !( a->ic && ( a->ic->flags & OPT_LOGGED_IN ) ) ) { irc_rootmsg( irc, "That account is not on-line" ); return; } if( cmd[3] ) { if( !nick_ok( irc, cmd[3] ) ) { irc_rootmsg( irc, "The requested nick `%s' is invalid", cmd[3] ); return; } else if( irc_user_by_name( irc, cmd[3] ) ) { irc_rootmsg( irc, "The requested nick `%s' already exists", cmd[3] ); return; } else { nick_set_raw( a, cmd[2], cmd[3] ); } } if( ( a->flags & ACC_FLAG_HANDLE_DOMAINS ) && cmd[2][0] != '_' && ( !( s = strchr( cmd[2], '@' ) ) || s[1] == '\0' ) ) { /* If there's no @ or it's the last char, append the user's domain name now. Exclude handles starting with a _ so adding _xmlconsole will keep working. */ if( s ) *s = '\0'; if( ( s = strchr( a->user, '@' ) ) ) cmd[2] = handle = g_strconcat( cmd[2], s, NULL ); } if( add_on_server ) { irc_channel_t *ic; char *s, *group = NULL;; if( ( ic = irc->root->last_channel ) && ( s = set_getstr( &ic->set, "fill_by" ) ) && strcmp( s, "group" ) == 0 && ( group = set_getstr( &ic->set, "group" ) ) ) irc_rootmsg( irc, "Adding `%s' to contact list (group %s)", cmd[2], group ); else irc_rootmsg( irc, "Adding `%s' to contact list", cmd[2] ); a->prpl->add_buddy( a->ic, cmd[2], group ); } else { bee_user_t *bu; irc_user_t *iu; /* Only for add -tmp. For regular adds, this callback will be called once the IM server confirms. */ if( ( bu = bee_user_new( irc->b, a->ic, cmd[2], BEE_USER_LOCAL ) ) && ( iu = bu->ui_data ) ) irc_rootmsg( irc, "Temporarily assigned nickname `%s' " "to contact `%s'", iu->nick, cmd[2] ); } g_free( handle ); } static void cmd_remove( irc_t *irc, char **cmd ) { irc_user_t *iu; bee_user_t *bu; char *s; if( !( iu = irc_user_by_name( irc, cmd[1] ) ) || !( bu = iu->bu ) ) { irc_rootmsg( irc, "Buddy `%s' not found", cmd[1] ); return; } s = g_strdup( bu->handle ); bu->ic->acc->prpl->remove_buddy( bu->ic, bu->handle, NULL ); nick_del( bu ); if( g_slist_find( irc->users, iu ) ) bee_user_free( irc->b, bu ); irc_rootmsg( irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1] ); g_free( s ); return; } static void cmd_info( irc_t *irc, char **cmd ) { struct im_connection *ic; account_t *a; if( !cmd[2] ) { irc_user_t *iu = irc_user_by_name( irc, cmd[1] ); if( !iu || !iu->bu ) { irc_rootmsg( irc, "Nick `%s' does not exist", cmd[1] ); return; } ic = iu->bu->ic; cmd[2] = iu->bu->handle; } else if( !( a = account_get( irc->b, cmd[1] ) ) ) { irc_rootmsg( irc, "Invalid account" ); return; } else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) ) { irc_rootmsg( irc, "That account is not on-line" ); return; } if( !ic->acc->prpl->get_info ) { irc_rootmsg( irc, "Command `%s' not supported by this protocol", cmd[0] ); } else { ic->acc->prpl->get_info( ic, cmd[2] ); } } static void cmd_rename( irc_t *irc, char **cmd ) { irc_user_t *iu, *old; gboolean del = g_strcasecmp( cmd[1], "-del" ) == 0; iu = irc_user_by_name( irc, cmd[del ? 2 : 1] ); if( iu == NULL ) { irc_rootmsg( irc, "Nick `%s' does not exist", cmd[1] ); } else if( del ) { if( iu->bu ) bee_irc_user_nick_reset( iu ); irc_rootmsg( irc, "Nickname reset to `%s'", iu->nick ); } else if( iu == irc->user ) { irc_rootmsg( irc, "Use /nick to change your own nickname" ); } else if( !nick_ok( irc, cmd[2] ) ) { irc_rootmsg( irc, "Nick `%s' is invalid", cmd[2] ); } else if( ( old = irc_user_by_name( irc, cmd[2] ) ) && old != iu ) { irc_rootmsg( irc, "Nick `%s' already exists", cmd[2] ); } else { if( !irc_user_set_nick( iu, cmd[2] ) ) { irc_rootmsg( irc, "Error while changing nick" ); return; } if( iu == irc->root ) { /* If we're called internally (user did "set root_nick"), let's not go O(INF). :-) */ if( strcmp( cmd[0], "set_rename" ) != 0 ) set_setstr( &irc->b->set, "root_nick", cmd[2] ); } else if( iu->bu ) { nick_set( iu->bu, cmd[2] ); } irc_rootmsg( irc, "Nick successfully changed" ); } } char *set_eval_root_nick( set_t *set, char *new_nick ) { irc_t *irc = set->data; if( strcmp( irc->root->nick, new_nick ) != 0 ) { char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL }; cmd_rename( irc, cmd ); } return strcmp( irc->root->nick, new_nick ) == 0 ? new_nick : SET_INVALID; } static void cmd_block( irc_t *irc, char **cmd ) { struct im_connection *ic; account_t *a; if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic ) { char *format; GSList *l; if( strchr( irc->umode, 'b' ) != NULL ) format = "%s\t%s"; else format = "%-32.32s %-16.16s"; irc_rootmsg( irc, format, "Handle", "Nickname" ); for( l = a->ic->deny; l; l = l->next ) { bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data ); irc_user_t *iu = bu ? bu->ui_data : NULL; irc_rootmsg( irc, format, l->data, iu ? iu->nick : "(none)" ); } irc_rootmsg( irc, "End of list." ); return; } else if( !cmd[2] ) { irc_user_t *iu = irc_user_by_name( irc, cmd[1] ); if( !iu || !iu->bu ) { irc_rootmsg( irc, "Nick `%s' does not exist", cmd[1] ); return; } ic = iu->bu->ic; cmd[2] = iu->bu->handle; } else if( !( a = account_get( irc->b, cmd[1] ) ) ) { irc_rootmsg( irc, "Invalid account" ); return; } else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) ) { irc_rootmsg( irc, "That account is not on-line" ); return; } if( !ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit ) { irc_rootmsg( irc, "Command `%s' not supported by this protocol", cmd[0] ); } else { imc_rem_allow( ic, cmd[2] ); imc_add_block( ic, cmd[2] ); irc_rootmsg( irc, "Buddy `%s' moved from allow- to block-list", cmd[2] ); } } static void cmd_allow( irc_t *irc, char **cmd ) { struct im_connection *ic; account_t *a; if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic ) { char *format; GSList *l; if( strchr( irc->umode, 'b' ) != NULL ) format = "%s\t%s"; else format = "%-32.32s %-16.16s"; irc_rootmsg( irc, format, "Handle", "Nickname" ); for( l = a->ic->permit; l; l = l->next ) { bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data ); irc_user_t *iu = bu ? bu->ui_data : NULL; irc_rootmsg( irc, format, l->data, iu ? iu->nick : "(none)" ); } irc_rootmsg( irc, "End of list." ); return; } else if( !cmd[2] ) { irc_user_t *iu = irc_user_by_name( irc, cmd[1] ); if( !iu || !iu->bu ) { irc_rootmsg( irc, "Nick `%s' does not exist", cmd[1] ); return; } ic = iu->bu->ic; cmd[2] = iu->bu->handle; } else if( !( a = account_get( irc->b, cmd[1] ) ) ) { irc_rootmsg( irc, "Invalid account" ); return; } else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) ) { irc_rootmsg( irc, "That account is not on-line" ); return; } if( !ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit ) { irc_rootmsg( irc, "Command `%s' not supported by this protocol", cmd[0] ); } else { imc_rem_block( ic, cmd[2] ); imc_add_allow( ic, cmd[2] ); irc_rootmsg( irc, "Buddy `%s' moved from block- to allow-list", cmd[2] ); } } static void cmd_yesno( irc_t *irc, char **cmd ) { query_t *q = NULL; int numq = 0; if( irc->queries == NULL ) { /* Alright, alright, let's add a tiny easter egg here. */ static irc_t *last_irc = NULL; static time_t last_time = 0; static int times = 0; static const char *msg[] = { "Oh yeah, that's right.", "Alright, alright. Now go back to work.", "Buuuuuuuuuuuuuuuurp... Excuse me!", "Yes?", "No?", }; if( last_irc == irc && time( NULL ) - last_time < 15 ) { if( ( ++times >= 3 ) ) { irc_rootmsg( irc, "%s", msg[rand()%(sizeof(msg)/sizeof(char*))] ); last_irc = NULL; times = 0; return; } } else { last_time = time( NULL ); last_irc = irc; times = 0; } irc_rootmsg( irc, "Did I ask you something?" ); return; } /* If there's an argument, the user seems to want to answer another question than the first/last (depending on the query_order setting) one. */ if( cmd[1] ) { if( sscanf( cmd[1], "%d", &numq ) != 1 ) { irc_rootmsg( irc, "Invalid query number" ); return; } for( q = irc->queries; q; q = q->next, numq -- ) if( numq == 0 ) break; if( !q ) { irc_rootmsg( irc, "Uhm, I never asked you something like that..." ); return; } } if( g_strcasecmp( cmd[0], "yes" ) == 0 ) query_answer( irc, q, 1 ); else if( g_strcasecmp( cmd[0], "no" ) == 0 ) query_answer( irc, q, 0 ); } static void cmd_set( irc_t *irc, char **cmd ) { cmd_set_real( irc, cmd, &irc->b->set, NULL ); } static void cmd_blist( irc_t *irc, char **cmd ) { int online = 0, away = 0, offline = 0; GSList *l; char s[256]; char *format; int n_online = 0, n_away = 0, n_offline = 0; if( cmd[1] && g_strcasecmp( cmd[1], "all" ) == 0 ) online = offline = away = 1; else if( cmd[1] && g_strcasecmp( cmd[1], "offline" ) == 0 ) offline = 1; else if( cmd[1] && g_strcasecmp( cmd[1], "away" ) == 0 ) away = 1; else if( cmd[1] && g_strcasecmp( cmd[1], "online" ) == 0 ) online = 1; else online = away = 1; if( strchr( irc->umode, 'b' ) != NULL ) format = "%s\t%s\t%s"; else format = "%-16.16s %-40.40s %s"; irc_rootmsg( irc, format, "Nick", "Handle/Account", "Status" ); if( irc->root->last_channel && strcmp( set_getstr( &irc->root->last_channel->set, "type" ), "control" ) != 0 ) irc->root->last_channel = NULL; for( l = irc->users; l; l = l->next ) { irc_user_t *iu = l->data; bee_user_t *bu = iu->bu; if( !bu || ( irc->root->last_channel && !irc_channel_wants_user( irc->root->last_channel, iu ) ) || ( bu->flags & ( BEE_USER_ONLINE | BEE_USER_AWAY ) ) != BEE_USER_ONLINE ) continue; if( online == 1 ) { char st[256] = "Online"; if( bu->status_msg ) g_snprintf( st, sizeof( st ) - 1, "Online (%s)", bu->status_msg ); g_snprintf( s, sizeof( s ) - 1, "%s %s", bu->handle, bu->ic->acc->tag ); irc_rootmsg( irc, format, iu->nick, s, st ); } n_online ++; } for( l = irc->users; l; l = l->next ) { irc_user_t *iu = l->data; bee_user_t *bu = iu->bu; if( !bu || ( irc->root->last_channel && !irc_channel_wants_user( irc->root->last_channel, iu ) ) || !( bu->flags & BEE_USER_ONLINE ) || !( bu->flags & BEE_USER_AWAY ) ) continue; if( away == 1 ) { g_snprintf( s, sizeof( s ) - 1, "%s %s", bu->handle, bu->ic->acc->tag ); irc_rootmsg( irc, format, iu->nick, s, irc_user_get_away( iu ) ); } n_away ++; } for( l = irc->users; l; l = l->next ) { irc_user_t *iu = l->data; bee_user_t *bu = iu->bu; if( !bu || ( irc->root->last_channel && !irc_channel_wants_user( irc->root->last_channel, iu ) ) || bu->flags & BEE_USER_ONLINE ) continue; if( offline == 1 ) { g_snprintf( s, sizeof( s ) - 1, "%s %s", bu->handle, bu->ic->acc->tag ); irc_rootmsg( irc, format, iu->nick, s, "Offline" ); } n_offline ++; } irc_rootmsg( irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online, n_away, n_offline ); } static void cmd_qlist( irc_t *irc, char **cmd ) { query_t *q = irc->queries; int num; if( !q ) { irc_rootmsg( irc, "There are no pending questions." ); return; } irc_rootmsg( irc, "Pending queries:" ); for( num = 0; q; q = q->next, num ++ ) if( q->ic ) /* Not necessary yet, but it might come later */ irc_rootmsg( irc, "%d, %s: %s", num, q->ic->acc->tag, q->question ); else irc_rootmsg( irc, "%d, BitlBee: %s", num, q->question ); } static void cmd_chat( irc_t *irc, char **cmd ) { account_t *acc; if( g_strcasecmp( cmd[1], "add" ) == 0 ) { char *channel, *s; struct irc_channel *ic; MIN_ARGS( 3 ); if( !( acc = account_get( irc->b, cmd[2] ) ) ) { irc_rootmsg( irc, "Invalid account" ); return; } else if( !acc->prpl->chat_join ) { irc_rootmsg( irc, "Named chatrooms not supported on that account." ); return; } if( cmd[4] == NULL ) { channel = g_strdup( cmd[3] ); if( ( s = strchr( channel, '@' ) ) ) *s = 0; } else { channel = g_strdup( cmd[4] ); } if( strchr( CTYPES, channel[0] ) == NULL ) { s = g_strdup_printf( "#%s", channel ); g_free( channel ); channel = s; irc_channel_name_strip( channel ); } if( ( ic = irc_channel_new( irc, channel ) ) && set_setstr( &ic->set, "type", "chat" ) && set_setstr( &ic->set, "chat_type", "room" ) && set_setstr( &ic->set, "account", cmd[2] ) && set_setstr( &ic->set, "room", cmd[3] ) ) { irc_rootmsg( irc, "Chatroom successfully added." ); } else { if( ic ) irc_channel_free( ic ); irc_rootmsg( irc, "Could not add chatroom." ); } g_free( channel ); } else if( g_strcasecmp( cmd[1], "with" ) == 0 ) { irc_user_t *iu; MIN_ARGS( 2 ); if( ( iu = irc_user_by_name( irc, cmd[2] ) ) && iu->bu && iu->bu->ic->acc->prpl->chat_with ) { if( !iu->bu->ic->acc->prpl->chat_with( iu->bu->ic, iu->bu->handle ) ) { irc_rootmsg( irc, "(Possible) failure while trying to open " "a groupchat with %s.", iu->nick ); } } else { irc_rootmsg( irc, "Can't open a groupchat with %s.", cmd[2] ); } } else if( g_strcasecmp( cmd[1], "list" ) == 0 || g_strcasecmp( cmd[1], "set" ) == 0 || g_strcasecmp( cmd[1], "del" ) == 0 ) { irc_rootmsg( irc, "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command." ); cmd_channel( irc, cmd ); } else { irc_rootmsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat", cmd[1] ); } } static void cmd_group( irc_t *irc, char **cmd ) { GSList *l; int len; len = strlen( cmd[1] ); if( g_strncasecmp( cmd[1], "list", len ) == 0 ) { int n = 0; if( strchr( irc->umode, 'b' ) ) irc_rootmsg( irc, "Group list:" ); for( l = irc->b->groups; l; l = l->next ) { bee_group_t *bg = l->data; irc_rootmsg( irc, "%d. %s", n ++, bg->name ); } irc_rootmsg( irc, "End of group list" ); } else if( g_strncasecmp(cmd[1], "info", len ) == 0 ) { bee_group_t *bg; int n = 0; MIN_ARGS(2); bg = bee_group_by_name( irc->b, cmd[2], FALSE ); if( bg ) { if( strchr(irc->umode, 'b') ) irc_rootmsg( irc, "Members of %s:", cmd[2] ); for( l = irc->b->users; l; l = l->next ) { bee_user_t *bu = l->data; if( bu->group == bg ) irc_rootmsg( irc, "%d. %s", n ++, bu->nick ? : bu->handle ); } irc_rootmsg( irc, "End of member list" ); } else irc_rootmsg( irc, "Unknown group: %s. Please use \x02group list\x02 to get a list of available groups.", cmd[2] ); } else { irc_rootmsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group", cmd[1] ); } } static void cmd_transfer( irc_t *irc, char **cmd ) { GSList *files = irc->file_transfers; enum { LIST, REJECT, CANCEL }; int subcmd = LIST; int fid; if( !files ) { irc_rootmsg( irc, "No pending transfers" ); return; } if( cmd[1] && ( strcmp( cmd[1], "reject" ) == 0 ) ) { subcmd = REJECT; } else if( cmd[1] && ( strcmp( cmd[1], "cancel" ) == 0 ) && cmd[2] && ( sscanf( cmd[2], "%d", &fid ) == 1 ) ) { subcmd = CANCEL; } for( ; files; files = g_slist_next( files ) ) { file_transfer_t *file = files->data; switch( subcmd ) { case LIST: if ( file->status == FT_STATUS_LISTENING ) irc_rootmsg( irc, "Pending file(id %d): %s (Listening...)", file->local_id, file->file_name); else { int kb_per_s = 0; time_t diff = time( NULL ) - file->started ? : 1; if ( ( file->started > 0 ) && ( file->bytes_transferred > 0 ) ) kb_per_s = file->bytes_transferred / 1024 / diff; irc_rootmsg( irc, "Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id, file->file_name, file->bytes_transferred/1024, file->file_size/1024, kb_per_s); } break; case REJECT: if( file->status == FT_STATUS_LISTENING ) { irc_rootmsg( irc, "Rejecting file transfer for %s", file->file_name ); imcb_file_canceled( file->ic, file, "Denied by user" ); } break; case CANCEL: if( file->local_id == fid ) { irc_rootmsg( irc, "Canceling file transfer for %s", file->file_name ); imcb_file_canceled( file->ic, file, "Canceled by user" ); } break; } } } static void cmd_nick( irc_t *irc, char **cmd ) { irc_rootmsg( irc, "This command is deprecated. Try: account %s set display_name", cmd[1] ); } /* Maybe this should be a stand-alone command as well? */ static void bitlbee_whatsnew( irc_t *irc ) { int last = set_getint( &irc->b->set, "last_version" ); char s[16], *msg; if( last >= BITLBEE_VERSION_CODE ) return; msg = help_get_whatsnew( &(global.help), last ); if( msg ) irc_rootmsg( irc, "%s: This seems to be your first time using this " "this version of BitlBee. Here's a list of new " "features you may like to know about:\n\n%s\n", irc->user->nick, msg ); g_free( msg ); g_snprintf( s, sizeof( s ), "%d", BITLBEE_VERSION_CODE ); set_setstr( &irc->b->set, "last_version", s ); } /* IMPORTANT: Keep this list sorted! The short command logic needs that. */ command_t root_commands[] = { { "account", 1, cmd_account, 0 }, { "add", 2, cmd_add, 0 }, { "allow", 1, cmd_allow, 0 }, { "blist", 0, cmd_blist, 0 }, { "block", 1, cmd_block, 0 }, { "channel", 1, cmd_channel, 0 }, { "chat", 1, cmd_chat, 0 }, { "drop", 1, cmd_drop, 0 }, { "ft", 0, cmd_transfer, 0 }, { "group", 1, cmd_group, 0 }, { "help", 0, cmd_help, 0 }, { "identify", 0, cmd_identify, 0 }, { "info", 1, cmd_info, 0 }, { "nick", 1, cmd_nick, 0 }, { "no", 0, cmd_yesno, 0 }, { "qlist", 0, cmd_qlist, 0 }, { "register", 0, cmd_register, 0 }, { "remove", 1, cmd_remove, 0 }, { "rename", 2, cmd_rename, 0 }, { "save", 0, cmd_save, 0 }, { "set", 0, cmd_set, 0 }, { "transfer", 0, cmd_transfer, 0 }, { "yes", 0, cmd_yesno, 0 }, /* Not expecting too many plugins adding root commands so just make a dumb array with some empty entried at the end. */ { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, }; static const int num_root_commands = sizeof( root_commands ) / sizeof( command_t ); gboolean root_command_add( const char *command, int params, void (*func)(irc_t *, char **args), int flags ) { int i; if( root_commands[num_root_commands-2].command ) /* Planning fail! List is full. */ return FALSE; for( i = 0; root_commands[i].command; i++ ) { if( g_strcasecmp( root_commands[i].command, command ) == 0 ) return FALSE; else if( g_strcasecmp( root_commands[i].command, command ) > 0 ) break; } memmove( root_commands + i + 1, root_commands + i, sizeof( command_t ) * ( num_root_commands - i - 1 ) ); root_commands[i].command = g_strdup( command ); root_commands[i].required_parameters = params; root_commands[i].execute = func; root_commands[i].flags = flags; return TRUE; } bitlbee-3.2.1/lib/0000755000175000017500000000000012245477444013266 5ustar wilmerwilmerbitlbee-3.2.1/lib/base64.c0000644000175000017500000001124312245474076014515 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Base64 handling functions. encode_real() is mostly based on the y64 en- * * coder from libyahoo2. Moving it to a new file because it's getting big. * * * * Copyright 2006 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include #include #include "base64.h" static const char real_b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; char *tobase64(const char *text) { return base64_encode((const unsigned char *)text, strlen(text)); } char *base64_encode(const unsigned char *in, int len) { char *out; out = g_malloc((len + 2) /* the == padding */ / 3 /* every 3-byte block */ * 4 /* becomes a 4-byte one */ + 1); /* and of course, ASCIIZ! */ base64_encode_real((unsigned char*) in, len, (unsigned char*) out, real_b64); return out; } int base64_encode_real(const unsigned char *in, int inlen, unsigned char *out, const char *b64digits) { int outlen = 0; for (; inlen >= 3; inlen -= 3) { out[outlen++] = b64digits[in[0] >> 2]; out[outlen++] = b64digits[((in[0]<<4) & 0x30) | (in[1]>>4)]; out[outlen++] = b64digits[((in[1]<<2) & 0x3c) | (in[2]>>6)]; out[outlen++] = b64digits[in[2] & 0x3f]; in += 3; } if (inlen > 0) { out[outlen++] = b64digits[in[0] >> 2]; if (inlen > 1) { out[outlen++] = b64digits[((in[0]<<4) & 0x30) | (in[1]>>4)]; out[outlen++] = b64digits[((in[1]<<2) & 0x3c)]; } else { out[outlen++] = b64digits[((in[0]<<4) & 0x30)]; out[outlen++] = b64digits[64]; } out[outlen++] = b64digits[64]; } out[outlen] = 0; return outlen; } /* Just a simple wrapper, but usually not very convenient because of zero termination. */ char *frombase64(const char *in) { unsigned char *out; base64_decode(in, &out); return (char*) out; } /* FIXME: Lookup table stuff is not threadsafe! (But for now BitlBee is not threaded.) */ int base64_decode(const char *in, unsigned char **out) { static char b64rev[256] = { 0 }; int len, i; /* Create a reverse-lookup for the Base64 sequence. */ if( b64rev[0] == 0 ) { memset( b64rev, 0xff, 256 ); for( i = 0; i <= 64; i ++ ) b64rev[(int)real_b64[i]] = i; } len = strlen( in ); *out = g_malloc( ( len + 6 ) / 4 * 3 ); len = base64_decode_real( (unsigned char*) in, *out, b64rev ); *out = g_realloc( *out, len + 1 ); out[0][len] = 0; /* Zero termination can't hurt. */ return len; } int base64_decode_real(const unsigned char *in, unsigned char *out, char *b64rev) { int i, outlen = 0; for( i = 0; in[i] && in[i+1] && in[i+2] && in[i+3]; i += 4 ) { int sx; sx = b64rev[(int)in[i+0]]; if( sx >= 64 ) break; out[outlen] = ( sx << 2 ) & 0xfc; sx = b64rev[(int)in[i+1]]; if( sx >= 64 ) break; out[outlen] |= ( sx >> 4 ) & 0x03; outlen ++; out[outlen] = ( sx << 4 ) & 0xf0; sx = b64rev[(int)in[i+2]]; if( sx >= 64 ) break; out[outlen] |= ( sx >> 2 ) & 0x0f; outlen ++; out[outlen] = ( sx << 6 ) & 0xc0; sx = b64rev[(int)in[i+3]]; if( sx >= 64 ) break; out[outlen] |= sx; outlen ++; } /* If sx > 64 the base64 string was damaged. Should we ignore this? */ return outlen; } bitlbee-3.2.1/lib/Makefile0000644000175000017500000000162312245474076014726 0ustar wilmerwilmer########################### ## Makefile for BitlBee ## ## ## ## Copyright 2006 Lintux ## ########################### ### DEFINITIONS -include ../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)lib/ endif # [SH] Program variables objects = arc.o base64.o $(EVENT_HANDLER) ftutil.o http_client.o ini.o json.o json_util.o md5.o misc.o oauth.o oauth2.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o LFLAGS += -r # [SH] Phony targets all: lib.o check: all lcov: check gcov: gcov *.c .PHONY: all clean distclean clean: $(subdirs) rm -f *.o $(OUTFILE) core distclean: clean $(subdirs) rm -rf .depend ### MAIN PROGRAM lib.o: $(objects) $(subdirs) @echo '*' Linking lib.o @$(LD) $(LFLAGS) $(objects) -o lib.o $(objects): ../Makefile.settings Makefile $(objects): %.o: $(_SRCDIR_)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $(CFLAGS_BITLBEE) $< -o $@ -include .depend/*.d bitlbee-3.2.1/lib/ssl_client.h0000644000175000017500000001147412245474076015603 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ /* SSL 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 */ /* ssl_client makes it easier to open SSL connections to servers. (It doesn't offer SSL server functionality yet, but it could be useful to add it later.) Different ssl_client modules are available, and ssl_client tries to make them all behave the same. It's very simple and basic, it just imitates the proxy_connect() function from the Gaim libs and passes the socket to the program once the handshake is completed. */ #include #include "proxy.h" /* Some generic error codes. Especially SSL_AGAIN is important if you want to do asynchronous I/O. */ #define SSL_OK 0 #define SSL_NOHANDSHAKE 1 #define SSL_AGAIN 2 #define VERIFY_CERT_ERROR 2 #define VERIFY_CERT_INVALID 4 #define VERIFY_CERT_REVOKED 8 #define VERIFY_CERT_SIGNER_NOT_FOUND 16 #define VERIFY_CERT_SIGNER_NOT_CA 32 #define VERIFY_CERT_INSECURE_ALGORITHM 64 #define VERIFY_CERT_NOT_ACTIVATED 128 #define VERIFY_CERT_EXPIRED 256 #define VERIFY_CERT_WRONG_HOSTNAME 512 extern int ssl_errno; /* This is what your callback function should look like. */ typedef gboolean (*ssl_input_function)(gpointer, int, void*, b_input_condition); /* Perform any global initialization the SSL library might need. */ G_MODULE_EXPORT void ssl_init( void ); /* Connect to host:port, call the given function when the connection is ready to be used for SSL traffic. This is all done asynchronously, no blocking I/O! (Except for the DNS lookups, for now...) */ G_MODULE_EXPORT void *ssl_connect( char *host, int port, gboolean verify, ssl_input_function func, gpointer data ); /* Start an SSL session on an existing fd. Useful for STARTTLS functionality, for example in Jabber. */ G_MODULE_EXPORT void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data ); /* Obviously you need special read/write functions to read data. */ G_MODULE_EXPORT int ssl_read( void *conn, char *buf, int len ); G_MODULE_EXPORT int ssl_write( void *conn, const char *buf, int len ); /* Now needed by most SSL libs. See for more info: http://www.gnu.org/software/gnutls/manual/gnutls.html#index-gnutls_005frecord_005fcheck_005fpending-209 http://www.openssl.org/docs/ssl/SSL_pending.html Required because OpenSSL empties the TCP buffer completely but doesn't necessarily give us all the unencrypted data. Or maybe you didn't ask for all of it because your buffer is too small. Returns 0 if there's nothing left, 1 if there's more data. */ G_MODULE_EXPORT int ssl_pending( void *conn ); /* Abort the SSL connection and disconnect the socket. Do not use close() directly, both the SSL library and the peer will be unhappy! */ G_MODULE_EXPORT void ssl_disconnect( void *conn_ ); /* Get the fd for this connection, you will usually need it for event handling. */ G_MODULE_EXPORT int ssl_getfd( void *conn ); /* This function returns B_EV_IO_READ/WRITE. With SSL connections it's possible that something has to be read while actually were trying to write something (think about key exchange/refresh/etc). So when an SSL operation returned SSL_AGAIN, *always* use this function when adding an event handler to the queue. (And it should perform exactly the same action as the handler that just received the SSL_AGAIN.) */ G_MODULE_EXPORT b_input_condition ssl_getdirection( void *conn ); /* Converts a verification bitfield passed to ssl_input_function into a more useful string. Or NULL if it had no useful bits set. */ G_MODULE_EXPORT char *ssl_verify_strerror( int code ); G_MODULE_EXPORT size_t ssl_des3_encrypt(const unsigned char *key, size_t key_len, const unsigned char *input, size_t input_len, const unsigned char *iv, unsigned char **res); bitlbee-3.2.1/lib/ssl_gnutls.c0000644000175000017500000003063712245474076015636 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* SSL module - GnuTLS version */ /* 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 #include #include #include #include "proxy.h" #include "ssl_client.h" #include "sock.h" #include "stdlib.h" #include "bitlbee.h" int ssl_errno = 0; static gboolean initialized = FALSE; gnutls_certificate_credentials_t xcred; #include #if defined(ULONG_MAX) && ULONG_MAX > 4294967295UL #define GNUTLS_STUPID_CAST (long) #else #define GNUTLS_STUPID_CAST (int) #endif #define SSLDEBUG 0 struct scd { ssl_input_function func; gpointer data; int fd; gboolean established; int inpa; char *hostname; gboolean verify; gnutls_session_t session; }; static GHashTable *session_cache; static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ); static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond ); static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond ); static void ssl_deinit( void ); static void ssl_log( int level, const char *line ) { printf( "%d %s", level, line ); } void ssl_init( void ) { if( initialized ) return; gnutls_global_init(); gnutls_certificate_allocate_credentials( &xcred ); if( global.conf->cafile ) { gnutls_certificate_set_x509_trust_file( xcred, global.conf->cafile, GNUTLS_X509_FMT_PEM ); /* Not needed in GnuTLS 2.11+ (enabled by default there) so don't do it (resets possible other defaults). */ if( !gnutls_check_version( "2.11" ) ) gnutls_certificate_set_verify_flags( xcred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT ); } initialized = TRUE; gnutls_global_set_log_function( ssl_log ); /* gnutls_global_set_log_level( 3 ); */ session_cache = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, g_free ); atexit( ssl_deinit ); } static void ssl_deinit( void ) { gnutls_global_deinit(); gnutls_certificate_free_credentials( xcred ); g_hash_table_destroy( session_cache ); session_cache = NULL; } void *ssl_connect( char *host, int port, gboolean verify, ssl_input_function func, gpointer data ) { struct scd *conn = g_new0( struct scd, 1 ); conn->func = func; conn->data = data; conn->inpa = -1; conn->hostname = g_strdup( host ); conn->verify = verify && global.conf->cafile; conn->fd = proxy_connect( host, port, ssl_connected, conn ); if( conn->fd < 0 ) { g_free( conn ); return NULL; } return conn; } void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data ) { struct scd *conn = g_new0( struct scd, 1 ); conn->fd = fd; conn->func = func; conn->data = data; conn->inpa = -1; conn->hostname = g_strdup( hostname ); /* For now, SSL verification is globally enabled by setting the cafile setting in bitlbee.conf. Commented out by default because probably not everyone has this file in the same place and plenty of folks may not have the cert of their private Jabber server in it. */ conn->verify = verify && global.conf->cafile; /* This function should be called via a (short) timeout instead of directly from here, because these SSL calls are *supposed* to be *completely* asynchronous and not ready yet when this function (or *_connect, for examle) returns. Also, errors are reported via the callback function, not via this function's return value. In short, doing things like this makes the rest of the code a lot simpler. */ b_timeout_add( 1, ssl_starttls_real, conn ); return conn; } static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond ) { struct scd *conn = data; return ssl_connected( conn, conn->fd, B_EV_IO_WRITE ); } static int verify_certificate_callback( gnutls_session_t session ) { unsigned int status; const gnutls_datum_t *cert_list; unsigned int cert_list_size; int gnutlsret; int verifyret = 0; gnutls_x509_crt_t cert; struct scd *conn; conn = gnutls_session_get_ptr( session ); gnutlsret = gnutls_certificate_verify_peers2( session, &status ); if( gnutlsret < 0 ) return VERIFY_CERT_ERROR; if( status & GNUTLS_CERT_INVALID ) verifyret |= VERIFY_CERT_INVALID; if( status & GNUTLS_CERT_REVOKED ) verifyret |= VERIFY_CERT_REVOKED; if( status & GNUTLS_CERT_SIGNER_NOT_FOUND ) verifyret |= VERIFY_CERT_SIGNER_NOT_FOUND; if( status & GNUTLS_CERT_SIGNER_NOT_CA ) verifyret |= VERIFY_CERT_SIGNER_NOT_CA; if( status & GNUTLS_CERT_INSECURE_ALGORITHM ) verifyret |= VERIFY_CERT_INSECURE_ALGORITHM; #ifdef GNUTLS_CERT_NOT_ACTIVATED /* Amusingly, the GnuTLS function used above didn't check for expiry until GnuTLS 2.8 or so. (See CVE-2009-1417) */ if( status & GNUTLS_CERT_NOT_ACTIVATED ) verifyret |= VERIFY_CERT_NOT_ACTIVATED; if( status & GNUTLS_CERT_EXPIRED ) verifyret |= VERIFY_CERT_EXPIRED; #endif if( gnutls_certificate_type_get( session ) != GNUTLS_CRT_X509 || gnutls_x509_crt_init( &cert ) < 0 ) return VERIFY_CERT_ERROR; cert_list = gnutls_certificate_get_peers( session, &cert_list_size ); if( cert_list == NULL || gnutls_x509_crt_import( cert, &cert_list[0], GNUTLS_X509_FMT_DER ) < 0 ) return VERIFY_CERT_ERROR; if( !gnutls_x509_crt_check_hostname( cert, conn->hostname ) ) { verifyret |= VERIFY_CERT_INVALID; verifyret |= VERIFY_CERT_WRONG_HOSTNAME; } gnutls_x509_crt_deinit( cert ); return verifyret; } struct ssl_session { size_t size; char data[]; }; static void ssl_cache_add( struct scd *conn ) { size_t data_size; struct ssl_session *data; char *hostname; if( !conn->hostname || gnutls_session_get_data( conn->session, NULL, &data_size ) != 0 ) return; data = g_malloc( sizeof( struct ssl_session ) + data_size ); if( gnutls_session_get_data( conn->session, data->data, &data->size ) != 0 ) { g_free( data ); return; } hostname = g_strdup( conn->hostname ); g_hash_table_insert( session_cache, hostname, data ); } static void ssl_cache_resume( struct scd *conn ) { struct ssl_session *data; if( conn->hostname && ( data = g_hash_table_lookup( session_cache, conn->hostname ) ) ) { gnutls_session_set_data( conn->session, data->data, data->size ); g_hash_table_remove( session_cache, conn->hostname ); } } char *ssl_verify_strerror( int code ) { GString *ret = g_string_new( "" ); if( code & VERIFY_CERT_REVOKED ) g_string_append( ret, "certificate has been revoked, " ); if( code & VERIFY_CERT_SIGNER_NOT_FOUND ) g_string_append( ret, "certificate hasn't got a known issuer, " ); if( code & VERIFY_CERT_SIGNER_NOT_CA ) g_string_append( ret, "certificate's issuer is not a CA, " ); if( code & VERIFY_CERT_INSECURE_ALGORITHM ) g_string_append( ret, "certificate uses an insecure algorithm, " ); if( code & VERIFY_CERT_NOT_ACTIVATED ) g_string_append( ret, "certificate has not been activated, " ); if( code & VERIFY_CERT_EXPIRED ) g_string_append( ret, "certificate has expired, " ); if( code & VERIFY_CERT_WRONG_HOSTNAME ) g_string_append( ret, "certificate hostname mismatch, " ); if( ret->len == 0 ) { g_string_free( ret, TRUE ); return NULL; } else { g_string_truncate( ret, ret->len - 2 ); return g_string_free( ret, FALSE ); } } static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ) { struct scd *conn = data; if( source == -1 ) { conn->func( conn->data, 0, NULL, cond ); g_free( conn ); return FALSE; } ssl_init(); gnutls_init( &conn->session, GNUTLS_CLIENT ); gnutls_session_set_ptr( conn->session, (void *) conn ); #if GNUTLS_VERSION_NUMBER < 0x020c00 gnutls_transport_set_lowat( conn->session, 0 ); #endif gnutls_set_default_priority( conn->session ); gnutls_credentials_set( conn->session, GNUTLS_CRD_CERTIFICATE, xcred ); if( conn->hostname && !isdigit( conn->hostname[0] ) ) gnutls_server_name_set( conn->session, GNUTLS_NAME_DNS, conn->hostname, strlen( conn->hostname ) ); sock_make_nonblocking( conn->fd ); gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr_t) GNUTLS_STUPID_CAST conn->fd ); ssl_cache_resume( conn ); return ssl_handshake( data, source, cond ); } static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond ) { struct scd *conn = data; int st, stver; if( ( st = gnutls_handshake( conn->session ) ) < 0 ) { if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED ) { conn->inpa = b_input_add( conn->fd, ssl_getdirection( conn ), ssl_handshake, data ); } else { conn->func( conn->data, 0, NULL, cond ); gnutls_deinit( conn->session ); closesocket( conn->fd ); g_free( conn ); } } else { if( conn->verify && ( stver = verify_certificate_callback( conn->session ) ) != 0 ) { conn->func( conn->data, stver, NULL, cond ); gnutls_deinit( conn->session ); closesocket( conn->fd ); g_free( conn ); } else { /* For now we can't handle non-blocking perfectly everywhere... */ sock_make_blocking( conn->fd ); ssl_cache_add( conn ); conn->established = TRUE; conn->func( conn->data, 0, conn, cond ); } } return FALSE; } int ssl_read( void *conn, char *buf, int len ) { int st; if( !((struct scd*)conn)->established ) { ssl_errno = SSL_NOHANDSHAKE; return -1; } st = gnutls_record_recv( ((struct scd*)conn)->session, buf, len ); ssl_errno = SSL_OK; if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED ) ssl_errno = SSL_AGAIN; if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st ); return st; } int ssl_write( void *conn, const char *buf, int len ) { int st; if( !((struct scd*)conn)->established ) { ssl_errno = SSL_NOHANDSHAKE; return -1; } st = gnutls_record_send( ((struct scd*)conn)->session, buf, len ); ssl_errno = SSL_OK; if( st == GNUTLS_E_AGAIN || st == GNUTLS_E_INTERRUPTED ) ssl_errno = SSL_AGAIN; if( SSLDEBUG && getenv( "BITLBEE_DEBUG" ) && st > 0 ) len = write( 2, buf, st ); return st; } int ssl_pending( void *conn ) { if( conn == NULL ) return 0; if( !((struct scd*)conn)->established ) { ssl_errno = SSL_NOHANDSHAKE; return 0; } #if GNUTLS_VERSION_NUMBER >= 0x03000d && GNUTLS_VERSION_NUMBER <= 0x030012 if( ssl_errno == SSL_AGAIN ) return 0; #endif return gnutls_record_check_pending( ((struct scd*)conn)->session ) != 0; } void ssl_disconnect( void *conn_ ) { struct scd *conn = conn_; if( conn->inpa != -1 ) b_event_remove( conn->inpa ); if( conn->established ) gnutls_bye( conn->session, GNUTLS_SHUT_WR ); closesocket( conn->fd ); if( conn->session ) gnutls_deinit( conn->session ); g_free( conn->hostname ); g_free( conn ); } int ssl_getfd( void *conn ) { return( ((struct scd*)conn)->fd ); } b_input_condition ssl_getdirection( void *conn ) { return( gnutls_record_get_direction( ((struct scd*)conn)->session ) ? B_EV_IO_WRITE : B_EV_IO_READ ); } size_t ssl_des3_encrypt( const unsigned char *key, size_t key_len, const unsigned char *input, size_t input_len, const unsigned char *iv, unsigned char **res ) { gcry_cipher_hd_t gcr; gcry_error_t st; ssl_init(); *res = g_malloc( input_len ); st = gcry_cipher_open( &gcr, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0 ) || gcry_cipher_setkey( gcr, key, key_len ) || gcry_cipher_setiv( gcr, iv, 8 ) || gcry_cipher_encrypt( gcr, *res, input_len, input, input_len ); gcry_cipher_close( gcr ); if( st == 0 ) return input_len; g_free( *res ); return 0; } bitlbee-3.2.1/lib/ftutil.c0000644000175000017500000001170412245474076014742 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Utility functions for file transfer * * * * Copyright 2008 Uli Meis * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #define BITLBEE_CORE #include "bitlbee.h" #include #include #include "lib/ftutil.h" #define ASSERTSOCKOP(op, msg) \ if( (op) == -1 ) {\ g_snprintf( errmsg, sizeof( errmsg ), msg ": %s", strerror( errno ) ); \ return -1; } /* * Creates a listening socket and returns it in saddr_ptr. */ int ft_listen( struct sockaddr_storage *saddr_ptr, char *host, char *port, int copy_fd, int for_bitlbee_client, char **errptr ) { int fd, gret, saddrlen; struct addrinfo hints, *rp; socklen_t ssize = sizeof( struct sockaddr_storage ); struct sockaddr_storage saddrs, *saddr = &saddrs; static char errmsg[1024]; char *ftlisten = global.conf->ft_listen; if( errptr ) *errptr = errmsg; strcpy( port, "0" ); /* Format is [:];[:] where * A is for connections with the bitlbee client (DCC) * and B is for connections with IM peers. */ if( ftlisten ) { char *scolon = strchr( ftlisten, ';' ); char *colon; if( scolon ) { if( for_bitlbee_client ) { *scolon = '\0'; strncpy( host, ftlisten, HOST_NAME_MAX ); *scolon = ';'; } else { strncpy( host, scolon + 1, HOST_NAME_MAX ); } } else { strncpy( host, ftlisten, HOST_NAME_MAX ); } if( ( colon = strchr( host, ':' ) ) ) { *colon = '\0'; strncpy( port, colon + 1, 5 ); } } else if( copy_fd >= 0 && getsockname( copy_fd, (struct sockaddr*) &saddrs, &ssize ) == 0 && ( saddrs.ss_family == AF_INET || saddrs.ss_family == AF_INET6 ) && getnameinfo( (struct sockaddr*) &saddrs, ssize, host, HOST_NAME_MAX, NULL, 0, NI_NUMERICHOST ) == 0 ) { /* We just took our local address on copy_fd, which is likely to be a sensible address from which we can do a file transfer now - the most sensible we can get easily. */ } else { ASSERTSOCKOP( gethostname( host, HOST_NAME_MAX + 1 ), "gethostname()" ); } memset( &hints, 0, sizeof( struct addrinfo ) ); hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICSERV; if ( ( gret = getaddrinfo( host, port, &hints, &rp ) ) != 0 ) { sprintf( errmsg, "getaddrinfo() failed: %s", gai_strerror( gret ) ); return -1; } saddrlen = rp->ai_addrlen; memcpy( saddr, rp->ai_addr, saddrlen ); freeaddrinfo( rp ); ASSERTSOCKOP( fd = socket( saddr->ss_family, SOCK_STREAM, 0 ), "Opening socket" ); ASSERTSOCKOP( bind( fd, ( struct sockaddr *)saddr, saddrlen ), "Binding socket" ); ASSERTSOCKOP( listen( fd, 1 ), "Making socket listen" ); if ( !inet_ntop( saddr->ss_family, saddr->ss_family == AF_INET ? ( void * )&( ( struct sockaddr_in * ) saddr )->sin_addr.s_addr : ( void * )&( ( struct sockaddr_in6 * ) saddr )->sin6_addr.s6_addr, host, HOST_NAME_MAX ) ) { strcpy( errmsg, "inet_ntop failed on listening socket" ); return -1; } ssize = sizeof( struct sockaddr_storage ); ASSERTSOCKOP( getsockname( fd, ( struct sockaddr *)saddr, &ssize ), "Getting socket name" ); if( saddr->ss_family == AF_INET ) g_snprintf( port, 6, "%d", ntohs( ( (struct sockaddr_in *) saddr )->sin_port ) ); else g_snprintf( port, 6, "%d", ntohs( ( (struct sockaddr_in6 *) saddr )->sin6_port ) ); if( saddr_ptr ) memcpy( saddr_ptr, saddr, saddrlen ); /* I hate static-length strings.. */ host[HOST_NAME_MAX] = '\0'; port[5] = '\0'; return fd; } bitlbee-3.2.1/lib/oauth.c0000644000175000017500000002632512245474076014560 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple OAuth client (consumer) implementation. * * * * Copyright 2010 Wilmer van der Gaast * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation, version * * 2.1. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the Free Software Foundation, * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * * \***************************************************************************/ #include #include #include #include #include "http_client.h" #include "base64.h" #include "misc.h" #include "sha1.h" #include "url.h" #include "oauth.h" #define HMAC_BLOCK_SIZE 64 static char *oauth_sign( const char *method, const char *url, const char *params, struct oauth_info *oi ) { uint8_t hash[sha1_hash_size]; GString *payload = g_string_new( "" ); char *key; char *s; key = g_strdup_printf( "%s&%s", oi->sp->consumer_secret, oi->token_secret ? oi->token_secret : "" ); g_string_append_printf( payload, "%s&", method ); s = g_new0( char, strlen( url ) * 3 + 1 ); strcpy( s, url ); http_encode( s ); g_string_append_printf( payload, "%s&", s ); g_free( s ); s = g_new0( char, strlen( params ) * 3 + 1 ); strcpy( s, params ); http_encode( s ); g_string_append( payload, s ); g_free( s ); sha1_hmac( key, 0, payload->str, 0, hash ); g_free( key ); g_string_free( payload, TRUE ); /* base64_encode + HTTP escape it (both consumers need it that away) and we're done. */ s = base64_encode( hash, sha1_hash_size ); s = g_realloc( s, strlen( s ) * 3 + 1 ); http_encode( s ); return s; } static char *oauth_nonce() { unsigned char bytes[21]; char *ret = g_new0( char, sizeof( bytes) / 3 * 4 + 1 ); random_bytes( bytes, sizeof( bytes ) ); base64_encode_real( bytes, sizeof( bytes), (unsigned char*) ret, "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0A" ); return ret; } void oauth_params_add( GSList **params, const char *key, const char *value ) { char *item; if( !key || !value ) return; item = g_strdup_printf( "%s=%s", key, value ); *params = g_slist_insert_sorted( *params, item, (GCompareFunc) strcmp ); } void oauth_params_del( GSList **params, const char *key ) { int key_len = strlen( key ); GSList *l, *n; if( params == NULL ) return; for( l = *params; l; l = n ) { n = l->next; if( strncmp( (char*) l->data, key, key_len ) == 0 && ((char*)l->data)[key_len] == '=' ) { g_free( l->data ); *params = g_slist_remove( *params, l->data ); } } } void oauth_params_set( GSList **params, const char *key, const char *value ) { oauth_params_del( params, key ); oauth_params_add( params, key, value ); } const char *oauth_params_get( GSList **params, const char *key ) { int key_len = strlen( key ); GSList *l; if( params == NULL ) return NULL; for( l = *params; l; l = l->next ) { if( strncmp( (char*) l->data, key, key_len ) == 0 && ((char*)l->data)[key_len] == '=' ) return (const char*) l->data + key_len + 1; } return NULL; } void oauth_params_parse( GSList **params, char *in ) { char *amp, *eq, *s; while( in && *in ) { eq = strchr( in, '=' ); if( !eq ) break; *eq = '\0'; if( ( amp = strchr( eq + 1, '&' ) ) ) *amp = '\0'; s = g_strdup( eq + 1 ); http_decode( s ); oauth_params_add( params, in, s ); g_free( s ); *eq = '='; if( amp == NULL ) break; *amp = '&'; in = amp + 1; } } void oauth_params_free( GSList **params ) { while( params && *params ) { g_free( (*params)->data ); *params = g_slist_remove( *params, (*params)->data ); } } char *oauth_params_string( GSList *params ) { GSList *l; GString *str = g_string_new( "" ); for( l = params; l; l = l->next ) { char *s, *eq; s = g_malloc( strlen( l->data ) * 3 + 1 ); strcpy( s, l->data ); if( ( eq = strchr( s, '=' ) ) ) http_encode( eq + 1 ); g_string_append( str, s ); g_free( s ); if( l->next ) g_string_append_c( str, '&' ); } return g_string_free( str, FALSE ); } void oauth_info_free( struct oauth_info *info ) { if( info ) { g_free( info->auth_url ); g_free( info->request_token ); g_free( info->token ); g_free( info->token_secret ); oauth_params_free( &info->params ); g_free( info ); } } static void oauth_add_default_params( GSList **params, const struct oauth_service *sp ) { char *s; oauth_params_set( params, "oauth_consumer_key", sp->consumer_key ); oauth_params_set( params, "oauth_signature_method", "HMAC-SHA1" ); s = g_strdup_printf( "%d", (int) time( NULL ) ); oauth_params_set( params, "oauth_timestamp", s ); g_free( s ); s = oauth_nonce(); oauth_params_set( params, "oauth_nonce", s ); g_free( s ); oauth_params_set( params, "oauth_version", "1.0" ); } static void *oauth_post_request( const char *url, GSList **params_, http_input_function func, struct oauth_info *oi ) { GSList *params = NULL; char *s, *params_s, *post; void *req; url_t url_p; if( !url_set( &url_p, url ) ) { oauth_params_free( params_ ); return NULL; } if( params_ ) params = *params_; oauth_add_default_params( ¶ms, oi->sp ); params_s = oauth_params_string( params ); oauth_params_free( ¶ms ); s = oauth_sign( "POST", url, params_s, oi ); post = g_strdup_printf( "%s&oauth_signature=%s", params_s, s ); g_free( params_s ); g_free( s ); s = g_strdup_printf( "POST %s HTTP/1.0\r\n" "Host: %s\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: %zd\r\n" "\r\n" "%s", url_p.file, url_p.host, strlen( post ), post ); g_free( post ); req = http_dorequest( url_p.host, url_p.port, url_p.proto == PROTO_HTTPS, s, func, oi ); g_free( s ); return req; } static void oauth_request_token_done( struct http_request *req ); struct oauth_info *oauth_request_token( const struct oauth_service *sp, oauth_cb func, void *data ) { struct oauth_info *st = g_new0( struct oauth_info, 1 ); GSList *params = NULL; st->func = func; st->data = data; st->sp = sp; oauth_params_add( ¶ms, "oauth_callback", "oob" ); if( !oauth_post_request( sp->url_request_token, ¶ms, oauth_request_token_done, st ) ) { oauth_info_free( st ); return NULL; } return st; } static void oauth_request_token_done( struct http_request *req ) { struct oauth_info *st = req->data; st->http = req; if( req->status_code == 200 ) { GSList *params = NULL; st->auth_url = g_strdup_printf( "%s?%s", st->sp->url_authorize, req->reply_body ); oauth_params_parse( ¶ms, req->reply_body ); st->request_token = g_strdup( oauth_params_get( ¶ms, "oauth_token" ) ); st->token_secret = g_strdup( oauth_params_get( ¶ms, "oauth_token_secret" ) ); oauth_params_free( ¶ms ); } st->stage = OAUTH_REQUEST_TOKEN; st->func( st ); } static void oauth_access_token_done( struct http_request *req ); gboolean oauth_access_token( const char *pin, struct oauth_info *st ) { GSList *params = NULL; oauth_params_add( ¶ms, "oauth_token", st->request_token ); oauth_params_add( ¶ms, "oauth_verifier", pin ); return oauth_post_request( st->sp->url_access_token, ¶ms, oauth_access_token_done, st ) != NULL; } static void oauth_access_token_done( struct http_request *req ) { struct oauth_info *st = req->data; st->http = req; if( req->status_code == 200 ) { oauth_params_parse( &st->params, req->reply_body ); st->token = g_strdup( oauth_params_get( &st->params, "oauth_token" ) ); g_free( st->token_secret ); st->token_secret = g_strdup( oauth_params_get( &st->params, "oauth_token_secret" ) ); } st->stage = OAUTH_ACCESS_TOKEN; if( st->func( st ) ) { /* Don't need these anymore, but keep the rest. */ g_free( st->auth_url ); st->auth_url = NULL; g_free( st->request_token ); st->request_token = NULL; oauth_params_free( &st->params ); } } char *oauth_http_header( struct oauth_info *oi, const char *method, const char *url, char *args ) { GSList *params = NULL, *l; char *sig = NULL, *params_s, *s; GString *ret = NULL; oauth_params_add( ¶ms, "oauth_token", oi->token ); oauth_add_default_params( ¶ms, oi->sp ); /* Start building the OAuth header. 'key="value", '... */ ret = g_string_new( "OAuth " ); for( l = params; l; l = l->next ) { char *kv = l->data; char *eq = strchr( kv, '=' ); char esc[strlen(kv)*3+1]; if( eq == NULL ) break; /* WTF */ strcpy( esc, eq + 1 ); http_encode( esc ); g_string_append_len( ret, kv, eq - kv + 1 ); g_string_append_c( ret, '"' ); g_string_append( ret, esc ); g_string_append( ret, "\", " ); } /* Now, before generating the signature, add GET/POST arguments to params since they should be included in the base signature string (but not in the HTTP header). */ if( args ) oauth_params_parse( ¶ms, args ); if( ( s = strchr( url, '?' ) ) ) { s = g_strdup( s + 1 ); oauth_params_parse( ¶ms, s ); g_free( s ); } /* Append the signature and we're done! */ params_s = oauth_params_string( params ); sig = oauth_sign( method, url, params_s, oi ); g_string_append_printf( ret, "oauth_signature=\"%s\"", sig ); g_free( params_s ); oauth_params_free( ¶ms ); g_free( sig ); return ret ? g_string_free( ret, FALSE ) : NULL; } char *oauth_to_string( struct oauth_info *oi ) { GSList *params = NULL; char *ret; oauth_params_add( ¶ms, "oauth_token", oi->token ); oauth_params_add( ¶ms, "oauth_token_secret", oi->token_secret ); ret = oauth_params_string( params ); oauth_params_free( ¶ms ); return ret; } struct oauth_info *oauth_from_string( char *in, const struct oauth_service *sp ) { struct oauth_info *oi = g_new0( struct oauth_info, 1 ); GSList *params = NULL; oauth_params_parse( ¶ms, in ); oi->token = g_strdup( oauth_params_get( ¶ms, "oauth_token" ) ); oi->token_secret = g_strdup( oauth_params_get( ¶ms, "oauth_token_secret" ) ); oauth_params_free( ¶ms ); oi->sp = sp; return oi; } bitlbee-3.2.1/lib/json_util.h0000644000175000017500000000424412245474076015447 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Helper functions for json.c * * * * Copyright 2012-2012 Wilmer van der Gaast * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation, version * * 2.1. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the Free Software Foundation, * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * * ****************************************************************************/ #include "json.h" #define JSON_O_FOREACH(o, k, v) \ char *k; json_value *v; int __i; \ for( __i = 0; ( __i < (o)->u.object.length ) && \ ( k = (o)->u.object.values[__i].name ) && \ ( v = (o)->u.object.values[__i].value ); \ __i ++ ) json_value *json_o_get( const json_value *obj, const json_char *name ); const char *json_o_str( const json_value *obj, const json_char *name ); char *json_o_strdup( const json_value *obj, const json_char *name ); bitlbee-3.2.1/lib/url.c0000644000175000017500000000562112245474076014236 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2001-2005 Wilmer van der Gaast and others * \********************************************************************/ /* URL/mirror stuff - Stolen from Axel */ /* 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 "url.h" /* Convert an URL to a url_t structure */ int url_set( url_t *url, const char *set_url ) { char s[MAX_STRING+1]; char *i; memset( url, 0, sizeof( url_t ) ); memset( s, 0, sizeof( s ) ); /* protocol:// */ if( ( i = strstr( set_url, "://" ) ) == NULL ) { url->proto = PROTO_DEFAULT; strncpy( s, set_url, MAX_STRING ); } else { if( g_strncasecmp( set_url, "http", i - set_url ) == 0 ) url->proto = PROTO_HTTP; else if( g_strncasecmp( set_url, "https", i - set_url ) == 0 ) url->proto = PROTO_HTTPS; else if( g_strncasecmp( set_url, "socks4", i - set_url ) == 0 ) url->proto = PROTO_SOCKS4; else if( g_strncasecmp( set_url, "socks5", i - set_url ) == 0 ) url->proto = PROTO_SOCKS5; else return 0; strncpy( s, i + 3, MAX_STRING ); } /* Split */ if( ( i = strchr( s, '/' ) ) == NULL ) { strcpy( url->file, "/" ); } else { strncpy( url->file, i, MAX_STRING ); *i = 0; } strncpy( url->host, s, MAX_STRING ); /* Check for username in host field */ if( strrchr( url->host, '@' ) != NULL ) { strncpy( url->user, url->host, MAX_STRING ); i = strrchr( url->user, '@' ); *i = 0; strcpy( url->host, i + 1 ); *url->pass = 0; } /* If not: Fill in defaults */ else { *url->user = *url->pass = 0; } /* Password? */ if( ( i = strchr( url->user, ':' ) ) != NULL ) { *i = 0; strcpy( url->pass, i + 1 ); } /* Port number? */ if( ( i = strchr( url->host, ':' ) ) != NULL ) { *i = 0; sscanf( i + 1, "%d", &url->port ); } else { if( url->proto == PROTO_HTTP ) url->port = 80; else if( url->proto == PROTO_HTTPS ) url->port = 443; else if( url->proto == PROTO_SOCKS4 || url->proto == PROTO_SOCKS5 ) url->port = 1080; } return( url->port > 0 ); } bitlbee-3.2.1/lib/xmltree.c0000644000175000017500000004151512245474076015116 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple XML (stream) parse tree handling code (Jabber/XMPP, mainly) * * * * Copyright 2006-2012 Wilmer van der Gaast * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation, version * * 2.1. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the Free Software Foundation, * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * * ****************************************************************************/ #include #include #include #include #include #include "xmltree.h" #define g_strcasecmp g_ascii_strcasecmp #define g_strncasecmp g_ascii_strncasecmp static void xt_start_element( GMarkupParseContext *ctx, const gchar *element_name, const gchar **attr_names, const gchar **attr_values, gpointer data, GError **error ) { struct xt_parser *xt = data; struct xt_node *node = g_new0( struct xt_node, 1 ), *nt; int i; node->parent = xt->cur; node->name = g_strdup( element_name ); /* First count the number of attributes */ for( i = 0; attr_names[i]; i ++ ); /* Then allocate a NULL-terminated array. */ node->attr = g_new0( struct xt_attr, i + 1 ); /* And fill it, saving one variable by starting at the end. */ for( i --; i >= 0; i -- ) { node->attr[i].key = g_strdup( attr_names[i] ); node->attr[i].value = g_strdup( attr_values[i] ); } /* Add it to the linked list of children nodes, if we have a current node yet. */ if( xt->cur ) { if( xt->cur->children ) { for( nt = xt->cur->children; nt->next; nt = nt->next ); nt->next = node; } else { xt->cur->children = node; } } else if( xt->root ) { /* ERROR situation: A second root-element??? */ } /* Now this node will be the new current node. */ xt->cur = node; /* And maybe this is the root? */ if( xt->root == NULL ) xt->root = node; } static void xt_text( GMarkupParseContext *ctx, const gchar *text, gsize text_len, gpointer data, GError **error ) { struct xt_parser *xt = data; struct xt_node *node = xt->cur; if( node == NULL ) return; /* FIXME: Does g_renew also OFFICIALLY accept NULL arguments? */ node->text = g_renew( char, node->text, node->text_len + text_len + 1 ); memcpy( node->text + node->text_len, text, text_len ); node->text_len += text_len; /* Zero termination is always nice to have. */ node->text[node->text_len] = 0; } static void xt_end_element( GMarkupParseContext *ctx, const gchar *element_name, gpointer data, GError **error ) { struct xt_parser *xt = data; xt->cur->flags |= XT_COMPLETE; xt->cur = xt->cur->parent; } GMarkupParser xt_parser_funcs = { xt_start_element, xt_end_element, xt_text, NULL, NULL }; struct xt_parser *xt_new( const struct xt_handler_entry *handlers, gpointer data ) { struct xt_parser *xt = g_new0( struct xt_parser, 1 ); xt->data = data; xt->handlers = handlers; xt_reset( xt ); return xt; } /* Reset the parser, flush everything we have so far. For example, we need this for XMPP when doing TLS/SASL to restart the stream. */ void xt_reset( struct xt_parser *xt ) { if( xt->parser ) g_markup_parse_context_free( xt->parser ); xt->parser = g_markup_parse_context_new( &xt_parser_funcs, 0, xt, NULL ); if( xt->root ) { xt_free_node( xt->root ); xt->root = NULL; xt->cur = NULL; } } /* Feed the parser, don't execute any handler. Returns -1 on errors, 0 on end-of-stream and 1 otherwise. */ int xt_feed( struct xt_parser *xt, const char *text, int text_len ) { if( !g_markup_parse_context_parse( xt->parser, text, text_len, &xt->gerr ) ) { return -1; } return !( xt->root && xt->root->flags & XT_COMPLETE ); } /* Find completed nodes and see if a handler has to be called. Passing a node isn't necessary if you want to start at the root, just pass NULL. This second argument is needed for recursive calls. */ int xt_handle( struct xt_parser *xt, struct xt_node *node, int depth ) { struct xt_node *c; xt_status st; int i; if( xt->root == NULL ) return 1; if( node == NULL ) return xt_handle( xt, xt->root, depth ); if( depth != 0 ) for( c = node->children; c; c = c->next ) if( !xt_handle( xt, c, depth > 0 ? depth - 1 : depth ) ) return 0; if( node->flags & XT_COMPLETE && !( node->flags & XT_SEEN ) ) { if( xt->handlers ) for( i = 0; xt->handlers[i].func; i ++ ) { /* This one is fun! \o/ */ /* If handler.name == NULL it means it should always match. */ if( ( xt->handlers[i].name == NULL || /* If it's not, compare. There should always be a name. */ g_strcasecmp( xt->handlers[i].name, node->name ) == 0 ) && /* If handler.parent == NULL, it's a match. */ ( xt->handlers[i].parent == NULL || /* If there's a parent node, see if the name matches. */ ( node->parent ? g_strcasecmp( xt->handlers[i].parent, node->parent->name ) == 0 : /* If there's no parent, the handler should mention as a parent. */ strcmp( xt->handlers[i].parent, "" ) == 0 ) ) ) { st = xt->handlers[i].func( node, xt->data ); if( st == XT_ABORT ) return 0; else if( st != XT_NEXT ) break; } } node->flags |= XT_SEEN; } return 1; } /* Garbage collection: Cleans up all nodes that are handled. Useful for streams because there's no reason to keep a complete packet history in memory. */ void xt_cleanup( struct xt_parser *xt, struct xt_node *node, int depth ) { struct xt_node *c, *prev; if( !xt || !xt->root ) return; if( node == NULL ) { xt_cleanup( xt, xt->root, depth ); return; } if( node->flags & XT_SEEN && node == xt->root ) { xt_free_node( xt->root ); xt->root = xt->cur = NULL; /* xt->cur should be NULL already, BTW... */ return; } /* c contains the current node, prev the previous node (or NULL). I admit, this one's pretty horrible. */ for( c = node->children, prev = NULL; c; prev = c, c = c ? c->next : node->children ) { if( c->flags & XT_SEEN ) { /* Remove the node from the linked list. */ if( prev ) prev->next = c->next; else node->children = c->next; xt_free_node( c ); /* Since the for loop wants to get c->next, make sure c points at something that exists (and that c->next will actually be the next item we should check). c can be NULL now, if we just removed the first item. That explains the ? thing in for(). */ c = prev; } else { /* This node can't be cleaned up yet, but maybe a subnode can. */ if( depth != 0 ) xt_cleanup( xt, c, depth > 0 ? depth - 1 : depth ); } } } struct xt_node *xt_from_string( const char *in, int len ) { struct xt_parser *parser; struct xt_node *ret = NULL; if( len == 0 ) len = strlen( in ); parser = xt_new( NULL, NULL ); xt_feed( parser, in, len ); if( parser->cur == NULL ) { ret = parser->root; parser->root = NULL; } xt_free( parser ); return ret; } static void xt_to_string_real( struct xt_node *node, GString *str, int indent ) { char *buf; struct xt_node *c; int i; if( indent > 1 ) g_string_append_len( str, "\n\t\t\t\t\t\t\t\t", indent < 8 ? indent : 8 ); g_string_append_printf( str, "<%s", node->name ); for( i = 0; node->attr[i].key; i ++ ) { buf = g_markup_printf_escaped( " %s=\"%s\"", node->attr[i].key, node->attr[i].value ); g_string_append( str, buf ); g_free( buf ); } if( node->text == NULL && node->children == NULL ) { g_string_append( str, "/>" ); return; } g_string_append( str, ">" ); if( node->text_len > 0 ) { buf = g_markup_escape_text( node->text, node->text_len ); g_string_append( str, buf ); g_free( buf ); } for( c = node->children; c; c = c->next ) xt_to_string_real( c, str, indent ? indent + 1 : 0 ); if( indent > 0 && node->children ) g_string_append_len( str, "\n\t\t\t\t\t\t\t\t", indent < 8 ? indent : 8 ); g_string_append_printf( str, "", node->name ); } char *xt_to_string( struct xt_node *node ) { GString *ret; ret = g_string_new( "" ); xt_to_string_real( node, ret, 0 ); return g_string_free( ret, FALSE ); } /* WITH indentation! */ char *xt_to_string_i( struct xt_node *node ) { GString *ret; ret = g_string_new( "" ); xt_to_string_real( node, ret, 1 ); return g_string_free( ret, FALSE ); } void xt_print( struct xt_node *node ) { char *str = xt_to_string_i( node ); fprintf( stderr, "%s", str ); g_free( str ); } struct xt_node *xt_dup( struct xt_node *node ) { struct xt_node *dup = g_new0( struct xt_node, 1 ); struct xt_node *c, *dc = NULL; int i; /* Let's NOT copy the parent element here BTW! Only do it for children. */ dup->name = g_strdup( node->name ); dup->flags = node->flags; if( node->text ) { dup->text = g_memdup( node->text, node->text_len + 1 ); dup->text_len = node->text_len; } /* Count the number of attributes and allocate the new array. */ for( i = 0; node->attr[i].key; i ++ ); dup->attr = g_new0( struct xt_attr, i + 1 ); /* Copy them all! */ for( i --; i >= 0; i -- ) { dup->attr[i].key = g_strdup( node->attr[i].key ); dup->attr[i].value = g_strdup( node->attr[i].value ); } /* This nice mysterious loop takes care of the children. */ for( c = node->children; c; c = c->next ) { if( dc == NULL ) dc = dup->children = xt_dup( c ); else dc = ( dc->next = xt_dup( c ) ); dc->parent = dup; } return dup; } /* Frees a node. This doesn't clean up references to itself from parents! */ void xt_free_node( struct xt_node *node ) { int i; if( !node ) return; g_free( node->name ); g_free( node->text ); for( i = 0; node->attr[i].key; i ++ ) { g_free( node->attr[i].key ); g_free( node->attr[i].value ); } g_free( node->attr ); while( node->children ) { struct xt_node *next = node->children->next; xt_free_node( node->children ); node->children = next; } g_free( node ); } void xt_free( struct xt_parser *xt ) { if( !xt ) return; if( xt->root ) xt_free_node( xt->root ); g_markup_parse_context_free( xt->parser ); g_free( xt ); } /* To find a node's child with a specific name, pass the node's children list, not the node itself! The reason you have to do this by hand: So that you can also use this function as a find-next. */ struct xt_node *xt_find_node( struct xt_node *node, const char *name ) { while( node ) { char *colon; if( g_strcasecmp( node->name, name ) == 0 || ( ( colon = strchr( node->name, ':' ) ) && g_strcasecmp( colon + 1, name ) == 0 ) ) break; node = node->next; } return node; } /* More advanced than the one above, understands something like ../foo/bar to find a subnode bar of a node foo which is a child of node's parent. Pass the node directly, not its list of children. */ struct xt_node *xt_find_path( struct xt_node *node, const char *name ) { while( name && *name && node ) { char *colon, *slash; int n; if( ( slash = strchr( name, '/' ) ) ) n = slash - name; else n = strlen( name ); if( strncmp( name, "..", n ) == 0 ) { node = node->parent; } else { node = node->children; while( node ) { if( g_strncasecmp( node->name, name, n ) == 0 || ( ( colon = strchr( node->name, ':' ) ) && g_strncasecmp( colon + 1, name, n ) == 0 ) ) break; node = node->next; } } name = slash ? slash + 1 : NULL; } return node; } char *xt_find_attr( struct xt_node *node, const char *key ) { int i; char *colon; if( !node ) return NULL; for( i = 0; node->attr[i].key; i ++ ) if( g_strcasecmp( node->attr[i].key, key ) == 0 ) break; /* This is an awful hack that only takes care of namespace prefixes inside a tag. Since IMHO excessive namespace usage in XMPP is massive overkill anyway (this code exists for almost four years now and never really missed it): Meh. */ if( !node->attr[i].key && strcmp( key, "xmlns" ) == 0 && ( colon = strchr( node->name, ':' ) ) ) { *colon = '\0'; for( i = 0; node->attr[i].key; i ++ ) if( strncmp( node->attr[i].key, "xmlns:", 6 ) == 0 && strcmp( node->attr[i].key + 6, node->name ) == 0 ) break; *colon = ':'; } return node->attr[i].value; } /* Strip a few non-printable characters that aren't allowed in XML streams (and upset some XMPP servers for example). */ void xt_strip_text( char *in ) { char *out = in; static const char nonprint[32] = { 0, 0, 0, 0, 0, 0, 0, 0, /* 0..7 */ 0, 1, 1, 0, 0, 1, 0, 0, /* 9 (tab), 10 (\n), 13 (\r) */ }; if( !in ) return; while( *in ) { if( (unsigned int) *in >= ' ' || nonprint[(unsigned int) *in] ) *out ++ = *in; in ++; } *out = *in; } struct xt_node *xt_new_node( char *name, const char *text, struct xt_node *children ) { struct xt_node *node, *c; node = g_new0( struct xt_node, 1 ); node->name = g_strdup( name ); node->children = children; node->attr = g_new0( struct xt_attr, 1 ); if( text ) { node->text = g_strdup( text ); xt_strip_text( node->text ); node->text_len = strlen( node->text ); } for( c = children; c; c = c->next ) { if( c->parent != NULL ) { /* ERROR CONDITION: They seem to have a parent already??? */ } c->parent = node; } return node; } void xt_add_child( struct xt_node *parent, struct xt_node *child ) { struct xt_node *node; /* This function can actually be used to add more than one child, so do handle this properly. */ for( node = child; node; node = node->next ) { if( node->parent != NULL ) { /* ERROR CONDITION: They seem to have a parent already??? */ } node->parent = parent; } if( parent->children == NULL ) { parent->children = child; } else { for( node = parent->children; node->next; node = node->next ); node->next = child; } } /* Same, but at the beginning. */ void xt_insert_child( struct xt_node *parent, struct xt_node *child ) { struct xt_node *node, *last = NULL; if( child == NULL ) return; /* BUG */ for( node = child; node; node = node->next ) { if( node->parent != NULL ) { /* ERROR CONDITION: They seem to have a parent already??? */ } node->parent = parent; last = node; } last->next = parent->children; parent->children = child; } void xt_add_attr( struct xt_node *node, const char *key, const char *value ) { int i; /* Now actually it'd be nice if we can also change existing attributes (which actually means this function doesn't have the right name). So let's find out if we have this attribute already... */ for( i = 0; node->attr[i].key; i ++ ) if( strcmp( node->attr[i].key, key ) == 0 ) break; if( node->attr[i].key == NULL ) { /* If not, allocate space for a new attribute. */ node->attr = g_renew( struct xt_attr, node->attr, i + 2 ); node->attr[i].key = g_strdup( key ); node->attr[i+1].key = NULL; } else { /* Otherwise, free the old value before setting the new one. */ g_free( node->attr[i].value ); } node->attr[i].value = g_strdup( value ); } int xt_remove_attr( struct xt_node *node, const char *key ) { int i, last; for( i = 0; node->attr[i].key; i ++ ) if( strcmp( node->attr[i].key, key ) == 0 ) break; /* If we didn't find the attribute... */ if( node->attr[i].key == NULL ) return 0; g_free( node->attr[i].key ); g_free( node->attr[i].value ); /* If it's the last, this is easy: */ if( node->attr[i+1].key == NULL ) { node->attr[i].key = node->attr[i].value = NULL; } else /* It's also pretty easy, actually. */ { /* Find the last item. */ for( last = i + 1; node->attr[last+1].key; last ++ ); node->attr[i] = node->attr[last]; node->attr[last].key = NULL; node->attr[last].value = NULL; } /* Let's not bother with reallocating memory here. It takes time and most packets don't stay in memory for long anyway. */ return 1; } bitlbee-3.2.1/lib/ssl_sspi.c0000644000175000017500000001544712245474076015302 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ /* SSL module - SSPI backend */ /* Copyright (C) 2005 Jelmer Vernooij */ /* 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 "ssl_client.h" #include #define SECURITY_WIN32 #include #include #include #include "sock.h" static gboolean initialized = FALSE; int ssl_errno; struct scd { int fd; ssl_input_function func; gpointer data; gboolean established; CredHandle cred; /* SSL credentials */ CtxtHandle context; /* SSL context */ SecPkgContext_StreamSizes sizes; char *host; char *pending_raw_data; gsize pending_raw_data_len; char *pending_data; gsize pending_data_len; }; static void ssl_connected(gpointer, gint, GaimInputCondition); void sspi_global_init(void) { /* FIXME */ } void sspi_global_deinit(void) { /* FIXME */ } void *ssl_connect(char *host, int port, ssl_input_function func, gpointer data) { struct scd *conn = g_new0(struct scd, 1); conn->fd = proxy_connect(host, port, ssl_connected, conn); sock_make_nonblocking(conn->fd); conn->func = func; conn->data = data; conn->host = g_strdup(host); if (conn->fd < 0) { g_free(conn); return NULL; } if (!initialized) { sspi_global_init(); initialized = TRUE; atexit(sspi_global_deinit); } return conn; } static void ssl_connected(gpointer _conn, gint fd, GaimInputCondition cond) { struct scd *conn = _conn; SCHANNEL_CRED ssl_cred; TimeStamp timestamp; SecBuffer ibuf[2],obuf[1]; SecBufferDesc ibufs,obufs; ULONG req = ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_USE_SESSION_KEY | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM | ISC_REQ_EXTENDED_ERROR | ISC_REQ_MANUAL_CRED_VALIDATION; ULONG a; gsize size = 0; gchar *data = NULL; memset(&ssl_cred, 0, sizeof(SCHANNEL_CRED)); ssl_cred.dwVersion = SCHANNEL_CRED_VERSION; ssl_cred.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT; SECURITY_STATUS st = AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, &ssl_cred, NULL, NULL, &conn->cred, ×tamp); if (st != SEC_E_OK) { conn->func(conn->data, NULL, cond); return; } do { /* initialize buffers */ ibuf[0].cbBuffer = size; ibuf[0].pvBuffer = data; ibuf[1].cbBuffer = 0; ibuf[1].pvBuffer = NULL; obuf[0].cbBuffer = 0; obuf[0].pvBuffer = NULL; ibuf[0].BufferType = obuf[0].BufferType = SECBUFFER_TOKEN; ibuf[1].BufferType = SECBUFFER_EMPTY; /* initialize buffer descriptors */ ibufs.ulVersion = obufs.ulVersion = SECBUFFER_VERSION; ibufs.cBuffers = 2; obufs.cBuffers = 1; ibufs.pBuffers = ibuf; obufs.pBuffers = obuf; st = InitializeSecurityContext(&conn->cred, size?&conn->context:NULL, conn->host, req, 0, SECURITY_NETWORK_DREP, size?&ibufs:NULL, 0, &conn->context, &obufs, &a, ×tamp); if (obuf[0].pvBuffer && obuf[0].cbBuffer) { /* FIXME: Check return value */ send(conn->fd, obuf[0].pvBuffer, obuf[0].cbBuffer, 0); } switch (st) { case SEC_I_INCOMPLETE_CREDENTIALS: break; case SEC_I_CONTINUE_NEEDED: break; case SEC_E_INCOMPLETE_MESSAGE: break; case SEC_E_OK: break; } QueryContextAttributes(&conn->context, SECPKG_ATTR_STREAM_SIZES, &conn->sizes); } while (1); conn->func(conn->data, conn, cond); } int ssl_read(void *conn, char *retdata, int len) { struct scd *scd = conn; SecBufferDesc msg; SecBuffer buf[4]; int ret = -1, i; char *data = g_malloc(scd->sizes.cbHeader + scd->sizes.cbMaximumMessage + scd->sizes.cbTrailer); /* FIXME: Try to read some data */ msg.ulVersion = SECBUFFER_VERSION; msg.cBuffers = 4; msg.pBuffers = buf; buf[0].BufferType = SECBUFFER_DATA; buf[0].cbBuffer = len; buf[0].pvBuffer = data; buf[1].BufferType = SECBUFFER_EMPTY; buf[2].BufferType = SECBUFFER_EMPTY; buf[3].BufferType = SECBUFFER_EMPTY; SECURITY_STATUS st = DecryptMessage(&scd->context, &msg, 0, NULL); if (st != SEC_E_OK) { /* FIXME */ return -1; } for (i = 0; i < 4; i++) { if (buf[i].BufferType == SECBUFFER_DATA) { memcpy(retdata, buf[i].pvBuffer, len); ret = len; } } g_free(data); return -1; } int ssl_write(void *conn, const char *userdata, int len) { struct scd *scd = conn; SecBuffer buf[4]; SecBufferDesc msg; char *data; int ret; msg.ulVersion = SECBUFFER_VERSION; msg.cBuffers = 4; msg.pBuffers = buf; data = g_malloc(scd->sizes.cbHeader + scd->sizes.cbMaximumMessage + scd->sizes.cbTrailer); memcpy(data + scd->sizes.cbHeader, userdata, len); buf[0].BufferType = SECBUFFER_STREAM_HEADER; buf[0].cbBuffer = scd->sizes.cbHeader; buf[0].pvBuffer = data; buf[1].BufferType = SECBUFFER_DATA; buf[1].cbBuffer = len; buf[1].pvBuffer = data + scd->sizes.cbHeader; buf[2].BufferType = SECBUFFER_STREAM_TRAILER; buf[2].cbBuffer = scd->sizes.cbTrailer; buf[2].pvBuffer = data + scd->sizes.cbHeader + len; buf[3].BufferType = SECBUFFER_EMPTY; SECURITY_STATUS st = EncryptMessage(&scd->context, 0, &msg, 0); ret = send(scd->fd, data, buf[0].cbBuffer + buf[1].cbBuffer + buf[2].cbBuffer, 0); g_free(data); return ret; } void ssl_disconnect(void *conn) { struct scd *scd = conn; SecBufferDesc msg; SecBuffer buf; DWORD dw; dw = SCHANNEL_SHUTDOWN; buf.cbBuffer = sizeof(dw); buf.BufferType = SECBUFFER_TOKEN; buf.pvBuffer = &dw; msg.ulVersion = SECBUFFER_VERSION; msg.cBuffers = 1; msg.pBuffers = &buf; SECURITY_STATUS st = ApplyControlToken(&scd->context, &msg); if (st != SEC_E_OK) { /* FIXME */ } /* FIXME: call InitializeSecurityContext(Schannel), passing * in empty buffers*/ DeleteSecurityContext(&scd->context); FreeCredentialsHandle(&scd->cred); closesocket(scd->fd); g_free(scd->host); g_free(scd); } int ssl_getfd(void *conn) { return ((struct scd*)conn)->fd; } GaimInputCondition ssl_getdirection( void *conn ) { return B_EV_IO_WRITE; /* FIXME: or B_EV_IO_READ */ } bitlbee-3.2.1/lib/md5.h0000644000175000017500000000271012245474076014122 0ustar wilmerwilmer/* * MD5 hashing code copied from Lepton's crack * * Adapted to be API-compatible with the previous (GPL-incompatible) code. */ /* * This code implements the MD5 message-digest algorithm. * The algorithm is due to Ron Rivest. This code was * written by Colin Plumb in 1993, no copyright is claimed. * This code is in the public domain; do with it what you wish. * * Equivalent code is available from RSA Data Security, Inc. * This code has been tested against that, and is equivalent, * except that you don't need to include two pages of legalese * with every copy. * * To compute the message digest of a chunk of bytes, declare an * MD5Context structure, pass it to MD5Init, call MD5Update as * needed on buffers full of bytes, and then call MD5Final, which * will fill a supplied 16-byte array with the digest. */ #ifndef _MD5_H #define _MD5_H #include #include #if(__sun) #include #else #include #endif typedef uint8_t md5_byte_t; typedef struct MD5Context { uint32_t buf[4]; uint32_t bits[2]; unsigned char in[64]; } md5_state_t; G_MODULE_EXPORT void md5_init(struct MD5Context *context); G_MODULE_EXPORT void md5_append(struct MD5Context *context, const md5_byte_t *buf, unsigned int len); G_MODULE_EXPORT void md5_finish(struct MD5Context *context, md5_byte_t digest[16]); G_MODULE_EXPORT void md5_finish_ascii(struct MD5Context *context, char *ascii); #endif bitlbee-3.2.1/lib/oauth2.h0000644000175000017500000000526212245474076014644 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple OAuth2 client (consumer) implementation. * * * * Copyright 2010-2013 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ /* Implementation mostly based on my experience with writing the previous OAuth module, and from http://code.google.com/apis/accounts/docs/OAuth2.html . */ typedef void (*oauth2_token_callback)( gpointer data, const char *atoken, const char *rtoken, const char *error ); struct oauth2_service { char *auth_url; char *token_url; char *redirect_url; char *scope; char *consumer_key; char *consumer_secret; }; #define OAUTH2_AUTH_CODE "authorization_code" #define OAUTH2_AUTH_REFRESH "refresh_token" /* Generate a URL the user should open in his/her browser to get an authorization code. */ char *oauth2_url( const struct oauth2_service *sp ); /* Exchanges an auth code or refresh token for an access token. auth_type is one of the two OAUTH2_AUTH_.. constants above. */ int oauth2_access_token( const struct oauth2_service *sp, const char *auth_type, const char *auth, oauth2_token_callback func, gpointer data ); bitlbee-3.2.1/lib/base64.h0000644000175000017500000000441012245474076014520 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Base64 handling functions. encode_real() is mostly based on the y64 en- * * coder from libyahoo2. Moving it to a new file because it's getting big. * * * * Copyright 2006 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include #include G_MODULE_EXPORT char *tobase64( const char *text ); G_MODULE_EXPORT char *base64_encode( const unsigned char *in, int len ); G_MODULE_EXPORT int base64_encode_real( const unsigned char *in, int inlen, unsigned char *out, const char *b64digits ); G_MODULE_EXPORT char *frombase64( const char *in ); G_MODULE_EXPORT int base64_decode( const char *in, unsigned char **out ); G_MODULE_EXPORT int base64_decode_real( const unsigned char *in, unsigned char *out, char *b64reverse ); bitlbee-3.2.1/lib/sha1.h0000644000175000017500000000402412245474076014271 0ustar wilmerwilmer/* * SHA1 hashing code copied from Lepton's crack * * Adapted to be API-compatible with the previous (GPL-incompatible) code. */ /* * sha1.h * * Description: * This is the header file for code which implements the Secure * Hashing Algorithm 1 as defined in FIPS PUB 180-1 published * April 17, 1995. * * Many of the variable names in this code, especially the * single character names, were used because those were the names * used in the publication. * * Please read the file sha1.c for more information. * */ #ifndef _SHA1_H_ #define _SHA1_H_ #if(__sun) #include #else #include #endif #include #ifndef _SHA_enum_ #define _SHA_enum_ enum { shaSuccess = 0, shaNull, /* Null pointer parameter */ shaInputTooLong, /* input data too long */ shaStateError /* called Input after Result */ }; #endif #define sha1_hash_size 20 /* * This structure will hold context information for the SHA-1 * hashing operation */ typedef struct SHA1Context { uint32_t Intermediate_Hash[sha1_hash_size/4]; /* Message Digest */ uint32_t Length_Low; /* Message length in bits */ uint32_t Length_High; /* Message length in bits */ /* Index into message block array */ int_least16_t Message_Block_Index; uint8_t Message_Block[64]; /* 512-bit message blocks */ int Computed; /* Is the digest computed? */ int Corrupted; /* Is the message digest corrupted? */ } sha1_state_t; /* * Function Prototypes */ G_MODULE_EXPORT int sha1_init(sha1_state_t *); G_MODULE_EXPORT int sha1_append(sha1_state_t *, const uint8_t *, unsigned int); G_MODULE_EXPORT int sha1_finish(sha1_state_t *, uint8_t Message_Digest[sha1_hash_size]); G_MODULE_EXPORT void sha1_hmac(const char *key_, size_t key_len, const char *payload, size_t payload_len, uint8_t Message_Digest[sha1_hash_size]); G_MODULE_EXPORT char *sha1_random_uuid( sha1_state_t * context ); #endif bitlbee-3.2.1/lib/misc.h0000644000175000017500000000526412245474076014377 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* Misc. functions */ /* 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 _MISC_H #define _MISC_H #include #include struct ns_srv_reply { int prio; int weight; int port; char name[]; }; G_MODULE_EXPORT void strip_linefeed( gchar *text ); G_MODULE_EXPORT char *add_cr( char *text ); G_MODULE_EXPORT char *strip_newlines(char *source); G_MODULE_EXPORT time_t get_time( int year, int month, int day, int hour, int min, int sec ); G_MODULE_EXPORT time_t mktime_utc( struct tm *tp ); double gettime( void ); G_MODULE_EXPORT void strip_html( char *msg ); G_MODULE_EXPORT char *escape_html( const char *html ); G_MODULE_EXPORT void http_decode( char *s ); G_MODULE_EXPORT void http_encode( char *s ); G_MODULE_EXPORT char *ipv6_wrap( char *src ); G_MODULE_EXPORT char *ipv6_unwrap( char *src ); G_MODULE_EXPORT signed int do_iconv( char *from_cs, char *to_cs, char *src, char *dst, size_t size, size_t maxbuf ); G_MODULE_EXPORT void random_bytes( unsigned char *buf, int count ); G_MODULE_EXPORT int is_bool( char *value ); G_MODULE_EXPORT int bool2int( char *value ); G_MODULE_EXPORT struct ns_srv_reply **srv_lookup( char *service, char *protocol, char *domain ); G_MODULE_EXPORT void srv_free( struct ns_srv_reply **srv ); G_MODULE_EXPORT char *word_wrap( const char *msg, int line_len ); G_MODULE_EXPORT gboolean ssl_sockerr_again( void *ssl ); G_MODULE_EXPORT int md5_verify_password( char *password, char *hash ); G_MODULE_EXPORT char **split_command_parts( char *command ); G_MODULE_EXPORT char *get_rfc822_header( const char *text, const char *header, int len ); #endif bitlbee-3.2.1/lib/oauth2.c0000644000175000017500000001547612245474076014647 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple OAuth2 client (consumer) implementation. * * * * Copyright 2010-2013 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ /* Out of protest, I should rename this file. OAuth2 is a pathetic joke, and of all things, DEFINITELY NOT A STANDARD. The only thing various OAuth2 implementations have in common is that name, wrongfully stolen from a pretty nice standard called OAuth 1.0a. That, and the fact that they use JSON. Wait, no, Facebook's version doesn't use JSON. For some of its responses. Apparently too many people were too retarded to comprehend the elementary bits of crypto in OAuth 1.0a (took me one afternoon to implement) so the standard was replaced with what comes down to a complicated scheme around what's really just application-specific passwords. And then a bunch of mostly incompatible implementations. Great work, guys. http://hueniverse.com/2012/07/oauth-2-0-and-the-road-to-hell/ */ #include #include "http_client.h" #include "oauth2.h" #include "oauth.h" #include "json.h" #include "json_util.h" #include "url.h" char *oauth2_url( const struct oauth2_service *sp ) { return g_strconcat( sp->auth_url, "?scope=", sp->scope, "&response_type=code" "&redirect_uri=", sp->redirect_url, "&client_id=", sp->consumer_key, NULL ); } struct oauth2_access_token_data { oauth2_token_callback func; gpointer data; }; static void oauth2_access_token_done( struct http_request *req ); int oauth2_access_token( const struct oauth2_service *sp, const char *auth_type, const char *auth, oauth2_token_callback func, gpointer data ) { GSList *args = NULL; char *args_s, *s; url_t url_p; struct http_request *req; struct oauth2_access_token_data *cb_data; if( !url_set( &url_p, sp->token_url ) ) return 0; oauth_params_add( &args, "client_id", sp->consumer_key ); oauth_params_add( &args, "client_secret", sp->consumer_secret ); oauth_params_add( &args, "grant_type", auth_type ); if( strcmp( auth_type, OAUTH2_AUTH_CODE ) == 0 ) { oauth_params_add( &args, "redirect_uri", sp->redirect_url ); oauth_params_add( &args, "code", auth ); } else { oauth_params_add( &args, "refresh_token", auth ); } args_s = oauth_params_string( args ); oauth_params_free( &args ); s = g_strdup_printf( "POST %s HTTP/1.0\r\n" "Host: %s\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: %zd\r\n" "\r\n" "%s", url_p.file, url_p.host, strlen( args_s ), args_s ); g_free( args_s ); cb_data = g_new0( struct oauth2_access_token_data, 1 ); cb_data->func = func; cb_data->data = data; req = http_dorequest( url_p.host, url_p.port, url_p.proto == PROTO_HTTPS, s, oauth2_access_token_done, cb_data ); g_free( s ); if( req == NULL ) g_free( cb_data ); return req != NULL; } static char* oauth2_parse_error( json_value *e ) { /* This does a reasonable job with some of the flavours of error responses I've seen. Because apparently it's not standardised. */ if( e->type == json_object ) { /* Facebook style */ const char *msg = json_o_str( e, "message" ); const char *type = json_o_str( e, "type" ); json_value *code_o = json_o_get( e, "code" ); int code = 0; if( code_o && code_o->type == json_integer ) code = code_o->u.integer; return g_strdup_printf( "Error %d: %s", code, msg ? msg : type ? type : "Unknown error" ); } else if( e->type == json_string ) { return g_strdup( e->u.string.ptr ); } return NULL; } static void oauth2_access_token_done( struct http_request *req ) { struct oauth2_access_token_data *cb_data = req->data; char *atoken = NULL, *rtoken = NULL, *error = NULL; char *content_type; if( getenv( "BITLBEE_DEBUG" ) && req->reply_body ) printf( "%s\n", req->reply_body ); content_type = get_rfc822_header( req->reply_headers, "Content-Type", 0 ); if( content_type && ( strstr( content_type, "application/json" ) || strstr( content_type, "text/javascript" ) ) ) { json_value *js = json_parse( req->reply_body ); if( js && js->type == json_object ) { JSON_O_FOREACH( js, k, v ) { if( strcmp( k, "error" ) == 0 ) error = oauth2_parse_error( v ); if( v->type != json_string ) continue; if( strcmp( k, "access_token" ) == 0 ) atoken = g_strdup( v->u.string.ptr ); if( strcmp( k, "refresh_token" ) == 0 ) rtoken = g_strdup( v->u.string.ptr ); } } json_value_free( js ); } else { /* Facebook use their own odd format here, seems to be URL-encoded. */ GSList *p_in = NULL; oauth_params_parse( &p_in, req->reply_body ); atoken = g_strdup( oauth_params_get( &p_in, "access_token" ) ); rtoken = g_strdup( oauth_params_get( &p_in, "refresh_token" ) ); oauth_params_free( &p_in ); } if( getenv( "BITLBEE_DEBUG" ) ) printf( "Extracted atoken=%s rtoken=%s\n", atoken, rtoken ); if( !atoken && !rtoken && !error ) error = g_strdup( "Unusuable response" ); cb_data->func( cb_data->data, atoken, rtoken, error ); g_free( content_type ); g_free( atoken ); g_free( rtoken ); g_free( error ); g_free( cb_data ); } bitlbee-3.2.1/lib/json_util.c0000644000175000017500000000502212245474076015435 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Helper functions for json.c * * * * Copyright 2012-2012 Wilmer van der Gaast * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation, version * * 2.1. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the Free Software Foundation, * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * * ****************************************************************************/ #include #include #include #include "json_util.h" json_value *json_o_get( const json_value *obj, const json_char *name ) { int i; if( !obj || obj->type != json_object ) return NULL; for( i = 0; i < obj->u.object.length; ++ i) if( strcmp( obj->u.object.values[i].name, name ) == 0 ) return obj->u.object.values[i].value; return NULL; } const char *json_o_str( const json_value *obj, const json_char *name ) { json_value *ret = json_o_get( obj, name ); if( ret && ret->type == json_string ) return ret->u.string.ptr; else return NULL; } char *json_o_strdup( const json_value *obj, const json_char *name ) { json_value *ret = json_o_get( obj, name ); if( ret && ret->type == json_string && ret->u.string.ptr ) return g_memdup( ret->u.string.ptr, ret->u.string.length + 1 ); else return NULL; } bitlbee-3.2.1/lib/http_client.h0000644000175000017500000000743612245474076015764 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* HTTP(S) 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 */ /* http_client allows you to talk (asynchronously, again) to HTTP servers. In the "background" it will send the whole query and wait for a complete response to come back. Initially written for MS Passport authentication, but used for many other things now like OAuth and Twitter. It's very useful for doing quick requests without blocking the whole program. Unfortunately it doesn't support fancy stuff like HTTP keep- alives. */ #include #include "ssl_client.h" struct http_request; typedef enum http_client_flags { HTTPC_STREAMING = 1, HTTPC_EOF = 2, HTTPC_CHUNKED = 4, /* Let's reserve 0x1000000+ for lib users. */ } http_client_flags_t; /* Your callback function should look like this: */ typedef void (*http_input_function)( struct http_request * ); /* This structure will be filled in by the http_dorequest* functions, and it will be passed to the callback function. Use the data field to add your own data. */ struct http_request { char *request; /* The request to send to the server. */ int request_length; /* Its size. */ short status_code; /* The numeric HTTP status code. (Or -1 if something really went wrong) */ char *status_string; /* The error text. */ char *reply_headers; char *reply_body; int body_size; /* The number of bytes in reply_body. */ short redir_ttl; /* You can set it to 0 if you don't want http_client to follow them. */ http_client_flags_t flags; http_input_function func; gpointer data; /* Please don't touch the things down here, you shouldn't need them. */ void *ssl; int fd; int inpa; int bytes_written; int bytes_read; int content_length; /* "Content-Length:" header or -1 */ /* Used in streaming mode. Caller should read from reply_body. */ char *sbuf; size_t sblen; /* Chunked encoding only. Raw chunked stream is decoded from here. */ char *cbuf; size_t cblen; }; /* The _url variant is probably more useful than the raw version. The raw version is probably only useful if you want to do POST requests or if you want to add some extra headers. As you can see, HTTPS connections are also supported (using ssl_client). */ struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data ); struct http_request *http_dorequest_url( char *url_string, http_input_function func, gpointer data ); /* For streaming connections only; flushes len bytes at the start of the buffer. */ void http_flush_bytes( struct http_request *req, size_t len ); void http_close( struct http_request *req ); bitlbee-3.2.1/lib/proxy.h0000644000175000017500000000277412245474076014630 0ustar wilmerwilmer/* * nogaim * * Copyright (C) 1998-1999, Mark Spencer * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* this is the export part of the proxy.c file. it does a little prototype-ing stuff and redefine some net function to mask them with some kind of transparent layer */ #ifndef _PROXY_H_ #define _PROXY_H_ #include #ifndef _WIN32 #include #include #include #endif #include #include #include "events.h" #define PROXY_NONE 0 #define PROXY_HTTP 1 #define PROXY_SOCKS4 2 #define PROXY_SOCKS5 3 extern char proxyhost[128]; extern int proxyport; extern int proxytype; extern char proxyuser[128]; extern char proxypass[128]; G_MODULE_EXPORT int proxy_connect(const char *host, int port, b_event_handler func, gpointer data); #endif /* _PROXY_H_ */ bitlbee-3.2.1/lib/arc.h0000644000175000017500000000423312245474076014204 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple (but secure) ArcFour implementation for safer password storage. * * * * Copyright 2007 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ /* See arc.c for more information. */ struct arc_state { unsigned char S[256]; unsigned char i, j; }; #ifndef G_GNUC_MALLOC #define G_GNUC_MALLOC #endif G_GNUC_MALLOC struct arc_state *arc_keymaker( unsigned char *key, int kl, int cycles ); unsigned char arc_getbyte( struct arc_state *st ); int arc_encode( char *clear, int clear_len, unsigned char **crypt, char *password, int pad_to ); int arc_decode( unsigned char *crypt, int crypt_len, char **clear, const char *password ); bitlbee-3.2.1/lib/ftutil.h0000644000175000017500000000436412245474076014753 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Utility functions for file transfer * * * * Copyright 2008 Uli Meis * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #ifndef AI_NUMERICSERV #define AI_NUMERICSERV 0x0400 /* Don't use name resolution. */ #endif /* Some ifdefs for ulibc and apparently also BSD (Thanks to Whoopie) */ #ifndef HOST_NAME_MAX #include #ifdef MAXHOSTNAMELEN #define HOST_NAME_MAX MAXHOSTNAMELEN #else #define HOST_NAME_MAX 255 #endif #endif /* This function should be used with care. host should be AT LEAST a char[HOST_NAME_MAX+1] and port AT LEAST a char[6]. */ int ft_listen( struct sockaddr_storage *saddr_ptr, char *host, char *port, int copy_fd, int for_bitlbee_client, char **errptr ); bitlbee-3.2.1/lib/json.h0000644000175000017500000001011012245474076014377 0ustar wilmerwilmer /* vim: set et ts=3 sw=3 ft=c: * * Copyright (C) 2012 James McLaughlin et al. All rights reserved. * https://github.com/udp/json-parser * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _JSON_H #define _JSON_H #ifndef json_char #define json_char char #endif #ifdef __cplusplus #include extern "C" { #endif typedef struct { unsigned long max_memory; int settings; } json_settings; #define json_relaxed_commas 1 typedef enum { json_none, json_object, json_array, json_integer, json_double, json_string, json_boolean, json_null } json_type; extern const struct _json_value json_value_none; typedef struct _json_value { struct _json_value * parent; json_type type; union { int boolean; long long integer; double dbl; struct { unsigned int length; json_char * ptr; /* null terminated */ } string; struct { unsigned int length; struct { json_char * name; struct _json_value * value; } * values; } object; struct { unsigned int length; struct _json_value ** values; } array; } u; union { struct _json_value * next_alloc; void * object_mem; } _reserved; /* Some C++ operator sugar */ #ifdef __cplusplus public: inline _json_value () { memset (this, 0, sizeof (_json_value)); } inline const struct _json_value &operator [] (int index) const { if (type != json_array || index < 0 || ((unsigned int) index) >= u.array.length) { return json_value_none; } return *u.array.values [index]; } inline const struct _json_value &operator [] (const char * index) const { if (type != json_object) return json_value_none; for (unsigned int i = 0; i < u.object.length; ++ i) if (!strcmp (u.object.values [i].name, index)) return *u.object.values [i].value; return json_value_none; } inline operator const char * () const { switch (type) { case json_string: return u.string.ptr; default: return ""; }; } inline operator long () const { return u.integer; } inline operator bool () const { return u.boolean != 0; } #endif } json_value; json_value * json_parse (const json_char * json); json_value * json_parse_ex (json_settings * settings, const json_char * json, char * error); void json_value_free (json_value *); #ifdef __cplusplus } /* extern "C" */ #endif #endif bitlbee-3.2.1/lib/ini.h0000644000175000017500000000262312245474076014217 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ /* INI file reading code */ /* 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 _INI_H #define _INI_H typedef struct { int line; char *c_section; char *section; char *key; char *value; int size; char *cur, *tok; char file[]; } ini_t; ini_t *ini_open( char *file ); int ini_read( ini_t *file ); void ini_close( ini_t *file ); #endif bitlbee-3.2.1/lib/url.h0000644000175000017500000000303612245474076014241 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2001-2004 Wilmer van der Gaast and others * \********************************************************************/ /* URL/mirror stuff - Stolen from Axel */ /* 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" #define PROTO_HTTP 2 #define PROTO_HTTPS 5 #define PROTO_SOCKS4 3 #define PROTO_SOCKS5 4 #define PROTO_DEFAULT PROTO_HTTP typedef struct url { int proto; int port; char host[MAX_STRING+1]; char file[MAX_STRING+1]; char user[MAX_STRING+1]; char pass[MAX_STRING+1]; } url_t; int url_set( url_t *url, const char *set_url ); bitlbee-3.2.1/lib/json.c0000644000175000017500000004653012245474076014411 0ustar wilmerwilmer /* vim: set et ts=3 sw=3 ft=c: * * Copyright (C) 2012 James McLaughlin et al. All rights reserved. * https://github.com/udp/json-parser * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "json.h" #ifdef _MSC_VER #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #endif #ifdef __cplusplus const struct _json_value json_value_none; /* zero-d by ctor */ #else const struct _json_value json_value_none = { 0 }; #endif #include #include #include #include #include typedef unsigned short json_uchar; static unsigned char hex_value (json_char c) { if (c >= 'A' && c <= 'F') return (c - 'A') + 10; if (c >= 'a' && c <= 'f') return (c - 'a') + 10; if (c >= '0' && c <= '9') return c - '0'; return 0xFF; } typedef struct { json_settings settings; int first_pass; unsigned long used_memory; unsigned int uint_max; unsigned long ulong_max; } json_state; static void * json_alloc (json_state * state, unsigned long size, int zero) { void * mem; if ((state->ulong_max - state->used_memory) < size) return 0; if (state->settings.max_memory && (state->used_memory += size) > state->settings.max_memory) { return 0; } if (! (mem = zero ? calloc (size, 1) : malloc (size))) return 0; return mem; } static int new_value (json_state * state, json_value ** top, json_value ** root, json_value ** alloc, json_type type) { json_value * value; int values_size; if (!state->first_pass) { value = *top = *alloc; *alloc = (*alloc)->_reserved.next_alloc; if (!*root) *root = value; switch (value->type) { case json_array: if (! (value->u.array.values = (json_value **) json_alloc (state, value->u.array.length * sizeof (json_value *), 0)) ) { return 0; } break; case json_object: values_size = sizeof (*value->u.object.values) * value->u.object.length; if (! ((*(void **) &value->u.object.values) = json_alloc (state, values_size + ((unsigned long) value->u.object.values), 0)) ) { return 0; } value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size; break; case json_string: if (! (value->u.string.ptr = (json_char *) json_alloc (state, (value->u.string.length + 1) * sizeof (json_char), 0)) ) { return 0; } break; default: break; }; value->u.array.length = 0; return 1; } value = (json_value *) json_alloc (state, sizeof (json_value), 1); if (!value) return 0; if (!*root) *root = value; value->type = type; value->parent = *top; if (*alloc) (*alloc)->_reserved.next_alloc = value; *alloc = *top = value; return 1; } #define e_off \ ((int) (i - cur_line_begin)) #define whitespace \ case '\n': ++ cur_line; cur_line_begin = i; \ case ' ': case '\t': case '\r' #define string_add(b) \ do { if (!state.first_pass) string [string_length] = b; ++ string_length; } while (0); const static int flag_next = 1, flag_reproc = 2, flag_need_comma = 4, flag_seek_value = 8, flag_exponent = 16, flag_got_exponent_sign = 32, flag_escaped = 64, flag_string = 128, flag_need_colon = 256, flag_done = 512; json_value * json_parse_ex (json_settings * settings, const json_char * json, char * error_buf) { json_char error [128]; unsigned int cur_line; const json_char * cur_line_begin, * i; json_value * top, * root, * alloc = 0; json_state state; int flags; error[0] = '\0'; memset (&state, 0, sizeof (json_state)); memcpy (&state.settings, settings, sizeof (json_settings)); memset (&state.uint_max, 0xFF, sizeof (state.uint_max)); memset (&state.ulong_max, 0xFF, sizeof (state.ulong_max)); state.uint_max -= 8; /* limit of how much can be added before next check */ state.ulong_max -= 8; for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass) { json_uchar uchar; unsigned char uc_b1, uc_b2, uc_b3, uc_b4; json_char * string = 0; unsigned int string_length = 0; top = root = 0; flags = flag_seek_value; cur_line = 1; cur_line_begin = json; for (i = json ;; ++ i) { json_char b = *i; if (flags & flag_done) { if (!b) break; switch (b) { whitespace: continue; default: sprintf (error, "%d:%d: Trailing garbage: `%c`", cur_line, e_off, b); goto e_failed; }; } if (flags & flag_string) { if (!b) { sprintf (error, "Unexpected EOF in string (at %d:%d)", cur_line, e_off); goto e_failed; } if (string_length > state.uint_max) goto e_overflow; if (flags & flag_escaped) { flags &= ~ flag_escaped; switch (b) { case 'b': string_add ('\b'); break; case 'f': string_add ('\f'); break; case 'n': string_add ('\n'); break; case 'r': string_add ('\r'); break; case 't': string_add ('\t'); break; case 'u': if ((uc_b1 = hex_value (*++ i)) == 0xFF || (uc_b2 = hex_value (*++ i)) == 0xFF || (uc_b3 = hex_value (*++ i)) == 0xFF || (uc_b4 = hex_value (*++ i)) == 0xFF) { sprintf (error, "Invalid character value `%c` (at %d:%d)", b, cur_line, e_off); goto e_failed; } uc_b1 = uc_b1 * 16 + uc_b2; uc_b2 = uc_b3 * 16 + uc_b4; uchar = ((json_char) uc_b1) * 256 + uc_b2; if (sizeof (json_char) >= sizeof (json_uchar) || (uc_b1 == 0 && uc_b2 <= 0x7F)) { string_add ((json_char) uchar); break; } if (uchar <= 0x7FF) { if (state.first_pass) string_length += 2; else { string [string_length ++] = 0xC0 | ((uc_b2 & 0xC0) >> 6) | ((uc_b1 & 0x7) << 2); string [string_length ++] = 0x80 | (uc_b2 & 0x3F); } break; } if (state.first_pass) string_length += 3; else { string [string_length ++] = 0xE0 | ((uc_b1 & 0xF0) >> 4); string [string_length ++] = 0x80 | ((uc_b1 & 0xF) << 2) | ((uc_b2 & 0xC0) >> 6); string [string_length ++] = 0x80 | (uc_b2 & 0x3F); } break; default: string_add (b); }; continue; } if (b == '\\') { flags |= flag_escaped; continue; } if (b == '"') { if (!state.first_pass) string [string_length] = 0; flags &= ~ flag_string; string = 0; switch (top->type) { case json_string: top->u.string.length = string_length; flags |= flag_next; break; case json_object: if (state.first_pass) (*(json_char **) &top->u.object.values) += string_length + 1; else { top->u.object.values [top->u.object.length].name = (json_char *) top->_reserved.object_mem; (*(json_char **) &top->_reserved.object_mem) += string_length + 1; } flags |= flag_seek_value | flag_need_colon; continue; default: break; }; } else { string_add (b); continue; } } if (flags & flag_seek_value) { switch (b) { whitespace: continue; case ']': if (top->type == json_array) flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next; else if (!state.settings.settings & json_relaxed_commas) { sprintf (error, "%d:%d: Unexpected ]", cur_line, e_off); goto e_failed; } break; default: if (flags & flag_need_comma) { if (b == ',') { flags &= ~ flag_need_comma; continue; } else { sprintf (error, "%d:%d: Expected , before %c", cur_line, e_off, b); goto e_failed; } } if (flags & flag_need_colon) { if (b == ':') { flags &= ~ flag_need_colon; continue; } else { sprintf (error, "%d:%d: Expected : before %c", cur_line, e_off, b); goto e_failed; } } flags &= ~ flag_seek_value; switch (b) { case '{': if (!new_value (&state, &top, &root, &alloc, json_object)) goto e_alloc_failure; continue; case '[': if (!new_value (&state, &top, &root, &alloc, json_array)) goto e_alloc_failure; flags |= flag_seek_value; continue; case '"': if (!new_value (&state, &top, &root, &alloc, json_string)) goto e_alloc_failure; flags |= flag_string; string = top->u.string.ptr; string_length = 0; continue; case 't': if (*(++ i) != 'r' || *(++ i) != 'u' || *(++ i) != 'e') goto e_unknown_value; if (!new_value (&state, &top, &root, &alloc, json_boolean)) goto e_alloc_failure; top->u.boolean = 1; flags |= flag_next; break; case 'f': if (*(++ i) != 'a' || *(++ i) != 'l' || *(++ i) != 's' || *(++ i) != 'e') goto e_unknown_value; if (!new_value (&state, &top, &root, &alloc, json_boolean)) goto e_alloc_failure; flags |= flag_next; break; case 'n': if (*(++ i) != 'u' || *(++ i) != 'l' || *(++ i) != 'l') goto e_unknown_value; if (!new_value (&state, &top, &root, &alloc, json_null)) goto e_alloc_failure; flags |= flag_next; break; default: if (isdigit (b) || b == '-') { if (!new_value (&state, &top, &root, &alloc, json_integer)) goto e_alloc_failure; flags &= ~ (flag_exponent | flag_got_exponent_sign); if (state.first_pass) continue; if (top->type == json_double) top->u.dbl = g_ascii_strtod (i, (json_char **) &i); else top->u.integer = g_ascii_strtoll (i, (json_char **) &i, 10); flags |= flag_next | flag_reproc; } else { sprintf (error, "%d:%d: Unexpected %c when seeking value", cur_line, e_off, b); goto e_failed; } }; }; } else { switch (top->type) { case json_object: switch (b) { whitespace: continue; case '"': if (flags & flag_need_comma && (!state.settings.settings & json_relaxed_commas)) { sprintf (error, "%d:%d: Expected , before \"", cur_line, e_off); goto e_failed; } flags |= flag_string; string = (json_char *) top->_reserved.object_mem; string_length = 0; break; case '}': flags = (flags & ~ flag_need_comma) | flag_next; break; case ',': if (flags & flag_need_comma) { flags &= ~ flag_need_comma; break; } default: sprintf (error, "%d:%d: Unexpected `%c` in object", cur_line, e_off, b); goto e_failed; }; break; case json_integer: case json_double: if (isdigit (b)) continue; if (b == 'e' || b == 'E') { if (!(flags & flag_exponent)) { flags |= flag_exponent; top->type = json_double; continue; } } else if (b == '+' || b == '-') { if (flags & flag_exponent && !(flags & flag_got_exponent_sign)) { flags |= flag_got_exponent_sign; continue; } } else if (b == '.' && top->type == json_integer) { top->type = json_double; continue; } flags |= flag_next | flag_reproc; break; default: break; }; } if (flags & flag_reproc) { flags &= ~ flag_reproc; -- i; } if (flags & flag_next) { flags = (flags & ~ flag_next) | flag_need_comma; if (!top->parent) { /* root value done */ flags |= flag_done; continue; } if (top->parent->type == json_array) flags |= flag_seek_value; if (!state.first_pass) { json_value * parent = top->parent; switch (parent->type) { case json_object: parent->u.object.values [parent->u.object.length].value = top; break; case json_array: parent->u.array.values [parent->u.array.length] = top; break; default: break; }; } if ( (++ top->parent->u.array.length) > state.uint_max) goto e_overflow; top = top->parent; continue; } } alloc = root; } return root; e_unknown_value: sprintf (error, "%d:%d: Unknown value", cur_line, e_off); goto e_failed; e_alloc_failure: strcpy (error, "Memory allocation failure"); goto e_failed; e_overflow: sprintf (error, "%d:%d: Too long (caught overflow)", cur_line, e_off); goto e_failed; e_failed: if (error_buf) { if (*error) strcpy (error_buf, error); else strcpy (error_buf, "Unknown error"); } if (state.first_pass) alloc = root; while (alloc) { top = alloc->_reserved.next_alloc; free (alloc); alloc = top; } if (!state.first_pass) json_value_free (root); return 0; } json_value * json_parse (const json_char * json) { json_settings settings; memset (&settings, 0, sizeof (json_settings)); return json_parse_ex (&settings, json, 0); } void json_value_free (json_value * value) { json_value * cur_value; if (!value) return; value->parent = 0; while (value) { switch (value->type) { case json_array: if (!value->u.array.length) { free (value->u.array.values); break; } value = value->u.array.values [-- value->u.array.length]; continue; case json_object: if (!value->u.object.length) { free (value->u.object.values); break; } value = value->u.object.values [-- value->u.object.length].value; continue; case json_string: free (value->u.string.ptr); break; default: break; }; cur_value = value; value = value->parent; free (cur_value); } } bitlbee-3.2.1/lib/events.h0000644000175000017500000000655612245474076014755 0ustar wilmerwilmer/* * nogaim * * Copyright (C) 2006 Wilmer van der Gaast and others * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* This stuff used to be in proxy.c too, but I split it off so BitlBee can use other libraries (like libevent) to handle events. proxy.c is one very nice piece of work from Gaim. It connects to a TCP server in the back- ground and calls a callback function once the connection is ready to use. This function (proxy_connect()) can be found in proxy.c. (It also transparently handles HTTP/SOCKS proxies, when necessary.) This file offers some extra event handling toys, which will be handled by GLib or libevent. The advantage of using libevent is that it can use more advanced I/O polling functions like epoll() in recent Linux kernels. This should improve BitlBee's scalability. */ #ifndef _EVENTS_H_ #define _EVENTS_H_ #include #ifndef _WIN32 #include #include #include #endif #include #include /* The conditions you can pass to b_input_add()/that will be passed to the given callback function. */ typedef enum { B_EV_IO_READ = 1 << 0, B_EV_IO_WRITE = 1 << 1, B_EV_FLAG_FORCE_ONCE = 1 << 16, B_EV_FLAG_FORCE_REPEAT = 1 << 17, } b_input_condition; typedef gboolean (*b_event_handler)(gpointer data, gint fd, b_input_condition cond); /* For internal use. */ #define GAIM_READ_COND (G_IO_IN | G_IO_HUP | G_IO_ERR) #define GAIM_WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL) #define GAIM_ERR_COND (G_IO_HUP | G_IO_ERR | G_IO_NVAL) /* #define event_debug( x... ) printf( x ) */ #define event_debug( x... ) /* Call this once when the program starts. It'll initialize the event handler library (if necessary) and then return immediately. */ G_MODULE_EXPORT void b_main_init(); /* This one enters the event loop. It shouldn't return until one of the event handlers calls b_main_quit(). */ G_MODULE_EXPORT void b_main_run(); G_MODULE_EXPORT void b_main_quit(); /* Add event handlers (for I/O or a timeout). The event handler will be called every time the event "happens", until your event handler returns FALSE (or until you remove it using b_event_remove(). As usual, the data argument can be used to pass your own data to the event handler. */ G_MODULE_EXPORT gint b_input_add(int fd, b_input_condition cond, b_event_handler func, gpointer data); G_MODULE_EXPORT gint b_timeout_add(gint timeout, b_event_handler func, gpointer data); G_MODULE_EXPORT void b_event_remove(gint id); /* With libevent, this one also cleans up event handlers if that wasn't already done (the caller is expected to do so but may miss it sometimes). */ G_MODULE_EXPORT void closesocket(int fd); #endif /* _EVENTS_H_ */ bitlbee-3.2.1/lib/xmltree.h0000644000175000017500000000744112245474076015123 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple XML (stream) parse tree handling code (Jabber/XMPP, mainly) * * * * Copyright 2006-2012 Wilmer van der Gaast * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation, version * * 2.1. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the Free Software Foundation, * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * * ****************************************************************************/ #ifndef _XMLTREE_H #define _XMLTREE_H typedef enum { XT_COMPLETE = 1, /* reached */ XT_SEEN = 2, /* Handler called (or not defined) */ } xt_flags; typedef enum { XT_ABORT, /* Abort, don't handle the rest anymore */ XT_HANDLED, /* Handled this tag properly, go to the next one */ XT_NEXT /* Try if there's another matching handler */ } xt_status; struct xt_attr { char *key, *value; }; struct xt_node { struct xt_node *parent; struct xt_node *children; char *name; struct xt_attr *attr; char *text; int text_len; struct xt_node *next; xt_flags flags; }; typedef xt_status (*xt_handler_func) ( struct xt_node *node, gpointer data ); struct xt_handler_entry { char *name, *parent; xt_handler_func func; }; struct xt_parser { GMarkupParseContext *parser; struct xt_node *root; struct xt_node *cur; const struct xt_handler_entry *handlers; gpointer data; GError *gerr; }; struct xt_parser *xt_new( const struct xt_handler_entry *handlers, gpointer data ); void xt_reset( struct xt_parser *xt ); int xt_feed( struct xt_parser *xt, const char *text, int text_len ); int xt_handle( struct xt_parser *xt, struct xt_node *node, int depth ); void xt_cleanup( struct xt_parser *xt, struct xt_node *node, int depth ); struct xt_node *xt_from_string( const char *in, int text_len ); char *xt_to_string( struct xt_node *node ); char *xt_to_string_i( struct xt_node *node ); void xt_print( struct xt_node *node ); struct xt_node *xt_dup( struct xt_node *node ); void xt_free_node( struct xt_node *node ); void xt_free( struct xt_parser *xt ); struct xt_node *xt_find_node( struct xt_node *node, const char *name ); struct xt_node *xt_find_path( struct xt_node *node, const char *name ); char *xt_find_attr( struct xt_node *node, const char *key ); struct xt_node *xt_new_node( char *name, const char *text, struct xt_node *children ); void xt_add_child( struct xt_node *parent, struct xt_node *child ); void xt_insert_child( struct xt_node *parent, struct xt_node *child ); void xt_add_attr( struct xt_node *node, const char *key, const char *value ); int xt_remove_attr( struct xt_node *node, const char *key ); #endif bitlbee-3.2.1/lib/ssl_openssl.c0000644000175000017500000001752412245474076016005 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* SSL module - OpenSSL version */ /* 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 #include #include #include #include #include "bitlbee.h" #include "proxy.h" #include "ssl_client.h" #include "sock.h" int ssl_errno = 0; static gboolean initialized = FALSE; struct scd { ssl_input_function func; gpointer data; int fd; gboolean established; gboolean verify; char *hostname; int inpa; int lasterr; /* Necessary for SSL_get_error */ SSL *ssl; }; static SSL_CTX *ssl_ctx; static void ssl_conn_free( struct scd *conn ); static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ); static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond ); static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond ); void ssl_init( void ) { const SSL_METHOD *meth; SSL_library_init(); meth = TLSv1_client_method(); ssl_ctx = SSL_CTX_new( meth ); initialized = TRUE; } void *ssl_connect( char *host, int port, gboolean verify, ssl_input_function func, gpointer data ) { struct scd *conn = g_new0( struct scd, 1 ); conn->fd = proxy_connect( host, port, ssl_connected, conn ); if( conn->fd < 0 ) { ssl_conn_free( conn ); return NULL; } conn->func = func; conn->data = data; conn->inpa = -1; conn->hostname = g_strdup( host ); return conn; } void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data ) { struct scd *conn = g_new0( struct scd, 1 ); conn->fd = fd; conn->func = func; conn->data = data; conn->inpa = -1; conn->verify = verify && global.conf->cafile; conn->hostname = g_strdup( hostname ); /* This function should be called via a (short) timeout instead of directly from here, because these SSL calls are *supposed* to be *completely* asynchronous and not ready yet when this function (or *_connect, for examle) returns. Also, errors are reported via the callback function, not via this function's return value. In short, doing things like this makes the rest of the code a lot simpler. */ b_timeout_add( 1, ssl_starttls_real, conn ); return conn; } static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition cond ) { struct scd *conn = data; return ssl_connected( conn, conn->fd, B_EV_IO_WRITE ); } static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ) { struct scd *conn = data; if( conn->verify ) { /* Right now we don't have any verification functionality for OpenSSL. */ conn->func( conn->data, 1, NULL, cond ); if( source >= 0 ) closesocket( source ); ssl_conn_free( conn ); return FALSE; } if( source == -1 ) goto ssl_connected_failure; if( !initialized ) { ssl_init(); } if( ssl_ctx == NULL ) goto ssl_connected_failure; conn->ssl = SSL_new( ssl_ctx ); if( conn->ssl == NULL ) goto ssl_connected_failure; /* We can do at least the handshake with non-blocking I/O */ sock_make_nonblocking( conn->fd ); SSL_set_fd( conn->ssl, conn->fd ); if( conn->hostname && !isdigit( conn->hostname[0] ) ) SSL_set_tlsext_host_name( conn->ssl, conn->hostname ); return ssl_handshake( data, source, cond ); ssl_connected_failure: conn->func( conn->data, 0, NULL, cond ); ssl_disconnect( conn ); return FALSE; } static gboolean ssl_handshake( gpointer data, gint source, b_input_condition cond ) { struct scd *conn = data; int st; if( ( st = SSL_connect( conn->ssl ) ) < 0 ) { conn->lasterr = SSL_get_error( conn->ssl, st ); if( conn->lasterr != SSL_ERROR_WANT_READ && conn->lasterr != SSL_ERROR_WANT_WRITE ) { conn->func( conn->data, 0, NULL, cond ); ssl_disconnect( conn ); return FALSE; } conn->inpa = b_input_add( conn->fd, ssl_getdirection( conn ), ssl_handshake, data ); return FALSE; } conn->established = TRUE; sock_make_blocking( conn->fd ); /* For now... */ conn->func( conn->data, 0, conn, cond ); return FALSE; } int ssl_read( void *conn, char *buf, int len ) { int st; if( !((struct scd*)conn)->established ) { ssl_errno = SSL_NOHANDSHAKE; return -1; } st = SSL_read( ((struct scd*)conn)->ssl, buf, len ); ssl_errno = SSL_OK; if( st <= 0 ) { ((struct scd*)conn)->lasterr = SSL_get_error( ((struct scd*)conn)->ssl, st ); if( ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_READ || ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_WRITE ) ssl_errno = SSL_AGAIN; } if( 0 && getenv( "BITLBEE_DEBUG" ) && st > 0 ) write( 1, buf, st ); return st; } int ssl_write( void *conn, const char *buf, int len ) { int st; if( !((struct scd*)conn)->established ) { ssl_errno = SSL_NOHANDSHAKE; return -1; } st = SSL_write( ((struct scd*)conn)->ssl, buf, len ); if( 0 && getenv( "BITLBEE_DEBUG" ) && st > 0 ) write( 1, buf, st ); ssl_errno = SSL_OK; if( st <= 0 ) { ((struct scd*)conn)->lasterr = SSL_get_error( ((struct scd*)conn)->ssl, st ); if( ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_READ || ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_WRITE ) ssl_errno = SSL_AGAIN; } return st; } int ssl_pending( void *conn ) { return ( ((struct scd*)conn) && ((struct scd*)conn)->established ) ? SSL_pending( ((struct scd*)conn)->ssl ) > 0 : 0; } static void ssl_conn_free( struct scd *conn ) { SSL_free( conn->ssl ); g_free( conn->hostname ); g_free( conn ); } void ssl_disconnect( void *conn_ ) { struct scd *conn = conn_; if( conn->inpa != -1 ) b_event_remove( conn->inpa ); if( conn->established ) SSL_shutdown( conn->ssl ); closesocket( conn->fd ); ssl_conn_free( conn ); } int ssl_getfd( void *conn ) { return( ((struct scd*)conn)->fd ); } b_input_condition ssl_getdirection( void *conn ) { return( ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_WRITE ? B_EV_IO_WRITE : B_EV_IO_READ ); } char *ssl_verify_strerror( int code ) { return g_strdup( "SSL certificate verification not supported by BitlBee OpenSSL code." ); } size_t ssl_des3_encrypt(const unsigned char *key, size_t key_len, const unsigned char *input, size_t input_len, const unsigned char *iv, unsigned char **res) { int output_length = 0; EVP_CIPHER_CTX ctx; *res = g_new0(unsigned char, 72); /* Don't set key or IV because we will modify the parameters */ EVP_CIPHER_CTX_init(&ctx); EVP_CipherInit_ex(&ctx, EVP_des_ede3_cbc(), NULL, NULL, NULL, 1); EVP_CIPHER_CTX_set_key_length(&ctx, key_len); EVP_CIPHER_CTX_set_padding(&ctx, 0); /* We finished modifying parameters so now we can set key and IV */ EVP_CipherInit_ex(&ctx, NULL, NULL, key, iv, 1); EVP_CipherUpdate(&ctx, *res, &output_length, input, input_len); EVP_CipherFinal_ex(&ctx, *res, &output_length); EVP_CIPHER_CTX_cleanup(&ctx); //EVP_cleanup(); return output_length; } bitlbee-3.2.1/lib/sha1.c0000644000175000017500000002626712245474076014301 0ustar wilmerwilmer/* * SHA1 hashing code copied from Lepton's crack * * Adapted to be API-compatible with the previous (GPL-incompatible) code. */ /* * sha1.c * * Description: * This file implements the Secure Hashing Algorithm 1 as * defined in FIPS PUB 180-1 published April 17, 1995. * * The SHA-1, produces a 160-bit message digest for a given * data stream. It should take about 2**n steps to find a * message with the same digest as a given message and * 2**(n/2) to find any two messages with the same digest, * when n is the digest size in bits. Therefore, this * algorithm can serve as a means of providing a * "fingerprint" for a message. * * Portability Issues: * SHA-1 is defined in terms of 32-bit "words". This code * uses (included via "sha1.h" to define 32 and 8 * bit unsigned integer types. If your C compiler does not * support 32 bit unsigned integers, this code is not * appropriate. * * Caveats: * SHA-1 is designed to work with messages less than 2^64 bits * long. Although SHA-1 allows a message digest to be generated * for messages of any number of bits less than 2^64, this * implementation only works with messages with a length that is * a multiple of the size of an 8-bit character. * */ #include #include #include "sha1.h" /* * Define the SHA1 circular left shift macro */ #define SHA1CircularShift(bits,word) \ (((word) << (bits)) | ((word) >> (32-(bits)))) /* Local Function Prototyptes */ static void sha1_pad(sha1_state_t *); static void sha1_process_block(sha1_state_t *); /* * sha1_init * * Description: * This function will initialize the sha1_state_t in preparation * for computing a new SHA1 message digest. * * Parameters: * context: [in/out] * The context to reset. * * Returns: * sha Error Code. * */ int sha1_init(sha1_state_t * context) { context->Length_Low = 0; context->Length_High = 0; context->Message_Block_Index = 0; context->Intermediate_Hash[0] = 0x67452301; context->Intermediate_Hash[1] = 0xEFCDAB89; context->Intermediate_Hash[2] = 0x98BADCFE; context->Intermediate_Hash[3] = 0x10325476; context->Intermediate_Hash[4] = 0xC3D2E1F0; context->Computed = 0; context->Corrupted = 0; return shaSuccess; } /* * sha1_finish * * Description: * This function will return the 160-bit message digest into the * Message_Digest array provided by the caller. * NOTE: The first octet of hash is stored in the 0th element, * the last octet of hash in the 19th element. * * Parameters: * context: [in/out] * The context to use to calculate the SHA-1 hash. * Message_Digest: [out] * Where the digest is returned. * * Returns: * sha Error Code. * */ int sha1_finish(sha1_state_t * context, uint8_t Message_Digest[sha1_hash_size]) { int i; if (!context || !Message_Digest) { return shaNull; } if (context->Corrupted) { return context->Corrupted; } if (!context->Computed) { sha1_pad(context); for (i = 0; i < 64; ++i) { /* message may be sensitive, clear it out */ context->Message_Block[i] = 0; } context->Length_Low = 0; /* and clear length */ context->Length_High = 0; context->Computed = 1; } for (i = 0; i < sha1_hash_size; ++i) { Message_Digest[i] = context->Intermediate_Hash[i >> 2] >> 8 * (3 - (i & 0x03)); } return shaSuccess; } /* * sha1_append * * Description: * This function accepts an array of octets as the next portion * of the message. * * Parameters: * context: [in/out] * The SHA context to update * message_array: [in] * An array of characters representing the next portion of * the message. * length: [in] * The length of the message in message_array * * Returns: * sha Error Code. * */ int sha1_append(sha1_state_t * context, const uint8_t * message_array, unsigned length) { if (!length) { return shaSuccess; } if (!context || !message_array) { return shaNull; } if (context->Computed) { context->Corrupted = shaStateError; return shaStateError; } if (context->Corrupted) { return context->Corrupted; } while (length-- && !context->Corrupted) { context->Message_Block[context->Message_Block_Index++] = (*message_array & 0xFF); context->Length_Low += 8; if (context->Length_Low == 0) { context->Length_High++; if (context->Length_High == 0) { /* Message is too long */ context->Corrupted = 1; } } if (context->Message_Block_Index == 64) { sha1_process_block(context); } message_array++; } return shaSuccess; } /* * sha1_process_block * * Description: * This function will process the next 512 bits of the message * stored in the Message_Block array. * * Parameters: * None. * * Returns: * Nothing. * * Comments: * Many of the variable names in this code, especially the * single character names, were used because those were the * names used in the publication. * * */ static void sha1_process_block(sha1_state_t * context) { const uint32_t K[] = { /* Constants defined in SHA-1 */ 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 }; int t; /* Loop counter */ uint32_t temp; /* Temporary word value */ uint32_t W[80]; /* Word sequence */ uint32_t A, B, C, D, E; /* Word buffers */ /* * Initialize the first 16 words in the array W */ for (t = 0; t < 16; t++) { W[t] = context->Message_Block[t * 4] << 24; W[t] |= context->Message_Block[t * 4 + 1] << 16; W[t] |= context->Message_Block[t * 4 + 2] << 8; W[t] |= context->Message_Block[t * 4 + 3]; } for (t = 16; t < 80; t++) { W[t] = SHA1CircularShift(1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]); } A = context->Intermediate_Hash[0]; B = context->Intermediate_Hash[1]; C = context->Intermediate_Hash[2]; D = context->Intermediate_Hash[3]; E = context->Intermediate_Hash[4]; for (t = 0; t < 20; t++) { temp = SHA1CircularShift(5, A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; E = D; D = C; C = SHA1CircularShift(30, B); B = A; A = temp; } for (t = 20; t < 40; t++) { temp = SHA1CircularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[1]; E = D; D = C; C = SHA1CircularShift(30, B); B = A; A = temp; } for (t = 40; t < 60; t++) { temp = SHA1CircularShift(5, A) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; E = D; D = C; C = SHA1CircularShift(30, B); B = A; A = temp; } for (t = 60; t < 80; t++) { temp = SHA1CircularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[3]; E = D; D = C; C = SHA1CircularShift(30, B); B = A; A = temp; } context->Intermediate_Hash[0] += A; context->Intermediate_Hash[1] += B; context->Intermediate_Hash[2] += C; context->Intermediate_Hash[3] += D; context->Intermediate_Hash[4] += E; context->Message_Block_Index = 0; } /* * sha1_pad * * Description: * According to the standard, the message must be padded to an even * 512 bits. The first padding bit must be a '1'. The last 64 * bits represent the length of the original message. All bits in * between should be 0. This function will pad the message * according to those rules by filling the Message_Block array * accordingly. It will also call the ProcessMessageBlock function * provided appropriately. When it returns, it can be assumed that * the message digest has been computed. * * Parameters: * context: [in/out] * The context to pad * ProcessMessageBlock: [in] * The appropriate SHA*ProcessMessageBlock function * Returns: * Nothing. * */ static void sha1_pad(sha1_state_t * context) { /* * Check to see if the current message block is too small to hold * the initial padding bits and length. If so, we will pad the * block, process it, and then continue padding into a second * block. */ if (context->Message_Block_Index > 55) { context->Message_Block[context->Message_Block_Index++] = 0x80; while (context->Message_Block_Index < 64) { context->Message_Block[context-> Message_Block_Index++] = 0; } sha1_process_block(context); while (context->Message_Block_Index < 56) { context->Message_Block[context-> Message_Block_Index++] = 0; } } else { context->Message_Block[context->Message_Block_Index++] = 0x80; while (context->Message_Block_Index < 56) { context->Message_Block[context-> Message_Block_Index++] = 0; } } /* * Store the message length as the last 8 octets */ context->Message_Block[56] = context->Length_High >> 24; context->Message_Block[57] = context->Length_High >> 16; context->Message_Block[58] = context->Length_High >> 8; context->Message_Block[59] = context->Length_High; context->Message_Block[60] = context->Length_Low >> 24; context->Message_Block[61] = context->Length_Low >> 16; context->Message_Block[62] = context->Length_Low >> 8; context->Message_Block[63] = context->Length_Low; sha1_process_block(context); } #define HMAC_BLOCK_SIZE 64 /* BitlBee addition: */ void sha1_hmac(const char *key_, size_t key_len, const char *payload, size_t payload_len, uint8_t Message_Digest[sha1_hash_size]) { sha1_state_t sha1; uint8_t hash[sha1_hash_size]; uint8_t key[HMAC_BLOCK_SIZE+1]; int i; if( key_len == 0 ) key_len = strlen( key_ ); if( payload_len == 0 ) payload_len = strlen( payload ); /* Create K. If our current key is >64 chars we have to hash it, otherwise just pad. */ memset( key, 0, HMAC_BLOCK_SIZE + 1 ); if( key_len > HMAC_BLOCK_SIZE ) { sha1_init( &sha1 ); sha1_append( &sha1, (uint8_t*) key_, key_len ); sha1_finish( &sha1, key ); } else { memcpy( key, key_, key_len ); } /* Inner part: H(K XOR 0x36, text) */ sha1_init( &sha1 ); for( i = 0; i < HMAC_BLOCK_SIZE; i ++ ) key[i] ^= 0x36; sha1_append( &sha1, key, HMAC_BLOCK_SIZE ); sha1_append( &sha1, (const uint8_t*) payload, payload_len ); sha1_finish( &sha1, hash ); /* Final result: H(K XOR 0x5C, inner stuff) */ sha1_init( &sha1 ); for( i = 0; i < HMAC_BLOCK_SIZE; i ++ ) key[i] ^= 0x36 ^ 0x5c; sha1_append( &sha1, key, HMAC_BLOCK_SIZE ); sha1_append( &sha1, hash, sha1_hash_size ); sha1_finish( &sha1, Message_Digest ); } /* I think this follows the scheme described on: http://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29 My random data comes from a SHA1 generator but hey, it's random enough for me, and RFC 4122 looks way more complicated than I need this to be. Returns a value that must be free()d. */ char *sha1_random_uuid( sha1_state_t * context ) { uint8_t dig[sha1_hash_size]; char *ret = g_new0( char, 40 ); /* 36 chars + \0 */ int i, p; sha1_finish(context, dig); for( p = i = 0; i < 16; i ++ ) { if( i == 4 || i == 6 || i == 8 || i == 10 ) ret[p++] = '-'; if( i == 6 ) dig[i] = ( dig[i] & 0x0f ) | 0x40; if( i == 8 ) dig[i] = ( dig[i] & 0x30 ) | 0x80; sprintf( ret + p, "%02x", dig[i] ); p += 2; } ret[p] = '\0'; return ret; } bitlbee-3.2.1/lib/http_client.c0000644000175000017500000004331212245474076015750 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 Wilmer van der Gaast and others * \********************************************************************/ /* HTTP(S) 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 */ #include #include #include "http_client.h" #include "url.h" #include "sock.h" static gboolean http_connected( gpointer data, int source, b_input_condition cond ); static gboolean http_ssl_connected( gpointer data, int returncode, void *source, b_input_condition cond ); static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond ); static void http_free( struct http_request *req ); struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data ) { struct http_request *req; int error = 0; req = g_new0( struct http_request, 1 ); if( ssl ) { req->ssl = ssl_connect( host, port, TRUE, http_ssl_connected, req ); if( req->ssl == NULL ) error = 1; } else { req->fd = proxy_connect( host, port, http_connected, req ); if( req->fd < 0 ) error = 1; } if( error ) { http_free( req ); return NULL; } req->func = func; req->data = data; req->request = g_strdup( request ); req->request_length = strlen( request ); req->redir_ttl = 3; req->content_length = -1; if( getenv( "BITLBEE_DEBUG" ) ) printf( "About to send HTTP request:\n%s\n", req->request ); return req; } struct http_request *http_dorequest_url( char *url_string, http_input_function func, gpointer data ) { url_t *url = g_new0( url_t, 1 ); char *request; void *ret; if( !url_set( url, url_string ) ) { g_free( url ); return NULL; } if( url->proto != PROTO_HTTP && url->proto != PROTO_HTTPS ) { g_free( url ); return NULL; } request = g_strdup_printf( "GET %s HTTP/1.0\r\n" "Host: %s\r\n" "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n" "\r\n", url->file, url->host ); ret = http_dorequest( url->host, url->port, url->proto == PROTO_HTTPS, request, func, data ); g_free( url ); g_free( request ); return ret; } /* This one is actually pretty simple... Might get more calls if we can't write the whole request at once. */ static gboolean http_connected( gpointer data, int source, b_input_condition cond ) { struct http_request *req = data; int st; if( source < 0 ) goto error; if( req->inpa > 0 ) b_event_remove( req->inpa ); sock_make_nonblocking( req->fd ); if( req->ssl ) { st = ssl_write( req->ssl, req->request + req->bytes_written, req->request_length - req->bytes_written ); if( st < 0 ) { if( ssl_errno != SSL_AGAIN ) { ssl_disconnect( req->ssl ); goto error; } } } else { st = write( source, req->request + req->bytes_written, req->request_length - req->bytes_written ); if( st < 0 ) { if( !sockerr_again() ) { closesocket( req->fd ); goto error; } } } if( st > 0 ) req->bytes_written += st; if( req->bytes_written < req->request_length ) req->inpa = b_input_add( source, req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_WRITE, http_connected, req ); else req->inpa = b_input_add( source, B_EV_IO_READ, http_incoming_data, req ); return FALSE; error: if( req->status_string == NULL ) req->status_string = g_strdup( "Error while writing HTTP request" ); req->func( req ); http_free( req ); return FALSE; } static gboolean http_ssl_connected( gpointer data, int returncode, void *source, b_input_condition cond ) { struct http_request *req = data; if( source == NULL ) { if( returncode != 0 ) { char *err = ssl_verify_strerror( returncode ); req->status_string = g_strdup_printf( "Certificate verification problem 0x%x: %s", returncode, err ? err : "Unknown" ); g_free( err ); } return http_connected( data, -1, cond ); } req->fd = ssl_getfd( source ); return http_connected( data, req->fd, cond ); } typedef enum { CR_OK, CR_EOF, CR_ERROR, CR_ABORT, } http_ret_t; static gboolean http_handle_headers( struct http_request *req ); static http_ret_t http_process_chunked_data( struct http_request *req, const char *buffer, int len ); static http_ret_t http_process_data( struct http_request *req, const char *buffer, int len ); static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond ) { struct http_request *req = data; char buffer[4096]; int st; if( req->inpa > 0 ) b_event_remove( req->inpa ); if( req->ssl ) { st = ssl_read( req->ssl, buffer, sizeof( buffer ) ); if( st < 0 ) { if( ssl_errno != SSL_AGAIN ) { /* goto cleanup; */ /* YAY! We have to deal with crappy Microsoft servers that LOVE to send invalid TLS packets that abort connections! \o/ */ goto eof; } } else if( st == 0 ) { goto eof; } } else { st = read( req->fd, buffer, sizeof( buffer ) ); if( st < 0 ) { if( !sockerr_again() ) { req->status_string = g_strdup( strerror( errno ) ); goto cleanup; } } else if( st == 0 ) { goto eof; } } if( st > 0 ) { http_ret_t c; if( req->flags & HTTPC_CHUNKED ) c = http_process_chunked_data( req, buffer, st ); else c = http_process_data( req, buffer, st ); if( c == CR_EOF ) goto eof; else if( c == CR_ERROR || c == CR_ABORT ) return FALSE; } if( req->content_length != -1 && req->body_size >= req->content_length ) goto eof; if( ssl_pending( req->ssl ) ) return http_incoming_data( data, source, cond ); /* There will be more! */ req->inpa = b_input_add( req->fd, req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_READ, http_incoming_data, req ); return FALSE; eof: req->flags |= HTTPC_EOF; /* Maybe if the webserver is overloaded, or when there's bad SSL support... */ if( req->bytes_read == 0 ) { req->status_string = g_strdup( "Empty HTTP reply" ); goto cleanup; } cleanup: if( req->ssl ) ssl_disconnect( req->ssl ); else closesocket( req->fd ); if( req->body_size < req->content_length ) { req->status_code = -1; g_free( req->status_string ); req->status_string = g_strdup( "Response truncated" ); } if( getenv( "BITLBEE_DEBUG" ) && req ) printf( "Finishing HTTP request with status: %s\n", req->status_string ? req->status_string : "NULL" ); req->func( req ); http_free( req ); return FALSE; } static http_ret_t http_process_chunked_data( struct http_request *req, const char *buffer, int len ) { char *chunk, *eos, *s; if( len < 0 ) return TRUE; if( len > 0 ) { req->cbuf = g_realloc( req->cbuf, req->cblen + len + 1 ); memcpy( req->cbuf + req->cblen, buffer, len ); req->cblen += len; req->cbuf[req->cblen] = '\0'; } /* Turns out writing a proper chunked-encoding state machine is not that simple. :-( I've tested this one feeding it byte by byte so I hope it's solid now. */ chunk = req->cbuf; eos = req->cbuf + req->cblen; while( TRUE ) { int clen = 0; /* Might be a \r\n from the last chunk. */ s = chunk; while( isspace( *s ) ) s ++; /* Chunk length. Might be incomplete. */ if( s < eos && sscanf( s, "%x", &clen ) != 1 ) return CR_ERROR; while( isxdigit( *s ) ) s ++; /* If we read anything here, it *must* be \r\n. */ if( strncmp( s, "\r\n", MIN( 2, eos - s ) ) != 0 ) return CR_ERROR; s += 2; if( s >= eos ) break; /* 0-length chunk means end of response. */ if( clen == 0 ) return CR_EOF; /* Wait for the whole chunk to arrive. */ if( s + clen > eos ) break; if( http_process_data( req, s, clen ) != CR_OK ) return CR_ABORT; chunk = s + clen; } if( chunk != req->cbuf ) { req->cblen = eos - chunk; s = g_memdup( chunk, req->cblen + 1 ); g_free( req->cbuf ); req->cbuf = s; } return CR_OK; } static http_ret_t http_process_data( struct http_request *req, const char *buffer, int len ) { if( len <= 0 ) return CR_OK; if( !req->reply_body ) { req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + len + 1 ); memcpy( req->reply_headers + req->bytes_read, buffer, len ); req->bytes_read += len; req->reply_headers[req->bytes_read] = '\0'; if( strstr( req->reply_headers, "\r\n\r\n" ) || strstr( req->reply_headers, "\n\n" ) ) { /* We've now received all headers. Look for something interesting. */ if( !http_handle_headers( req ) ) return CR_ABORT; /* Start parsing the body as chunked if required. */ if( req->flags & HTTPC_CHUNKED ) return http_process_chunked_data( req, NULL, 0 ); } } else { int pos = req->reply_body - req->sbuf; req->sbuf = g_realloc( req->sbuf, req->sblen + len + 1 ); memcpy( req->sbuf + req->sblen, buffer, len ); req->bytes_read += len; req->sblen += len; req->sbuf[req->sblen] = '\0'; req->reply_body = req->sbuf + pos; req->body_size = req->sblen - pos; } if( ( req->flags & HTTPC_STREAMING ) && req->reply_body ) req->func( req ); return CR_OK; } /* Splits headers and body. Checks result code, in case of 300s it'll handle redirects. If this returns FALSE, don't call any callbacks! */ static gboolean http_handle_headers( struct http_request *req ) { char *end1, *end2, *s; int evil_server = 0; /* Zero termination is very convenient. */ req->reply_headers[req->bytes_read] = '\0'; /* Find the separation between headers and body, and keep stupid webservers in mind. */ end1 = strstr( req->reply_headers, "\r\n\r\n" ); end2 = strstr( req->reply_headers, "\n\n" ); if( end2 && end2 < end1 ) { end1 = end2 + 1; evil_server = 1; } else if( end1 ) { end1 += 2; } else { req->status_string = g_strdup( "Malformed HTTP reply" ); return TRUE; } *end1 = '\0'; if( getenv( "BITLBEE_DEBUG" ) ) printf( "HTTP response headers:\n%s\n", req->reply_headers ); if( evil_server ) req->reply_body = end1 + 1; else req->reply_body = end1 + 2; /* Separately allocated space for headers and body. */ req->sblen = req->body_size = req->reply_headers + req->bytes_read - req->reply_body; req->sbuf = req->reply_body = g_memdup( req->reply_body, req->body_size + 1 ); req->reply_headers = g_realloc( req->reply_headers, end1 - req->reply_headers + 1 ); if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL ) { if( sscanf( end1 + 1, "%hd", &req->status_code ) != 1 ) { req->status_string = g_strdup( "Can't parse status code" ); req->status_code = -1; } else { char *eol; if( evil_server ) eol = strchr( end1, '\n' ); else eol = strchr( end1, '\r' ); req->status_string = g_strndup( end1 + 1, eol - end1 - 1 ); /* Just to be sure... */ if( ( eol = strchr( req->status_string, '\r' ) ) ) *eol = 0; if( ( eol = strchr( req->status_string, '\n' ) ) ) *eol = 0; } } else { req->status_string = g_strdup( "Can't locate status code" ); req->status_code = -1; } if( ( ( req->status_code >= 301 && req->status_code <= 303 ) || req->status_code == 307 ) && req->redir_ttl-- > 0 ) { char *loc, *new_request, *new_host; int error = 0, new_port, new_proto; /* We might fill it again, so let's not leak any memory. */ g_free( req->status_string ); req->status_string = NULL; loc = strstr( req->reply_headers, "\nLocation: " ); if( loc == NULL ) /* We can't handle this redirect... */ { req->status_string = g_strdup( "Can't locate Location: header" ); return TRUE; } loc += 11; while( *loc == ' ' ) loc ++; /* TODO/FIXME: Possibly have to handle relative redirections, and rewrite Host: headers. Not necessary for now, it's enough for passport authentication like this. */ if( *loc == '/' ) { /* Just a different pathname... */ /* Since we don't cache the servername, and since we don't need this yet anyway, I won't implement it. */ req->status_string = g_strdup( "Can't handle relative redirects" ); return TRUE; } else { /* A whole URL */ url_t *url; char *s, *version, *headers; const char *new_method; s = strstr( loc, "\r\n" ); if( s == NULL ) return TRUE; url = g_new0( url_t, 1 ); *s = 0; if( !url_set( url, loc ) ) { req->status_string = g_strdup( "Malformed redirect URL" ); g_free( url ); return TRUE; } /* Find all headers and, if necessary, the POST request contents. Skip the old Host: header though. This crappy code here means anything using this http_client MUST put the Host: header at the top. */ if( !( ( s = strstr( req->request, "\r\nHost: " ) ) && ( s = strstr( s + strlen( "\r\nHost: " ), "\r\n" ) ) ) ) { req->status_string = g_strdup( "Error while rebuilding request string" ); g_free( url ); return TRUE; } headers = s; /* More or less HTTP/1.0 compliant, from my reading of RFC 2616. Always perform a GET request unless we received a 301. 303 was meant for this but it's HTTP/1.1-only and we're specifically speaking HTTP/1.0. ... Well except someone at identi.ca's didn't bother reading any RFCs and just return HTTP/1.1-specific status codes to HTTP/1.0 requests. Fuckers. So here we are, handle 301..303,307. */ if( strncmp( req->request, "GET", 3 ) == 0 ) /* GETs never become POSTs. */ new_method = "GET"; else if( req->status_code == 302 || req->status_code == 303 ) /* 302 de-facto becomes GET, 303 as specified by RFC 2616#10.3.3 */ new_method = "GET"; else /* 301 de-facto should stay POST, 307 specifally RFC 2616#10.3.8 */ new_method = "POST"; if( ( version = strstr( req->request, " HTTP/" ) ) && ( s = strstr( version, "\r\n" ) ) ) { version ++; version = g_strndup( version, s - version ); } else version = g_strdup( "HTTP/1.0" ); /* Okay, this isn't fun! We have to rebuild the request... :-( */ new_request = g_strdup_printf( "%s %s %s\r\nHost: %s%s", new_method, url->file, version, url->host, headers ); new_host = g_strdup( url->host ); new_port = url->port; new_proto = url->proto; /* If we went from POST to GET, truncate the request content. */ if( new_request[0] != req->request[0] && new_request[0] == 'G' && ( s = strstr( new_request, "\r\n\r\n" ) ) ) s[4] = '\0'; g_free( url ); g_free( version ); } if( req->ssl ) ssl_disconnect( req->ssl ); else closesocket( req->fd ); req->fd = -1; req->ssl = NULL; if( getenv( "BITLBEE_DEBUG" ) ) printf( "New headers for redirected HTTP request:\n%s\n", new_request ); if( new_proto == PROTO_HTTPS ) { req->ssl = ssl_connect( new_host, new_port, TRUE, http_ssl_connected, req ); if( req->ssl == NULL ) error = 1; } else { req->fd = proxy_connect( new_host, new_port, http_connected, req ); if( req->fd < 0 ) error = 1; } g_free( new_host ); if( error ) { req->status_string = g_strdup( "Connection problem during redirect" ); g_free( new_request ); return TRUE; } g_free( req->request ); g_free( req->reply_headers ); g_free( req->sbuf ); req->request = new_request; req->request_length = strlen( new_request ); req->bytes_read = req->bytes_written = req->inpa = 0; req->reply_headers = req->reply_body = NULL; req->sbuf = req->cbuf = NULL; req->sblen = req->cblen = 0; return FALSE; } if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) && sscanf( s, "%d", &req->content_length ) != 1 ) req->content_length = -1; g_free( s ); if( ( s = get_rfc822_header( req->reply_headers, "Transfer-Encoding", 0 ) ) ) { if( strcasestr( s, "chunked" ) ) { req->flags |= HTTPC_CHUNKED; req->cbuf = req->sbuf; req->cblen = req->sblen; req->reply_body = req->sbuf = g_strdup( "" ); req->body_size = req->sblen = 0; } g_free( s ); } return TRUE; } void http_flush_bytes( struct http_request *req, size_t len ) { if( len <= 0 || len > req->body_size || !( req->flags & HTTPC_STREAMING ) ) return; req->reply_body += len; req->body_size -= len; if( req->reply_body - req->sbuf >= 512 ) { char *new = g_memdup( req->reply_body, req->body_size + 1 ); g_free( req->sbuf ); req->reply_body = req->sbuf = new; req->sblen = req->body_size; } } void http_close( struct http_request *req ) { if( !req ) return; if( req->inpa > 0 ) b_event_remove( req->inpa ); if( req->ssl ) ssl_disconnect( req->ssl ); else closesocket( req->fd ); http_free( req ); } static void http_free( struct http_request *req ) { g_free( req->request ); g_free( req->reply_headers ); g_free( req->status_string ); g_free( req->sbuf ); g_free( req->cbuf ); g_free( req ); } bitlbee-3.2.1/lib/events_glib.c0000644000175000017500000000770712245474076015744 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2006 Wilmer van der Gaast and others * \********************************************************************/ /* * Event handling (using GLib) */ /* 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 */ #define BITLBEE_CORE #include #include #include #include #ifndef _WIN32 #include #include #include #include #include #else #include "sock.h" #define ETIMEDOUT WSAETIMEDOUT #define EINPROGRESS WSAEINPROGRESS #endif #include #include #include "proxy.h" typedef struct _GaimIOClosure { b_event_handler function; gpointer data; guint flags; } GaimIOClosure; static GMainLoop *loop = NULL; void b_main_init() { if( loop == NULL ) loop = g_main_new( FALSE ); } void b_main_run() { g_main_run( loop ); } void b_main_quit() { g_main_quit( loop ); } static gboolean gaim_io_invoke(GIOChannel *source, GIOCondition condition, gpointer data) { GaimIOClosure *closure = data; b_input_condition gaim_cond = 0; gboolean st; if (condition & G_IO_NVAL) return FALSE; if (condition & GAIM_READ_COND) gaim_cond |= B_EV_IO_READ; if (condition & GAIM_WRITE_COND) gaim_cond |= B_EV_IO_WRITE; event_debug( "gaim_io_invoke( %d, %d, 0x%x )\n", g_io_channel_unix_get_fd(source), condition, data ); st = closure->function(closure->data, g_io_channel_unix_get_fd(source), gaim_cond); if( !st ) event_debug( "Returned FALSE, cancelling.\n" ); if (closure->flags & B_EV_FLAG_FORCE_ONCE) return FALSE; else if (closure->flags & B_EV_FLAG_FORCE_REPEAT) return TRUE; else return st; } static void gaim_io_destroy(gpointer data) { event_debug( "gaim_io_destroy( 0x%x )\n", data ); g_free(data); } gint b_input_add(gint source, b_input_condition condition, b_event_handler function, gpointer data) { GaimIOClosure *closure = g_new0(GaimIOClosure, 1); GIOChannel *channel; GIOCondition cond = 0; int st; closure->function = function; closure->data = data; closure->flags = condition; if (condition & B_EV_IO_READ) cond |= GAIM_READ_COND; if (condition & B_EV_IO_WRITE) cond |= GAIM_WRITE_COND; channel = g_io_channel_unix_new(source); st = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond, gaim_io_invoke, closure, gaim_io_destroy); event_debug( "b_input_add( %d, %d, 0x%x, 0x%x ) = %d (%p)\n", source, condition, function, data, st, closure ); g_io_channel_unref(channel); return st; } gint b_timeout_add(gint timeout, b_event_handler func, gpointer data) { /* GSourceFunc and the BitlBee event handler function aren't really the same, but they're "compatible". ;-) It will do for now, BitlBee only looks at the "data" argument. */ gint st = g_timeout_add(timeout, (GSourceFunc) func, data); event_debug( "b_timeout_add( %d, %d, %d ) = %d\n", timeout, func, data, st ); return st; } void b_event_remove(gint tag) { event_debug( "b_event_remove( %d )\n", tag ); if (tag > 0) g_source_remove(tag); } void closesocket( int fd ) { close( fd ); } bitlbee-3.2.1/lib/proxy.c0000644000175000017500000003312612245474076014616 0ustar wilmerwilmer/* * gaim * * Copyright (C) 1998-1999, Mark Spencer * Copyright (C) 2002-2004, Wilmer van der Gaast, Jelmer Vernooij * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #define BITLBEE_CORE #include #include #include #include #ifndef _WIN32 #include #include #include #include #include #else #include "sock.h" #define ETIMEDOUT WSAETIMEDOUT #define EINPROGRESS WSAEINPROGRESS #endif #include #include #include "nogaim.h" #include "proxy.h" #include "base64.h" char proxyhost[128] = ""; int proxyport = 0; int proxytype = PROXY_NONE; char proxyuser[128] = ""; char proxypass[128] = ""; /* Some systems don't know this one. It's not essential, so set it to 0 then. */ #ifndef AI_NUMERICSERV #define AI_NUMERICSERV 0 #endif #ifndef AI_ADDRCONFIG #define AI_ADDRCONFIG 0 #endif struct PHB { b_event_handler func, proxy_func; gpointer data, proxy_data; char *host; int port; int fd; gint inpa; struct addrinfo *gai, *gai_cur; }; static int proxy_connect_none(const char *host, unsigned short port_, struct PHB *phb); static gboolean gaim_io_connected(gpointer data, gint source, b_input_condition cond) { struct PHB *phb = data; unsigned int len; int error = ETIMEDOUT; len = sizeof(error); #ifndef _WIN32 if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0 || error) { if ((phb->gai_cur = phb->gai_cur->ai_next)) { int new_fd; b_event_remove(phb->inpa); if ((new_fd = proxy_connect_none(NULL, 0, phb))) { b_event_remove(phb->inpa); closesocket(source); dup2(new_fd, source); closesocket(new_fd); phb->inpa = b_input_add(source, B_EV_IO_WRITE, gaim_io_connected, phb); return FALSE; } } freeaddrinfo(phb->gai); closesocket(source); b_event_remove(phb->inpa); if( phb->proxy_func ) phb->proxy_func(phb->proxy_data, -1, B_EV_IO_READ); else { phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb); } return FALSE; } #endif freeaddrinfo(phb->gai); sock_make_blocking(source); b_event_remove(phb->inpa); if( phb->proxy_func ) phb->proxy_func(phb->proxy_data, source, B_EV_IO_READ); else { phb->func(phb->data, source, B_EV_IO_READ); g_free(phb); } return FALSE; } static int proxy_connect_none(const char *host, unsigned short port_, struct PHB *phb) { struct sockaddr_in me; int fd = -1; if (phb->gai_cur == NULL) { int ret; char port[6]; struct addrinfo hints; g_snprintf(port, sizeof(port), "%d", port_); memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV; if (!(ret = getaddrinfo(host, port, &hints, &phb->gai))) phb->gai_cur = phb->gai; else event_debug("gai(): %s\n", gai_strerror(ret)); } for (; phb->gai_cur; phb->gai_cur = phb->gai_cur->ai_next) { if ((fd = socket(phb->gai_cur->ai_family, phb->gai_cur->ai_socktype, phb->gai_cur->ai_protocol)) < 0) { event_debug( "socket failed: %d\n", errno); continue; } sock_make_nonblocking(fd); if (global.conf->iface_out) { me.sin_family = AF_INET; me.sin_port = 0; me.sin_addr.s_addr = inet_addr( global.conf->iface_out ); if (bind(fd, (struct sockaddr *) &me, sizeof(me)) != 0) event_debug("bind( %d, \"%s\" ) failure\n", fd, global.conf->iface_out); } event_debug("proxy_connect_none( \"%s\", %d ) = %d\n", host, port_, fd); if (connect(fd, phb->gai_cur->ai_addr, phb->gai_cur->ai_addrlen) < 0 && !sockerr_again()) { event_debug( "connect failed: %s\n", strerror(errno)); closesocket(fd); fd = -1; continue; } else { phb->inpa = b_input_add(fd, B_EV_IO_WRITE, gaim_io_connected, phb); phb->fd = fd; break; } } if(fd < 0 && host) g_free(phb); return fd; } /* Connecting to HTTP proxies */ #define HTTP_GOODSTRING "HTTP/1.0 200 Connection established" #define HTTP_GOODSTRING2 "HTTP/1.1 200 Connection established" static gboolean http_canread(gpointer data, gint source, b_input_condition cond) { int nlc = 0; int pos = 0; struct PHB *phb = data; char inputline[8192]; b_event_remove(phb->inpa); while ((pos < sizeof(inputline)-1) && (nlc != 2) && (read(source, &inputline[pos++], 1) == 1)) { if (inputline[pos - 1] == '\n') nlc++; else if (inputline[pos - 1] != '\r') nlc = 0; } inputline[pos] = '\0'; if ((memcmp(HTTP_GOODSTRING, inputline, strlen(HTTP_GOODSTRING)) == 0) || (memcmp(HTTP_GOODSTRING2, inputline, strlen(HTTP_GOODSTRING2)) == 0)) { phb->func(phb->data, source, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } static gboolean http_canwrite(gpointer data, gint source, b_input_condition cond) { char cmd[384]; struct PHB *phb = data; unsigned int len; int error = ETIMEDOUT; if (phb->inpa > 0) b_event_remove(phb->inpa); len = sizeof(error); if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } sock_make_blocking(source); g_snprintf(cmd, sizeof(cmd), "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n", phb->host, phb->port, phb->host, phb->port); if (send(source, cmd, strlen(cmd), 0) < 0) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } if (strlen(proxyuser) > 0) { char *t1, *t2; t1 = g_strdup_printf("%s:%s", proxyuser, proxypass); t2 = tobase64(t1); g_free(t1); g_snprintf(cmd, sizeof(cmd), "Proxy-Authorization: Basic %s\r\n", t2); g_free(t2); if (send(source, cmd, strlen(cmd), 0) < 0) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } } g_snprintf(cmd, sizeof(cmd), "\r\n"); if (send(source, cmd, strlen(cmd), 0) < 0) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } phb->inpa = b_input_add(source, B_EV_IO_READ, http_canread, phb); return FALSE; } static int proxy_connect_http(const char *host, unsigned short port, struct PHB *phb) { phb->host = g_strdup(host); phb->port = port; phb->proxy_func = http_canwrite; phb->proxy_data = phb; return( proxy_connect_none( proxyhost, proxyport, phb ) ); } /* Connecting to SOCKS4 proxies */ static gboolean s4_canread(gpointer data, gint source, b_input_condition cond) { unsigned char packet[12]; struct PHB *phb = data; b_event_remove(phb->inpa); memset(packet, 0, sizeof(packet)); if (read(source, packet, 9) >= 4 && packet[1] == 90) { phb->func(phb->data, source, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } static gboolean s4_canwrite(gpointer data, gint source, b_input_condition cond) { unsigned char packet[12]; struct hostent *hp; struct PHB *phb = data; unsigned int len; int error = ETIMEDOUT; if (phb->inpa > 0) b_event_remove(phb->inpa); len = sizeof(error); if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } sock_make_blocking(source); /* XXX does socks4 not support host name lookups by the proxy? */ if (!(hp = gethostbyname(phb->host))) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } packet[0] = 4; packet[1] = 1; packet[2] = phb->port >> 8; packet[3] = phb->port & 0xff; packet[4] = (unsigned char)(hp->h_addr_list[0])[0]; packet[5] = (unsigned char)(hp->h_addr_list[0])[1]; packet[6] = (unsigned char)(hp->h_addr_list[0])[2]; packet[7] = (unsigned char)(hp->h_addr_list[0])[3]; packet[8] = 0; if (write(source, packet, 9) != 9) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } phb->inpa = b_input_add(source, B_EV_IO_READ, s4_canread, phb); return FALSE; } static int proxy_connect_socks4(const char *host, unsigned short port, struct PHB *phb) { phb->host = g_strdup(host); phb->port = port; phb->proxy_func = s4_canwrite; phb->proxy_data = phb; return( proxy_connect_none( proxyhost, proxyport, phb ) ); } /* Connecting to SOCKS5 proxies */ static gboolean s5_canread_again(gpointer data, gint source, b_input_condition cond) { unsigned char buf[512]; struct PHB *phb = data; b_event_remove(phb->inpa); if (read(source, buf, 10) < 10) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } if ((buf[0] != 0x05) || (buf[1] != 0x00)) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } phb->func(phb->data, source, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } static void s5_sendconnect(gpointer data, gint source) { unsigned char buf[512]; struct PHB *phb = data; int hlen = strlen(phb->host); buf[0] = 0x05; buf[1] = 0x01; /* CONNECT */ buf[2] = 0x00; /* reserved */ buf[3] = 0x03; /* address type -- host name */ buf[4] = hlen; memcpy(buf + 5, phb->host, hlen); buf[5 + strlen(phb->host)] = phb->port >> 8; buf[5 + strlen(phb->host) + 1] = phb->port & 0xff; if (write(source, buf, (5 + strlen(phb->host) + 2)) < (5 + strlen(phb->host) + 2)) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return; } phb->inpa = b_input_add(source, B_EV_IO_READ, s5_canread_again, phb); } static gboolean s5_readauth(gpointer data, gint source, b_input_condition cond) { unsigned char buf[512]; struct PHB *phb = data; b_event_remove(phb->inpa); if (read(source, buf, 2) < 2) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } if ((buf[0] != 0x01) || (buf[1] != 0x00)) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } s5_sendconnect(phb, source); return FALSE; } static gboolean s5_canread(gpointer data, gint source, b_input_condition cond) { unsigned char buf[512]; struct PHB *phb = data; b_event_remove(phb->inpa); if (read(source, buf, 2) < 2) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } if ((buf[0] != 0x05) || (buf[1] == 0xff)) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } if (buf[1] == 0x02) { unsigned int i = strlen(proxyuser), j = strlen(proxypass); buf[0] = 0x01; /* version 1 */ buf[1] = i; memcpy(buf + 2, proxyuser, i); buf[2 + i] = j; memcpy(buf + 2 + i + 1, proxypass, j); if (write(source, buf, 3 + i + j) < 3 + i + j) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } phb->inpa = b_input_add(source, B_EV_IO_READ, s5_readauth, phb); } else { s5_sendconnect(phb, source); } return FALSE; } static gboolean s5_canwrite(gpointer data, gint source, b_input_condition cond) { unsigned char buf[512]; int i; struct PHB *phb = data; unsigned int len; int error = ETIMEDOUT; if (phb->inpa > 0) b_event_remove(phb->inpa); len = sizeof(error); if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } sock_make_blocking(source); i = 0; buf[0] = 0x05; /* SOCKS version 5 */ if (proxyuser[0]) { buf[1] = 0x02; /* two methods */ buf[2] = 0x00; /* no authentication */ buf[3] = 0x02; /* username/password authentication */ i = 4; } else { buf[1] = 0x01; buf[2] = 0x00; i = 3; } if (write(source, buf, i) < i) { close(source); phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } phb->inpa = b_input_add(source, B_EV_IO_READ, s5_canread, phb); return FALSE; } static int proxy_connect_socks5(const char *host, unsigned short port, struct PHB *phb) { phb->host = g_strdup(host); phb->port = port; phb->proxy_func = s5_canwrite; phb->proxy_data = phb; return( proxy_connect_none( proxyhost, proxyport, phb ) ); } /* Export functions */ int proxy_connect(const char *host, int port, b_event_handler func, gpointer data) { struct PHB *phb; if (!host || port <= 0 || !func || strlen(host) > 128) { return -1; } phb = g_new0(struct PHB, 1); phb->func = func; phb->data = data; if (proxytype == PROXY_NONE || !proxyhost[0] || proxyport <= 0) return proxy_connect_none(host, port, phb); else if (proxytype == PROXY_HTTP) return proxy_connect_http(host, port, phb); else if (proxytype == PROXY_SOCKS4) return proxy_connect_socks4(host, port, phb); else if (proxytype == PROXY_SOCKS5) return proxy_connect_socks5(host, port, phb); g_free(phb); return -1; } bitlbee-3.2.1/lib/events_libevent.c0000644000175000017500000001745512245474076016640 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2006 Wilmer van der Gaast and others * \********************************************************************/ /* * Event handling (using libevent) */ /* 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 */ #define BITLBEE_CORE #include #include #include #include #include #include #include #include "proxy.h" static void b_main_restart(); static guint id_next = 1; /* Next ID to be allocated to an event handler. */ static guint id_cur = 0; /* Event ID that we're currently handling. */ static guint id_dead; /* Set to 1 if b_event_remove removes id_cur. */ static GHashTable *id_hash; static int quitting = 0; /* Prepare to quit, stop handling events. */ /* Since libevent doesn't handle two event handlers for one fd-condition very well (which happens sometimes when BitlBee changes event handlers for a combination), let's buid some indexes so we can delete them here already, just in time. */ static GHashTable *read_hash; static GHashTable *write_hash; struct event_base *leh; struct event_base *old_leh; struct b_event_data { guint id; struct event evinfo; gint timeout; b_event_handler function; void *data; guint flags; }; void b_main_init() { if( leh != NULL ) { /* Clean up the hash tables? */ b_main_restart(); old_leh = leh; } leh = event_init(); id_hash = g_hash_table_new( g_int_hash, g_int_equal ); read_hash = g_hash_table_new( g_int_hash, g_int_equal ); write_hash = g_hash_table_new( g_int_hash, g_int_equal ); } void b_main_run() { /* This while loop is necessary to exit the event loop and start a different one (necessary for ForkDaemon mode). */ while( event_base_dispatch( leh ) == 0 && !quitting ) { if( old_leh != NULL ) { /* For some reason this just isn't allowed... Possibly a bug in older versions, will see later. event_base_free( old_leh ); */ old_leh = NULL; } event_debug( "New event loop.\n" ); } } static void b_main_restart() { struct timeval tv; memset( &tv, 0, sizeof( struct timeval ) ); event_base_loopexit( leh, &tv ); event_debug( "b_main_restart()\n" ); } void b_main_quit() { /* Tell b_main_run() that it shouldn't restart the loop. Also, libevent sometimes generates events before really quitting, we want to stop them. */ quitting = 1; b_main_restart(); } static void b_event_passthrough( int fd, short event, void *data ) { struct b_event_data *b_ev = data; b_input_condition cond = 0; gboolean st; if( fd >= 0 ) { if( event & EV_READ ) cond |= B_EV_IO_READ; if( event & EV_WRITE ) cond |= B_EV_IO_WRITE; } event_debug( "b_event_passthrough( %d, %d, 0x%x ) (%d)\n", fd, event, (int) data, b_ev->id ); /* Since the called function might cancel this handler already (which free()s b_ev), we have to remember the ID here. */ id_cur = b_ev->id; id_dead = 0; if( quitting ) { b_event_remove( id_cur ); return; } st = b_ev->function( b_ev->data, fd, cond ); if( id_dead ) { /* This event was killed already, don't touch it! */ return; } else if( !st && !( b_ev->flags & B_EV_FLAG_FORCE_REPEAT ) ) { event_debug( "Handler returned FALSE: " ); b_event_remove( id_cur ); } else if( fd == -1 ) { /* fd == -1 means it was a timer. These can't be auto-repeated so it has to be recreated every time. */ struct timeval tv; tv.tv_sec = b_ev->timeout / 1000; tv.tv_usec = ( b_ev->timeout % 1000 ) * 1000; evtimer_add( &b_ev->evinfo, &tv ); } } gint b_input_add( gint fd, b_input_condition condition, b_event_handler function, gpointer data ) { struct b_event_data *b_ev; event_debug( "b_input_add( %d, %d, 0x%x, 0x%x ) ", fd, condition, function, data ); if( ( condition & B_EV_IO_READ && ( b_ev = g_hash_table_lookup( read_hash, &fd ) ) ) || ( condition & B_EV_IO_WRITE && ( b_ev = g_hash_table_lookup( write_hash, &fd ) ) ) ) { /* We'll stick with this libevent entry, but give it a new BitlBee id. */ g_hash_table_remove( id_hash, &b_ev->id ); event_debug( "(replacing old handler (id = %d)) = %d\n", b_ev->id, id_next ); b_ev->id = id_next++; b_ev->function = function; b_ev->data = data; } else { GIOCondition out_cond; event_debug( "(new) = %d\n", id_next ); b_ev = g_new0( struct b_event_data, 1 ); b_ev->id = id_next++; b_ev->function = function; b_ev->data = data; out_cond = EV_PERSIST; if( condition & B_EV_IO_READ ) out_cond |= EV_READ; if( condition & B_EV_IO_WRITE ) out_cond |= EV_WRITE; event_set( &b_ev->evinfo, fd, out_cond, b_event_passthrough, b_ev ); event_add( &b_ev->evinfo, NULL ); if( out_cond & EV_READ ) g_hash_table_insert( read_hash, &b_ev->evinfo.ev_fd, b_ev ); if( out_cond & EV_WRITE ) g_hash_table_insert( write_hash, &b_ev->evinfo.ev_fd, b_ev ); } b_ev->flags = condition; g_hash_table_insert( id_hash, &b_ev->id, b_ev ); return b_ev->id; } /* TODO: Persistence for timers! */ gint b_timeout_add( gint timeout, b_event_handler function, gpointer data ) { struct b_event_data *b_ev = g_new0( struct b_event_data, 1 ); struct timeval tv; b_ev->id = id_next++; b_ev->timeout = timeout; b_ev->function = function; b_ev->data = data; tv.tv_sec = timeout / 1000; tv.tv_usec = ( timeout % 1000 ) * 1000; evtimer_set( &b_ev->evinfo, b_event_passthrough, b_ev ); evtimer_add( &b_ev->evinfo, &tv ); event_debug( "b_timeout_add( %d, 0x%x, 0x%x ) = %d\n", timeout, function, data, b_ev->id ); g_hash_table_insert( id_hash, &b_ev->id, b_ev ); return b_ev->id; } void b_event_remove( gint id ) { struct b_event_data *b_ev = g_hash_table_lookup( id_hash, &id ); event_debug( "b_event_remove( %d )\n", id ); if( b_ev ) { if( id == id_cur ) id_dead = TRUE; g_hash_table_remove( id_hash, &b_ev->id ); if( b_ev->evinfo.ev_fd >= 0 ) { if( b_ev->evinfo.ev_events & EV_READ ) g_hash_table_remove( read_hash, &b_ev->evinfo.ev_fd ); if( b_ev->evinfo.ev_events & EV_WRITE ) g_hash_table_remove( write_hash, &b_ev->evinfo.ev_fd ); } event_del( &b_ev->evinfo ); g_free( b_ev ); } else { event_debug( "Already removed?\n" ); } } void closesocket( int fd ) { struct b_event_data *b_ev; /* Since epoll() (the main reason we use libevent) automatically removes sockets from the epoll() list when a socket gets closed and some modules have a habit of closing sockets before removing event handlers, our and libevent's administration get a little bit messed up. So this little function will remove the handlers properly before closing a socket. */ if( ( b_ev = g_hash_table_lookup( read_hash, &fd ) ) ) { event_debug( "Warning: fd %d still had a read event handler when shutting down.\n", fd ); b_event_remove( b_ev->id ); } if( ( b_ev = g_hash_table_lookup( write_hash, &fd ) ) ) { event_debug( "Warning: fd %d still had a write event handler when shutting down.\n", fd ); b_event_remove( b_ev->id ); } close( fd ); } bitlbee-3.2.1/lib/arc.c0000644000175000017500000001713612245474076014205 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple (but secure) ArcFour implementation for safer password storage. * * * * Copyright 2006 Wilmer van der Gaast * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation, version * * 2.1. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the Free Software Foundation, * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * * \***************************************************************************/ /* This file implements ArcFour-encryption, which will mainly be used to save IM passwords safely in the new XML-format. Possibly other uses will come up later. It's supposed to be quite reliable (thanks to the use of a 6-byte IV/seed), certainly compared to the old format. The only realistic way to crack BitlBee passwords now is to use a sniffer to get your hands on the user's password. If you see that something's wrong in this implementation (I asked a couple of people to look at it already, but who knows), please tell me. The reason I picked ArcFour is because it's pretty simple but effective, so it will work without adding several KBs or an extra library dependency. (ArcFour is an RC4-compatible cipher. See for details: http://www.mozilla.org/projects/security/pki/nss/draft-kaukonen-cipher-arcfour-03.txt) */ #include #include #include #include #include "misc.h" #include "arc.h" /* Add some seed to the password, to make sure we *never* use the same key. This defines how many bytes we use as a seed. */ #define ARC_IV_LEN 6 /* To defend against a "Fluhrer, Mantin and Shamir attack", it is recommended to shuffle S[] just a bit more before you start to use it. This defines how many bytes we'll request before we'll really use them for encryption. */ #define ARC_CYCLES 1024 struct arc_state *arc_keymaker( unsigned char *key, int kl, int cycles ) { struct arc_state *st; int i, j, tmp; unsigned char S2[256]; st = g_malloc( sizeof( struct arc_state ) ); st->i = st->j = 0; if( kl <= 0 ) kl = strlen( (char*) key ); for( i = 0; i < 256; i ++ ) { st->S[i] = i; S2[i] = key[i%kl]; } for( i = j = 0; i < 256; i ++ ) { j = ( j + st->S[i] + S2[i] ) & 0xff; tmp = st->S[i]; st->S[i] = st->S[j]; st->S[j] = tmp; } memset( S2, 0, 256 ); i = j = 0; for( i = 0; i < cycles; i ++ ) arc_getbyte( st ); return st; } /* For those who don't know, ArcFour is basically an algorithm that generates a stream of bytes after you give it a key. Just get a byte from it and xor it with your cleartext. To decrypt, just give it the same key again and start xorring. The function above initializes the byte generator, the next function can be used to get bytes from the generator (and shuffle things a bit). */ unsigned char arc_getbyte( struct arc_state *st ) { unsigned char tmp; /* Unfortunately the st-> stuff doesn't really improve readability here... */ st->i ++; st->j += st->S[st->i]; tmp = st->S[st->i]; st->S[st->i] = st->S[st->j]; st->S[st->j] = tmp; tmp = (st->S[st->i] + st->S[st->j]) & 0xff; return st->S[tmp]; } /* The following two functions can be used for reliable encryption and decryption. Known plaintext attacks are prevented by adding some (6, by default) random bytes to the password before setting up the state structures. These 6 bytes are also saved in the results, because of course we'll need them in arc_decode(). Because the length of the resulting string is unknown to the caller, it should pass a char**. Since the encode/decode functions allocate memory for the string, make sure the char** points at a NULL-pointer (or at least to something you already free()d), or you'll leak memory. And of course, don't forget to free() the result when you don't need it anymore. Both functions return the number of bytes in the result string. Note that if you use the pad_to argument, you will need zero-termi- nation to find back the original string length after decryption. So it shouldn't be used if your string contains \0s by itself! */ int arc_encode( char *clear, int clear_len, unsigned char **crypt, char *password, int pad_to ) { struct arc_state *st; unsigned char *key; char *padded = NULL; int key_len, i, padded_len; key_len = strlen( password ) + ARC_IV_LEN; if( clear_len <= 0 ) clear_len = strlen( clear ); /* Pad the string to the closest multiple of pad_to. This makes it impossible to see the exact length of the password. */ if( pad_to > 0 && ( clear_len % pad_to ) > 0 ) { padded_len = clear_len + pad_to - ( clear_len % pad_to ); padded = g_malloc( padded_len ); memcpy( padded, clear, clear_len ); /* First a \0 and then random data, so we don't have to do anything special when decrypting. */ padded[clear_len] = 0; random_bytes( (unsigned char*) padded + clear_len + 1, padded_len - clear_len - 1 ); clear = padded; clear_len = padded_len; } /* Prepare buffers and the key + IV */ *crypt = g_malloc( clear_len + ARC_IV_LEN ); key = g_malloc( key_len ); strcpy( (char*) key, password ); /* Add the salt. Save it for later (when decrypting) and, of course, add it to the encryption key. */ random_bytes( crypt[0], ARC_IV_LEN ); memcpy( key + key_len - ARC_IV_LEN, crypt[0], ARC_IV_LEN ); /* Generate the initial S[] from the IVed key. */ st = arc_keymaker( key, key_len, ARC_CYCLES ); g_free( key ); for( i = 0; i < clear_len; i ++ ) crypt[0][i+ARC_IV_LEN] = clear[i] ^ arc_getbyte( st ); g_free( st ); g_free( padded ); return clear_len + ARC_IV_LEN; } int arc_decode( unsigned char *crypt, int crypt_len, char **clear, const char *password ) { struct arc_state *st; unsigned char *key; int key_len, clear_len, i; key_len = strlen( password ) + ARC_IV_LEN; clear_len = crypt_len - ARC_IV_LEN; if( clear_len < 0 ) { *clear = g_strdup( "" ); return -1; } /* Prepare buffers and the key + IV */ *clear = g_malloc( clear_len + 1 ); key = g_malloc( key_len ); strcpy( (char*) key, password ); for( i = 0; i < ARC_IV_LEN; i ++ ) key[key_len-ARC_IV_LEN+i] = crypt[i]; /* Generate the initial S[] from the IVed key. */ st = arc_keymaker( key, key_len, ARC_CYCLES ); g_free( key ); for( i = 0; i < clear_len; i ++ ) clear[0][i] = crypt[i+ARC_IV_LEN] ^ arc_getbyte( st ); clear[0][i] = 0; /* Nice to have for plaintexts. */ g_free( st ); return clear_len; } bitlbee-3.2.1/lib/misc.c0000644000175000017500000004235712245474076014376 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* * Various utility functions. Some are copied from Gaim to support the * IM-modules, most are from BitlBee. * * Copyright (C) 1998-1999, Mark Spencer * (and possibly other members of the Gaim team) * Copyright 2002-2012 Wilmer van der Gaast */ /* 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 */ #define BITLBEE_CORE #include "nogaim.h" #include "base64.h" #include "md5.h" #include #include #include #include #include #include #ifdef HAVE_RESOLV_A #include #include #endif #include "md5.h" #include "ssl_client.h" void strip_linefeed(gchar *text) { int i, j; gchar *text2 = g_malloc(strlen(text) + 1); for (i = 0, j = 0; text[i]; i++) if (text[i] != '\r') text2[j++] = text[i]; text2[j] = '\0'; strcpy(text, text2); g_free(text2); } time_t get_time(int year, int month, int day, int hour, int min, int sec) { struct tm tm; memset(&tm, 0, sizeof(struct tm)); tm.tm_year = year - 1900; tm.tm_mon = month - 1; tm.tm_mday = day; tm.tm_hour = hour; tm.tm_min = min; tm.tm_sec = sec >= 0 ? sec : time(NULL) % 60; return mktime(&tm); } time_t mktime_utc( struct tm *tp ) { struct tm utc; time_t res, tres; tp->tm_isdst = -1; res = mktime( tp ); /* Problem is, mktime() just gave us the GMT timestamp for the given local time... While the given time WAS NOT local. So we should fix this now. Now I could choose between messing with environment variables (kludgy) or using timegm() (not portable)... Or doing the following, which I actually prefer... tzset() may also work but in other places I actually want to use local time. FFFFFFFFFFFFFFFFFFFFFUUUUUUUUUUUUUUUUUUUU!! */ gmtime_r( &res, &utc ); utc.tm_isdst = -1; if( utc.tm_hour == tp->tm_hour && utc.tm_min == tp->tm_min ) /* Sweet! We're in UTC right now... */ return res; tres = mktime( &utc ); res += res - tres; /* Yes, this is a hack. And it will go wrong around DST changes. BUT this is more likely to be threadsafe than messing with environment variables, and possibly more portable... */ return res; } typedef struct htmlentity { char code[7]; char is[3]; } htmlentity_t; static const htmlentity_t ent[] = { { "lt", "<" }, { "gt", ">" }, { "amp", "&" }, { "apos", "'" }, { "quot", "\"" }, { "aacute", "á" }, { "eacute", "é" }, { "iacute", "é" }, { "oacute", "ó" }, { "uacute", "ú" }, { "agrave", "à" }, { "egrave", "è" }, { "igrave", "ì" }, { "ograve", "ò" }, { "ugrave", "ù" }, { "acirc", "â" }, { "ecirc", "ê" }, { "icirc", "î" }, { "ocirc", "ô" }, { "ucirc", "û" }, { "auml", "ä" }, { "euml", "ë" }, { "iuml", "ï" }, { "ouml", "ö" }, { "uuml", "ü" }, { "nbsp", " " }, { "", "" } }; void strip_html( char *in ) { char *start = in; char out[strlen(in)+1]; char *s = out, *cs; int i, matched; int taglen; memset( out, 0, sizeof( out ) ); while( *in ) { if( *in == '<' && ( isalpha( *(in+1) ) || *(in+1) == '/' ) ) { /* If in points at a < and in+1 points at a letter or a slash, this is probably a HTML-tag. Try to find a closing > and continue there. If the > can't be found, assume that it wasn't a HTML-tag after all. */ cs = in; while( *in && *in != '>' ) in ++; taglen = in - cs - 1; /* not <0 because the above loop runs at least once */ if( *in ) { if( g_strncasecmp( cs+1, "b", taglen) == 0 ) *(s++) = '\x02'; else if( g_strncasecmp( cs+1, "/b", taglen) == 0 ) *(s++) = '\x02'; else if( g_strncasecmp( cs+1, "i", taglen) == 0 ) *(s++) = '\x1f'; else if( g_strncasecmp( cs+1, "/i", taglen) == 0 ) *(s++) = '\x1f'; else if( g_strncasecmp( cs+1, "br", taglen) == 0 ) *(s++) = '\n'; in ++; } else { in = cs; *(s++) = *(in++); } } else if( *in == '&' ) { cs = ++in; while( *in && isalpha( *in ) ) in ++; if( *in == ';' ) in ++; matched = 0; for( i = 0; *ent[i].code; i ++ ) if( g_strncasecmp( ent[i].code, cs, strlen( ent[i].code ) ) == 0 ) { int j; for( j = 0; ent[i].is[j]; j ++ ) *(s++) = ent[i].is[j]; matched = 1; break; } /* None of the entities were matched, so return the string */ if( !matched ) { in = cs - 1; *(s++) = *(in++); } } else { *(s++) = *(in++); } } strcpy( start, out ); } char *escape_html( const char *html ) { const char *c = html; GString *ret; char *str; if( html == NULL ) return( NULL ); ret = g_string_new( "" ); while( *c ) { switch( *c ) { case '&': ret = g_string_append( ret, "&" ); break; case '<': ret = g_string_append( ret, "<" ); break; case '>': ret = g_string_append( ret, ">" ); break; case '"': ret = g_string_append( ret, """ ); break; default: ret = g_string_append_c( ret, *c ); } c ++; } str = ret->str; g_string_free( ret, FALSE ); return( str ); } /* Decode%20a%20file%20name */ void http_decode( char *s ) { char *t; int i, j, k; t = g_new( char, strlen( s ) + 1 ); for( i = j = 0; s[i]; i ++, j ++ ) { if( s[i] == '%' ) { if( sscanf( s + i + 1, "%2x", &k ) ) { t[j] = k; i += 2; } else { *t = 0; break; } } else { t[j] = s[i]; } } t[j] = 0; strcpy( s, t ); g_free( t ); } /* Warning: This one explodes the string. Worst-cases can make the string 3x its original size! */ /* This fuction is safe, but make sure you call it safely as well! */ void http_encode( char *s ) { char t[strlen(s)+1]; int i, j; strcpy( t, s ); for( i = j = 0; t[i]; i ++, j ++ ) { /* Warning: isalnum() is locale-aware, so don't use it here! */ if( ( t[i] >= 'A' && t[i] <= 'Z' ) || ( t[i] >= 'a' && t[i] <= 'z' ) || ( t[i] >= '0' && t[i] <= '9' ) || strchr( "._-~", t[i] ) ) { s[j] = t[i]; } else { sprintf( s + j, "%%%02X", ((unsigned char*)t)[i] ); j += 2; } } s[j] = 0; } /* Strip newlines from a string. Modifies the string passed to it. */ char *strip_newlines( char *source ) { int i; for( i = 0; source[i] != '\0'; i ++ ) if( source[i] == '\n' || source[i] == '\r' ) source[i] = ' '; return source; } /* Wrap an IPv4 address into IPv6 space. Not thread-safe... */ char *ipv6_wrap( char *src ) { static char dst[64]; int i; for( i = 0; src[i]; i ++ ) if( ( src[i] < '0' || src[i] > '9' ) && src[i] != '.' ) break; /* Hmm, it's not even an IP... */ if( src[i] ) return src; g_snprintf( dst, sizeof( dst ), "::ffff:%s", src ); return dst; } /* Unwrap an IPv4 address into IPv6 space. Thread-safe, because it's very simple. :-) */ char *ipv6_unwrap( char *src ) { int i; if( g_strncasecmp( src, "::ffff:", 7 ) != 0 ) return src; for( i = 7; src[i]; i ++ ) if( ( src[i] < '0' || src[i] > '9' ) && src[i] != '.' ) break; /* Hmm, it's not even an IP... */ if( src[i] ) return src; return ( src + 7 ); } /* Convert from one charset to another. from_cs, to_cs: Source and destination charsets src, dst: Source and destination strings size: Size if src. 0 == use strlen(). strlen() is not reliable for UNICODE/UTF16 strings though. maxbuf: Maximum number of bytes to write to dst Returns the number of bytes written to maxbuf or -1 on an error. */ signed int do_iconv( char *from_cs, char *to_cs, char *src, char *dst, size_t size, size_t maxbuf ) { GIConv cd; size_t res; size_t inbytesleft, outbytesleft; char *inbuf = src; char *outbuf = dst; cd = g_iconv_open( to_cs, from_cs ); if( cd == (GIConv) -1 ) return -1; inbytesleft = size ? size : strlen( src ); outbytesleft = maxbuf - 1; res = g_iconv( cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft ); *outbuf = '\0'; g_iconv_close( cd ); if( res != 0 ) return -1; else return outbuf - dst; } /* A pretty reliable random number generator. Tries to use the /dev/random devices first, and falls back to the random number generator from libc when it fails. Opens randomizer devices with O_NONBLOCK to make sure a lack of entropy won't halt BitlBee. */ void random_bytes( unsigned char *buf, int count ) { #ifndef _WIN32 static int use_dev = -1; /* Actually this probing code isn't really necessary, is it? */ if( use_dev == -1 ) { if( access( "/dev/random", R_OK ) == 0 || access( "/dev/urandom", R_OK ) == 0 ) use_dev = 1; else { use_dev = 0; srand( ( getpid() << 16 ) ^ time( NULL ) ); } } if( use_dev ) { int fd; /* At least on Linux, /dev/random can block if there's not enough entropy. We really don't want that, so if it can't give anything, use /dev/urandom instead. */ if( ( fd = open( "/dev/random", O_RDONLY | O_NONBLOCK ) ) >= 0 ) if( read( fd, buf, count ) == count ) { close( fd ); return; } close( fd ); /* urandom isn't supposed to block at all, but just to be sure. If it blocks, we'll disable use_dev and use the libc randomizer instead. */ if( ( fd = open( "/dev/urandom", O_RDONLY | O_NONBLOCK ) ) >= 0 ) if( read( fd, buf, count ) == count ) { close( fd ); return; } close( fd ); /* If /dev/random blocks once, we'll still try to use it again next time. If /dev/urandom also fails for some reason, stick with libc during this session. */ use_dev = 0; srand( ( getpid() << 16 ) ^ time( NULL ) ); } if( !use_dev ) #endif { int i; /* Possibly the LSB of rand() isn't very random on some platforms. Seems okay on at least Linux and OSX though. */ for( i = 0; i < count; i ++ ) buf[i] = rand() & 0xff; } } int is_bool( char *value ) { if( *value == 0 ) return 0; if( ( g_strcasecmp( value, "true" ) == 0 ) || ( g_strcasecmp( value, "yes" ) == 0 ) || ( g_strcasecmp( value, "on" ) == 0 ) ) return 1; if( ( g_strcasecmp( value, "false" ) == 0 ) || ( g_strcasecmp( value, "no" ) == 0 ) || ( g_strcasecmp( value, "off" ) == 0 ) ) return 1; while( *value ) if( !isdigit( *value ) ) return 0; else value ++; return 1; } int bool2int( char *value ) { int i; if( ( g_strcasecmp( value, "true" ) == 0 ) || ( g_strcasecmp( value, "yes" ) == 0 ) || ( g_strcasecmp( value, "on" ) == 0 ) ) return 1; if( ( g_strcasecmp( value, "false" ) == 0 ) || ( g_strcasecmp( value, "no" ) == 0 ) || ( g_strcasecmp( value, "off" ) == 0 ) ) return 0; if( sscanf( value, "%d", &i ) == 1 ) return i; return 0; } struct ns_srv_reply **srv_lookup( char *service, char *protocol, char *domain ) { struct ns_srv_reply **replies = NULL; #ifdef HAVE_RESOLV_A struct ns_srv_reply *reply = NULL; char name[1024]; unsigned char querybuf[1024]; const unsigned char *buf; ns_msg nsh; ns_rr rr; int i, n, len, size; g_snprintf( name, sizeof( name ), "_%s._%s.%s", service, protocol, domain ); if( ( size = res_query( name, ns_c_in, ns_t_srv, querybuf, sizeof( querybuf ) ) ) <= 0 ) return NULL; if( ns_initparse( querybuf, size, &nsh ) != 0 ) return NULL; n = 0; while( ns_parserr( &nsh, ns_s_an, n, &rr ) == 0 ) { size = ns_rr_rdlen( rr ); buf = ns_rr_rdata( rr ); len = 0; for( i = 6; i < size && buf[i]; i += buf[i] + 1 ) len += buf[i] + 1; if( i > size ) break; reply = g_malloc( sizeof( struct ns_srv_reply ) + len ); memcpy( reply->name, buf + 7, len ); for( i = buf[6]; i < len && buf[7+i]; i += buf[7+i] + 1 ) reply->name[i] = '.'; if( i > len ) { g_free( reply ); break; } reply->prio = ( buf[0] << 8 ) | buf[1]; reply->weight = ( buf[2] << 8 ) | buf[3]; reply->port = ( buf[4] << 8 ) | buf[5]; n ++; replies = g_renew( struct ns_srv_reply *, replies, n + 1 ); replies[n-1] = reply; } if( replies ) replies[n] = NULL; #endif return replies; } void srv_free( struct ns_srv_reply **srv ) { int i; if( srv == NULL ) return; for( i = 0; srv[i]; i ++ ) g_free( srv[i] ); g_free( srv ); } /* Word wrapping. Yes, I know this isn't UTF-8 clean. I'm willing to take the risk. */ char *word_wrap( const char *msg, int line_len ) { GString *ret = g_string_sized_new( strlen( msg ) + 16 ); while( strlen( msg ) > line_len ) { int i; /* First try to find out if there's a newline already. Don't want to add more splits than necessary. */ for( i = line_len; i > 0 && msg[i] != '\n'; i -- ); if( msg[i] == '\n' ) { g_string_append_len( ret, msg, i + 1 ); msg += i + 1; continue; } for( i = line_len; i > 0; i -- ) { if( msg[i] == '-' ) { g_string_append_len( ret, msg, i + 1 ); g_string_append_c( ret, '\n' ); msg += i + 1; break; } else if( msg[i] == ' ' ) { g_string_append_len( ret, msg, i ); g_string_append_c( ret, '\n' ); msg += i + 1; break; } } if( i == 0 ) { g_string_append_len( ret, msg, line_len ); g_string_append_c( ret, '\n' ); msg += line_len; } } g_string_append( ret, msg ); return g_string_free( ret, FALSE ); } gboolean ssl_sockerr_again( void *ssl ) { if( ssl ) return ssl_errno == SSL_AGAIN; else return sockerr_again(); } /* Returns values: -1 == Failure (base64-decoded to something unexpected) 0 == Okay 1 == Password doesn't match the hash. */ int md5_verify_password( char *password, char *hash ) { md5_byte_t *pass_dec = NULL; md5_byte_t pass_md5[16]; md5_state_t md5_state; int ret = -1, i; if( base64_decode( hash, &pass_dec ) == 21 ) { md5_init( &md5_state ); md5_append( &md5_state, (md5_byte_t*) password, strlen( password ) ); md5_append( &md5_state, (md5_byte_t*) pass_dec + 16, 5 ); /* Hmmm, salt! */ md5_finish( &md5_state, pass_md5 ); for( i = 0; i < 16; i ++ ) { if( pass_dec[i] != pass_md5[i] ) { ret = 1; break; } } /* If we reached the end of the loop, it was a match! */ if( i == 16 ) ret = 0; } g_free( pass_dec ); return ret; } /* Split commands (root-style, *not* IRC-style). Handles "quoting of" white\ space in 'various ways'. Returns a NULL-terminated static char** so watch out with nested use! Definitely not thread-safe. */ char **split_command_parts( char *command ) { static char *cmd[IRC_MAX_ARGS+1]; char *s, q = 0; int k; memset( cmd, 0, sizeof( cmd ) ); cmd[0] = command; k = 1; for( s = command; *s && k < IRC_MAX_ARGS; s ++ ) if( *s == ' ' && !q ) { *s = 0; while( *++s == ' ' ); if( *s == '"' || *s == '\'' ) { q = *s; s ++; } if( *s ) { cmd[k++] = s; s --; } else { break; } } else if( *s == '\\' && ( ( !q && s[1] ) || ( q && q == s[1] ) ) ) { char *cpy; for( cpy = s; *cpy; cpy ++ ) cpy[0] = cpy[1]; } else if( *s == q ) { q = *s = 0; } /* Full zero-padding for easier argc checking. */ while( k <= IRC_MAX_ARGS ) cmd[k++] = NULL; return cmd; } char *get_rfc822_header( const char *text, const char *header, int len ) { int hlen = strlen( header ), i; const char *ret; if( text == NULL ) return NULL; 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( ( i >= 4 && strncmp( text + i - 4, "\r\n\r\n", 4 ) == 0 ) || ( i >= 2 && ( strncmp( text + i - 2, "\n\n", 2 ) == 0 || strncmp( text + i - 2, "\r\r", 2 ) == 0 ) ) ) { break; } } return NULL; } bitlbee-3.2.1/lib/md5.c0000644000175000017500000002117412245474076014122 0ustar wilmerwilmer/* * MD5 hashing code copied from Lepton's crack * * Adapted to be API-compatible with the previous (GPL-incompatible) code. */ /* * This code implements the MD5 message-digest algorithm. * The algorithm is due to Ron Rivest. This code was * written by Colin Plumb in 1993, no copyright is claimed. * This code is in the public domain; do with it what you wish. * * Equivalent code is available from RSA Data Security, Inc. * This code has been tested against that, and is equivalent, * except that you don't need to include two pages of legalese * with every copy. * * To compute the message digest of a chunk of bytes, declare an * MD5Context structure, pass it to MD5Init, call MD5Update as * needed on buffers full of bytes, and then call MD5Final, which * will fill a supplied 16-byte array with the digest. */ #include #include /* for memcpy() */ #include #include "md5.h" static void md5_transform(uint32_t buf[4], uint32_t const in[16]); /* * Wrapper function for all-in-one MD5 * * Bernardo Reino, aka Lepton. * 20021120 */ /* Turns out MD5 was designed for little-endian machines. If we're running on a big-endian machines, we have to swap some bytes. Since detecting endianness at compile time reliably seems pretty hard, let's do it at run-time. It's not like we're going to checksum megabytes of data... */ static uint32_t cvt32(uint32_t val) { static int little_endian = -1; if (little_endian == -1) { little_endian = 1; little_endian = *((char*) &little_endian); } if (little_endian) return val; else return (val >> 24) | ((val >> 8) & 0xff00) | ((val << 8) & 0xff0000) | (val << 24); } void md5_init(struct MD5Context *ctx) { ctx->buf[0] = 0x67452301; ctx->buf[1] = 0xefcdab89; ctx->buf[2] = 0x98badcfe; ctx->buf[3] = 0x10325476; ctx->bits[0] = 0; ctx->bits[1] = 0; } /* * Update context to reflect the concatenation of another buffer full * of bytes. */ void md5_append(struct MD5Context *ctx, const md5_byte_t *buf, unsigned int len) { uint32_t t; /* Update bitcount */ t = ctx->bits[0]; if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++; /* Carry from low to high */ ctx->bits[1] += len >> 29; t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ /* Handle any leading odd-sized chunks */ if (t) { unsigned char *p = (unsigned char *) ctx->in + t; t = 64 - t; if (len < t) { memcpy(p, buf, len); return; } memcpy(p, buf, t); md5_transform(ctx->buf, (uint32_t *) ctx->in); buf += t; len -= t; } /* Process data in 64-byte chunks */ while (len >= 64) { memcpy(ctx->in, buf, 64); md5_transform(ctx->buf, (uint32_t *) ctx->in); buf += 64; len -= 64; } /* Handle any remaining bytes of data. */ memcpy(ctx->in, buf, len); } /* * Final wrapup - pad to 64-byte boundary with the bit pattern * 1 0* (64-bit count of bits processed, MSB-first) */ void md5_finish(struct MD5Context *ctx, md5_byte_t digest[16]) { unsigned count; unsigned char *p; /* Compute number of bytes mod 64 */ count = (ctx->bits[0] >> 3) & 0x3F; /* Set the first char of padding to 0x80. This is safe since there is always at least one byte free */ p = ctx->in + count; *p++ = 0x80; /* Bytes of padding needed to make 64 bytes */ count = 64 - 1 - count; /* Pad out to 56 mod 64 */ if (count < 8) { /* Two lots of padding: Pad the first block to 64 bytes */ memset(p, 0, count); md5_transform(ctx->buf, (uint32_t *) ctx->in); /* Now fill the next block with 56 bytes */ memset(ctx->in, 0, 56); } else { /* Pad block to 56 bytes */ memset(p, 0, count - 8); } /* Append length in bits and transform */ ((uint32_t *) ctx->in)[14] = cvt32(ctx->bits[0]); ((uint32_t *) ctx->in)[15] = cvt32(ctx->bits[1]); md5_transform(ctx->buf, (uint32_t *) ctx->in); ctx->buf[0] = cvt32(ctx->buf[0]); ctx->buf[1] = cvt32(ctx->buf[1]); ctx->buf[2] = cvt32(ctx->buf[2]); ctx->buf[3] = cvt32(ctx->buf[3]); memcpy(digest, ctx->buf, 16); memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ } void md5_finish_ascii(struct MD5Context *context, char *ascii) { md5_byte_t bin[16]; int i; md5_finish(context, bin); for (i = 0; i < 16; i ++) sprintf(ascii + i * 2, "%02x", bin[i]); } /* The four core functions - F1 is optimized somewhat */ /* #define F1(x, y, z) (x & y | ~x & z) */ #define F1(x, y, z) (z ^ (x & (y ^ z))) #define F2(x, y, z) F1(z, x, y) #define F3(x, y, z) (x ^ y ^ z) #define F4(x, y, z) (y ^ (x | ~z)) /* This is the central step in the MD5 algorithm. */ #define MD5STEP(f, w, x, y, z, data, s) \ ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) /* * The core of the MD5 algorithm, this alters an existing MD5 hash to * reflect the addition of 16 longwords of new data. MD5Update blocks * the data and converts bytes into longwords for this routine. */ static void md5_transform(uint32_t buf[4], uint32_t const in[16]) { register uint32_t a, b, c, d; a = buf[0]; b = buf[1]; c = buf[2]; d = buf[3]; MD5STEP(F1, a, b, c, d, cvt32(in[0]) + 0xd76aa478, 7); MD5STEP(F1, d, a, b, c, cvt32(in[1]) + 0xe8c7b756, 12); MD5STEP(F1, c, d, a, b, cvt32(in[2]) + 0x242070db, 17); MD5STEP(F1, b, c, d, a, cvt32(in[3]) + 0xc1bdceee, 22); MD5STEP(F1, a, b, c, d, cvt32(in[4]) + 0xf57c0faf, 7); MD5STEP(F1, d, a, b, c, cvt32(in[5]) + 0x4787c62a, 12); MD5STEP(F1, c, d, a, b, cvt32(in[6]) + 0xa8304613, 17); MD5STEP(F1, b, c, d, a, cvt32(in[7]) + 0xfd469501, 22); MD5STEP(F1, a, b, c, d, cvt32(in[8]) + 0x698098d8, 7); MD5STEP(F1, d, a, b, c, cvt32(in[9]) + 0x8b44f7af, 12); MD5STEP(F1, c, d, a, b, cvt32(in[10]) + 0xffff5bb1, 17); MD5STEP(F1, b, c, d, a, cvt32(in[11]) + 0x895cd7be, 22); MD5STEP(F1, a, b, c, d, cvt32(in[12]) + 0x6b901122, 7); MD5STEP(F1, d, a, b, c, cvt32(in[13]) + 0xfd987193, 12); MD5STEP(F1, c, d, a, b, cvt32(in[14]) + 0xa679438e, 17); MD5STEP(F1, b, c, d, a, cvt32(in[15]) + 0x49b40821, 22); MD5STEP(F2, a, b, c, d, cvt32(in[1]) + 0xf61e2562, 5); MD5STEP(F2, d, a, b, c, cvt32(in[6]) + 0xc040b340, 9); MD5STEP(F2, c, d, a, b, cvt32(in[11]) + 0x265e5a51, 14); MD5STEP(F2, b, c, d, a, cvt32(in[0]) + 0xe9b6c7aa, 20); MD5STEP(F2, a, b, c, d, cvt32(in[5]) + 0xd62f105d, 5); MD5STEP(F2, d, a, b, c, cvt32(in[10]) + 0x02441453, 9); MD5STEP(F2, c, d, a, b, cvt32(in[15]) + 0xd8a1e681, 14); MD5STEP(F2, b, c, d, a, cvt32(in[4]) + 0xe7d3fbc8, 20); MD5STEP(F2, a, b, c, d, cvt32(in[9]) + 0x21e1cde6, 5); MD5STEP(F2, d, a, b, c, cvt32(in[14]) + 0xc33707d6, 9); MD5STEP(F2, c, d, a, b, cvt32(in[3]) + 0xf4d50d87, 14); MD5STEP(F2, b, c, d, a, cvt32(in[8]) + 0x455a14ed, 20); MD5STEP(F2, a, b, c, d, cvt32(in[13]) + 0xa9e3e905, 5); MD5STEP(F2, d, a, b, c, cvt32(in[2]) + 0xfcefa3f8, 9); MD5STEP(F2, c, d, a, b, cvt32(in[7]) + 0x676f02d9, 14); MD5STEP(F2, b, c, d, a, cvt32(in[12]) + 0x8d2a4c8a, 20); MD5STEP(F3, a, b, c, d, cvt32(in[5]) + 0xfffa3942, 4); MD5STEP(F3, d, a, b, c, cvt32(in[8]) + 0x8771f681, 11); MD5STEP(F3, c, d, a, b, cvt32(in[11]) + 0x6d9d6122, 16); MD5STEP(F3, b, c, d, a, cvt32(in[14]) + 0xfde5380c, 23); MD5STEP(F3, a, b, c, d, cvt32(in[1]) + 0xa4beea44, 4); MD5STEP(F3, d, a, b, c, cvt32(in[4]) + 0x4bdecfa9, 11); MD5STEP(F3, c, d, a, b, cvt32(in[7]) + 0xf6bb4b60, 16); MD5STEP(F3, b, c, d, a, cvt32(in[10]) + 0xbebfbc70, 23); MD5STEP(F3, a, b, c, d, cvt32(in[13]) + 0x289b7ec6, 4); MD5STEP(F3, d, a, b, c, cvt32(in[0]) + 0xeaa127fa, 11); MD5STEP(F3, c, d, a, b, cvt32(in[3]) + 0xd4ef3085, 16); MD5STEP(F3, b, c, d, a, cvt32(in[6]) + 0x04881d05, 23); MD5STEP(F3, a, b, c, d, cvt32(in[9]) + 0xd9d4d039, 4); MD5STEP(F3, d, a, b, c, cvt32(in[12]) + 0xe6db99e5, 11); MD5STEP(F3, c, d, a, b, cvt32(in[15]) + 0x1fa27cf8, 16); MD5STEP(F3, b, c, d, a, cvt32(in[2]) + 0xc4ac5665, 23); MD5STEP(F4, a, b, c, d, cvt32(in[0]) + 0xf4292244, 6); MD5STEP(F4, d, a, b, c, cvt32(in[7]) + 0x432aff97, 10); MD5STEP(F4, c, d, a, b, cvt32(in[14]) + 0xab9423a7, 15); MD5STEP(F4, b, c, d, a, cvt32(in[5]) + 0xfc93a039, 21); MD5STEP(F4, a, b, c, d, cvt32(in[12]) + 0x655b59c3, 6); MD5STEP(F4, d, a, b, c, cvt32(in[3]) + 0x8f0ccc92, 10); MD5STEP(F4, c, d, a, b, cvt32(in[10]) + 0xffeff47d, 15); MD5STEP(F4, b, c, d, a, cvt32(in[1]) + 0x85845dd1, 21); MD5STEP(F4, a, b, c, d, cvt32(in[8]) + 0x6fa87e4f, 6); MD5STEP(F4, d, a, b, c, cvt32(in[15]) + 0xfe2ce6e0, 10); MD5STEP(F4, c, d, a, b, cvt32(in[6]) + 0xa3014314, 15); MD5STEP(F4, b, c, d, a, cvt32(in[13]) + 0x4e0811a1, 21); MD5STEP(F4, a, b, c, d, cvt32(in[4]) + 0xf7537e82, 6); MD5STEP(F4, d, a, b, c, cvt32(in[11]) + 0xbd3af235, 10); MD5STEP(F4, c, d, a, b, cvt32(in[2]) + 0x2ad7d2bb, 15); MD5STEP(F4, b, c, d, a, cvt32(in[9]) + 0xeb86d391, 21); buf[0] += a; buf[1] += b; buf[2] += c; buf[3] += d; } bitlbee-3.2.1/lib/ssl_nss.c0000644000175000017500000002360112245474076015116 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* SSL module - NSS version */ /* Copyright 2005 Jelmer Vernooij */ /* 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 "proxy.h" #include "ssl_client.h" #include "sock.h" #include #include #include #include #include #include #include #include #include #include #include #include int ssl_errno = 0; static gboolean initialized = FALSE; #define SSLDEBUG 0 struct scd { ssl_input_function func; gpointer data; int fd; char *hostname; PRFileDesc *prfd; gboolean established; gboolean verify; }; static gboolean ssl_connected(gpointer data, gint source, b_input_condition cond); static gboolean ssl_starttls_real(gpointer data, gint source, b_input_condition cond); static SECStatus nss_auth_cert(void *arg, PRFileDesc * socket, PRBool checksig, PRBool isserver) { return SECSuccess; } static SECStatus nss_bad_cert(void *arg, PRFileDesc * socket) { PRErrorCode err; if (!arg) return SECFailure; *(PRErrorCode *) arg = err = PORT_GetError(); switch (err) { case SEC_ERROR_INVALID_AVA: case SEC_ERROR_INVALID_TIME: case SEC_ERROR_BAD_SIGNATURE: case SEC_ERROR_EXPIRED_CERTIFICATE: case SEC_ERROR_UNKNOWN_ISSUER: case SEC_ERROR_UNTRUSTED_CERT: case SEC_ERROR_CERT_VALID: case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: case SEC_ERROR_CRL_EXPIRED: case SEC_ERROR_CRL_BAD_SIGNATURE: case SEC_ERROR_EXTENSION_VALUE_INVALID: case SEC_ERROR_CA_CERT_INVALID: case SEC_ERROR_CERT_USAGES_INVALID: case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: return SECSuccess; default: return SECFailure; } } void ssl_init(void) { PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); // https://www.mozilla.org/projects/security/pki/nss/ref/ssl/sslfnc.html#1234224 // This NSS function is not intended for use with SSL, which // requires that the certificate and key database files be // opened. Relates to whole non-verification of servers for now. NSS_NoDB_Init(NULL); NSS_SetDomesticPolicy(); initialized = TRUE; } void *ssl_connect(char *host, int port, gboolean verify, ssl_input_function func, gpointer data) { struct scd *conn = g_new0(struct scd, 1); conn->fd = proxy_connect(host, port, ssl_connected, conn); conn->func = func; conn->data = data; conn->hostname = g_strdup(host); if (conn->fd < 0) { g_free(conn->hostname); g_free(conn); return (NULL); } if (!initialized) { ssl_init(); } return (conn); } static gboolean ssl_starttls_real(gpointer data, gint source, b_input_condition cond) { struct scd *conn = data; return ssl_connected(conn, conn->fd, B_EV_IO_WRITE); } void *ssl_starttls(int fd, char *hostname, gboolean verify, ssl_input_function func, gpointer data) { struct scd *conn = g_new0(struct scd, 1); conn->fd = fd; conn->func = func; conn->data = data; conn->hostname = g_strdup(hostname); /* For now, SSL verification is globally enabled by setting the cafile setting in bitlbee.conf. Commented out by default because probably not everyone has this file in the same place and plenty of folks may not have the cert of their private Jabber server in it. */ conn->verify = verify && global.conf->cafile; /* This function should be called via a (short) timeout instead of directly from here, because these SSL calls are *supposed* to be *completely* asynchronous and not ready yet when this function (or *_connect, for examle) returns. Also, errors are reported via the callback function, not via this function's return value. In short, doing things like this makes the rest of the code a lot simpler. */ b_timeout_add(1, ssl_starttls_real, conn); return conn; } static gboolean ssl_connected(gpointer data, gint source, b_input_condition cond) { struct scd *conn = data; /* Right now we don't have any verification functionality for NSS. */ if (conn->verify) { conn->func(conn->data, 1, NULL, cond); if (source >= 0) closesocket(source); g_free(conn->hostname); g_free(conn); return FALSE; } if (source == -1) goto ssl_connected_failure; /* Until we find out how to handle non-blocking I/O with NSS... */ sock_make_blocking(conn->fd); conn->prfd = SSL_ImportFD(NULL, PR_ImportTCPSocket(source)); if (!conn->prfd) goto ssl_connected_failure; SSL_OptionSet(conn->prfd, SSL_SECURITY, PR_TRUE); SSL_OptionSet(conn->prfd, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE); SSL_BadCertHook(conn->prfd, (SSLBadCertHandler) nss_bad_cert, NULL); SSL_AuthCertificateHook(conn->prfd, (SSLAuthCertificate) nss_auth_cert, (void *)CERT_GetDefaultCertDB()); SSL_SetURL(conn->prfd, conn->hostname); SSL_ResetHandshake(conn->prfd, PR_FALSE); if (SSL_ForceHandshake(conn->prfd)) { goto ssl_connected_failure; } conn->established = TRUE; conn->func(conn->data, 0, conn, cond); return FALSE; ssl_connected_failure: conn->func(conn->data, 0, NULL, cond); if (conn->prfd) PR_Close(conn->prfd); if (source >= 0) closesocket(source); g_free(conn->hostname); g_free(conn); return FALSE; } int ssl_read(void *conn, char *buf, int len) { int st; PRErrorCode PR_err; if (!((struct scd *)conn)->established) { ssl_errno = SSL_NOHANDSHAKE; return -1; } st = PR_Read(((struct scd *)conn)->prfd, buf, len); PR_err = PR_GetError(); ssl_errno = SSL_OK; if (PR_err == PR_WOULD_BLOCK_ERROR) ssl_errno = SSL_AGAIN; if (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) len = write(STDERR_FILENO, buf, st); return st; } int ssl_write(void *conn, const char *buf, int len) { int st; PRErrorCode PR_err; if (!((struct scd *)conn)->established) { ssl_errno = SSL_NOHANDSHAKE; return -1; } st = PR_Write(((struct scd *)conn)->prfd, buf, len); PR_err = PR_GetError(); ssl_errno = SSL_OK; if (PR_err == PR_WOULD_BLOCK_ERROR) ssl_errno = SSL_AGAIN; if (SSLDEBUG && getenv("BITLBEE_DEBUG") && st > 0) len = write(2, buf, st); return st; } int ssl_pending(void *conn) { struct scd *c = (struct scd *)conn; if (c == NULL) { return 0; } return (c->established && SSL_DataPending(c->prfd) > 0); } void ssl_disconnect(void *conn_) { struct scd *conn = conn_; // When we swich to NSS_Init, we should have here // NSS_Shutdown(); if (conn->prfd) PR_Close(conn->prfd); g_free(conn->hostname); g_free(conn); } int ssl_getfd(void *conn) { return (((struct scd *)conn)->fd); } b_input_condition ssl_getdirection(void *conn) { /* Just in case someone calls us, let's return the most likely case: */ return B_EV_IO_READ; } char *ssl_verify_strerror(int code) { return g_strdup ("SSL certificate verification not supported by BitlBee NSS code."); } size_t ssl_des3_encrypt(const unsigned char *key, size_t key_len, const unsigned char *input, size_t input_len, const unsigned char *iv, unsigned char **res) { #define CIPHER_MECH CKM_DES3_CBC #define MAX_OUTPUT_LEN 72 int len1; unsigned int len2; PK11Context *ctx = NULL; PK11SlotInfo *slot = NULL; SECItem keyItem; SECItem ivItem; SECItem *secParam = NULL; PK11SymKey *symKey = NULL; size_t rc; SECStatus rv; if (!initialized) { ssl_init(); } keyItem.data = (unsigned char *)key; keyItem.len = key_len; slot = PK11_GetBestSlot(CIPHER_MECH, NULL); if (slot == NULL) { fprintf(stderr, "PK11_GetBestSlot failed (err %d)\n", PR_GetError()); rc = 0; goto out; } symKey = PK11_ImportSymKey(slot, CIPHER_MECH, PK11_OriginUnwrap, CKA_ENCRYPT, &keyItem, NULL); if (symKey == NULL) { fprintf(stderr, "PK11_ImportSymKey failed (err %d)\n", PR_GetError()); rc = 0; goto out; } ivItem.data = (unsigned char *)iv; /* See msn_soap_passport_sso_handle_response in protocols/msn/soap.c */ ivItem.len = 8; secParam = PK11_ParamFromIV(CIPHER_MECH, &ivItem); if (secParam == NULL) { fprintf(stderr, "PK11_ParamFromIV failed (err %d)\n", PR_GetError()); rc = 0; goto out; } ctx = PK11_CreateContextBySymKey(CIPHER_MECH, CKA_ENCRYPT, symKey, secParam); if (ctx == NULL) { fprintf(stderr, "PK11_CreateContextBySymKey failed (err %d)\n", PR_GetError()); rc = 0; goto out; } *res = g_new0(unsigned char, MAX_OUTPUT_LEN); rv = PK11_CipherOp(ctx, *res, &len1, MAX_OUTPUT_LEN, (unsigned char *)input, input_len); if (rv != SECSuccess) { fprintf(stderr, "PK11_CipherOp failed (err %d)\n", PR_GetError()); rc = 0; goto out; } assert(len1 <= MAX_OUTPUT_LEN); rv = PK11_DigestFinal(ctx, *res + len1, &len2, (unsigned int)MAX_OUTPUT_LEN - len1); if (rv != SECSuccess) { fprintf(stderr, "PK11_DigestFinal failed (err %d)\n", PR_GetError()); rc = 0; goto out; } rc = len1 + len2; out: if (ctx) PK11_DestroyContext(ctx, PR_TRUE); if (symKey) PK11_FreeSymKey(symKey); if (secParam) SECITEM_FreeItem(secParam, PR_TRUE); if (slot) PK11_FreeSlot(slot); return rc; } bitlbee-3.2.1/lib/oauth.h0000644000175000017500000001013412245474076014554 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple OAuth client (consumer) implementation. * * * * Copyright 2010 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ /* http://oauth.net/core/1.0a/ */ struct oauth_info; /* Callback function called twice during the access token request process. Return FALSE if something broke and the process must be aborted. */ typedef gboolean (*oauth_cb)( struct oauth_info * ); typedef enum { OAUTH_INIT, OAUTH_REQUEST_TOKEN, OAUTH_ACCESS_TOKEN, } oauth_stage_t; struct oauth_info { oauth_stage_t stage; const struct oauth_service *sp; oauth_cb func; void *data; struct http_request *http; char *auth_url; char *request_token; char *token; char *token_secret; GSList *params; }; struct oauth_service { char *url_request_token; char *url_access_token; char *url_authorize; char *consumer_key; char *consumer_secret; }; /* http://oauth.net/core/1.0a/#auth_step1 (section 6.1) Request an initial anonymous token which can be used to construct an authorization URL for the user. This is passed to the callback function in a struct oauth_info. */ struct oauth_info *oauth_request_token( const struct oauth_service *sp, oauth_cb func, void *data ); /* http://oauth.net/core/1.0a/#auth_step3 (section 6.3) The user gets a PIN or so which we now exchange for the final access token. This is passed to the callback function in the same struct oauth_info. */ gboolean oauth_access_token( const char *pin, struct oauth_info *st ); /* http://oauth.net/core/1.0a/#anchor12 (section 7) Generate an OAuth Authorization: HTTP header. access_token should be saved/fetched using the functions above. args can be a string with whatever's going to be in the POST body of the request. GET args will automatically be grabbed from url. */ char *oauth_http_header( struct oauth_info *oi, const char *method, const char *url, char *args ); /* Shouldn't normally be required unless the process is aborted by the user. */ void oauth_info_free( struct oauth_info *info ); /* Convert to and back from strings, for easier saving. */ char *oauth_to_string( struct oauth_info *oi ); struct oauth_info *oauth_from_string( char *in, const struct oauth_service *sp ); /* For reading misc. data. */ void oauth_params_add( GSList **params, const char *key, const char *value ); void oauth_params_parse( GSList **params, char *in ); void oauth_params_free( GSList **params ); char *oauth_params_string( GSList *params ); void oauth_params_set( GSList **params, const char *key, const char *value ); const char *oauth_params_get( GSList **params, const char *key ); bitlbee-3.2.1/lib/ini.c0000644000175000017500000000625212245474076014214 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2008 Wilmer van der Gaast and others * \********************************************************************/ /* INI file reading code */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" ini_t *ini_open( char *file ) { int fd; ini_t *ini = NULL; struct stat fi; if( ( fd = open( file, O_RDONLY ) ) != -1 && fstat( fd, &fi ) == 0 && fi.st_size <= 16384 && ( ini = g_malloc( sizeof( ini_t ) + fi.st_size + 1 ) ) && read( fd, ini->file, fi.st_size ) == fi.st_size ) { memset( ini, 0, sizeof( ini_t ) ); ini->size = fi.st_size; ini->file[ini->size] = 0; ini->cur = ini->file; ini->c_section = ""; close( fd ); return ini; } if( fd >= 0 ) close( fd ); ini_close( ini ); return NULL; } /* Strips leading and trailing whitespace and returns a pointer to the first non-ws character of the given string. */ static char *ini_strip_whitespace( char *in ) { char *e; while( isspace( *in ) ) in++; e = in + strlen( in ) - 1; while( e > in && isspace( *e ) ) e--; e[1] = 0; return in; } int ini_read( ini_t *file ) { char *s; while( file->cur && file->cur < file->file + file->size ) { char *e, *next; file->line++; /* Find the end of line */ if( ( e = strchr( file->cur, '\n' ) ) != NULL ) { *e = 0; next = e + 1; } else { /* No more lines. */ e = file->cur + strlen( file->cur ); next = NULL; } /* Comment? */ if( ( s = strchr( file->cur, '#' ) ) != NULL ) *s = 0; file->cur = ini_strip_whitespace( file->cur ); if( *file->cur == '[' ) { file->cur++; if( ( s = strchr( file->cur, ']' ) ) != NULL ) { *s = 0; file->c_section = file->cur; } } else if( ( s = strchr( file->cur, '=' ) ) != NULL ) { *s = 0; file->key = ini_strip_whitespace( file->cur ); file->value = ini_strip_whitespace( s + 1 ); if( ( s = strchr( file->key, '.' ) ) != NULL ) { *s = 0; file->section = file->key; file->key = s + 1; } else { file->section = file->c_section; } file->cur = next; return 1; } /* else: noise/comment/etc, let's just ignore it. */ file->cur = next; } return 0; } void ini_close( ini_t *file ) { g_free( file ); } bitlbee-3.2.1/conf.c0000644000175000017500000002623612245474076013620 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2005 Wilmer van der Gaast and others * \********************************************************************/ /* Configuration reading code */ /* 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 #include #include #include "conf.h" #include "ini.h" #include "url.h" #include "ipc.h" #include "proxy.h" static int conf_loadini( conf_t *conf, char *file ); conf_t *conf_load( int argc, char *argv[] ) { conf_t *conf; int opt, i, config_missing = 0; conf = g_new0( conf_t, 1 ); conf->iface_in = NULL; conf->iface_out = NULL; conf->port = g_strdup( "6667" ); conf->nofork = 0; conf->verbose = 0; conf->primary_storage = g_strdup( "xml" ); conf->migrate_storage = g_strsplit( "text", ",", -1 ); conf->runmode = RUNMODE_INETD; conf->authmode = AUTHMODE_OPEN; conf->auth_pass = NULL; conf->oper_pass = NULL; conf->configdir = g_strdup( CONFIG ); conf->plugindir = g_strdup( PLUGINDIR ); conf->pidfile = g_strdup( PIDFILE ); conf->motdfile = g_strdup( ETCDIR "/motd.txt" ); conf->ping_interval = 180; conf->ping_timeout = 300; conf->user = NULL; conf->ft_max_size = SIZE_MAX; conf->ft_max_kbps = G_MAXUINT; conf->ft_listen = NULL; conf->protocols = NULL; conf->cafile = NULL; proxytype = 0; i = conf_loadini( conf, global.conf_file ); if( i == 0 ) { fprintf( stderr, "Error: Syntax error in configuration file `%s'.\n", global.conf_file ); return NULL; } else if( i == -1 ) { config_missing ++; /* Whine after parsing the options if there was no -c pointing at a *valid* configuration file. */ } while( argc > 0 && ( opt = getopt( argc, argv, "i:p:P:nvIDFc:d:hu:V" ) ) >= 0 ) /* ^^^^ Just to make sure we skip this step from the REHASH handler. */ { if( opt == 'i' ) { conf->iface_in = g_strdup( optarg ); } else if( opt == 'p' ) { g_free( conf->port ); conf->port = g_strdup( optarg ); } else if( opt == 'P' ) { g_free( conf->pidfile ); conf->pidfile = g_strdup( optarg ); } else if( opt == 'n' ) conf->nofork = 1; else if( opt == 'v' ) conf->verbose = 1; else if( opt == 'I' ) conf->runmode = RUNMODE_INETD; else if( opt == 'D' ) conf->runmode = RUNMODE_DAEMON; else if( opt == 'F' ) conf->runmode = RUNMODE_FORKDAEMON; else if( opt == 'c' ) { if( strcmp( global.conf_file, optarg ) != 0 ) { g_free( global.conf_file ); global.conf_file = g_strdup( optarg ); g_free( conf ); /* Re-evaluate arguments. Don't use this option twice, you'll end up in an infinite loop! Hope this trick works with all libcs BTW.. */ optind = 1; return conf_load( argc, argv ); } } else if( opt == 'd' ) { g_free( conf->configdir ); conf->configdir = g_strdup( optarg ); } else if( opt == 'h' ) { printf( "Usage: bitlbee [-D/-F [-i ] [-p ] [-n] [-v]] [-I]\n" " [-c ] [-d ] [-x] [-h]\n" "\n" "An IRC-to-other-chat-networks gateway\n" "\n" " -I Classic/InetD mode. (Default)\n" " -D Daemon mode. (one process serves all)\n" " -F Forking daemon. (one process per client)\n" " -u Run daemon as specified user.\n" " -P Specify PID-file (not for inetd mode)\n" " -i Specify the interface (by IP address) to listen on.\n" " (Default: 0.0.0.0 (any interface))\n" " -p Port number to listen on. (Default: 6667)\n" " -n Don't fork.\n" " -v Be verbose (only works in combination with -n)\n" " -c Load alternative configuration file\n" " -d Specify alternative user configuration directory\n" " -x Command-line interface to password encryption/hashing\n" " -h Show this help page.\n" " -V Show version info.\n" ); return NULL; } else if( opt == 'V' ) { printf( "BitlBee %s\nAPI version %06x\n", BITLBEE_VERSION, BITLBEE_VERSION_CODE ); return NULL; } else if( opt == 'u' ) { g_free( conf->user ); conf->user = g_strdup( optarg ); } } if( conf->configdir[strlen(conf->configdir)-1] != '/' ) { char *s = g_new( char, strlen( conf->configdir ) + 2 ); sprintf( s, "%s/", conf->configdir ); g_free( conf->configdir ); conf->configdir = s; } if( config_missing ) fprintf( stderr, "Warning: Unable to read configuration file `%s'.\n", global.conf_file ); if( conf->cafile && access( conf->cafile, R_OK ) != 0 ) { /* Let's treat this as a serious problem so people won't think they're secure when in fact they're not. */ fprintf( stderr, "Error: Could not read CA file %s: %s\n", conf->cafile, strerror( errno ) ); return NULL; } return conf; } static int conf_loadini( conf_t *conf, char *file ) { ini_t *ini; int i; ini = ini_open( file ); if( ini == NULL ) return -1; while( ini_read( ini ) ) { if( g_strcasecmp( ini->section, "settings" ) == 0 ) { if( g_strcasecmp( ini->key, "runmode" ) == 0 ) { if( g_strcasecmp( ini->value, "daemon" ) == 0 ) conf->runmode = RUNMODE_DAEMON; else if( g_strcasecmp( ini->value, "forkdaemon" ) == 0 ) conf->runmode = RUNMODE_FORKDAEMON; else conf->runmode = RUNMODE_INETD; } else if( g_strcasecmp( ini->key, "pidfile" ) == 0 ) { g_free( conf->pidfile ); conf->pidfile = g_strdup( ini->value ); } else if( g_strcasecmp( ini->key, "daemoninterface" ) == 0 ) { g_free( conf->iface_in ); conf->iface_in = g_strdup( ini->value ); } else if( g_strcasecmp( ini->key, "daemonport" ) == 0 ) { g_free( conf->port ); conf->port = g_strdup( ini->value ); } else if( g_strcasecmp( ini->key, "clientinterface" ) == 0 ) { g_free( conf->iface_out ); conf->iface_out = g_strdup( ini->value ); } else if( g_strcasecmp( ini->key, "authmode" ) == 0 ) { if( g_strcasecmp( ini->value, "registered" ) == 0 ) conf->authmode = AUTHMODE_REGISTERED; else if( g_strcasecmp( ini->value, "closed" ) == 0 ) conf->authmode = AUTHMODE_CLOSED; else conf->authmode = AUTHMODE_OPEN; } else if( g_strcasecmp( ini->key, "authpassword" ) == 0 ) { g_free( conf->auth_pass ); conf->auth_pass = g_strdup( ini->value ); } else if( g_strcasecmp( ini->key, "operpassword" ) == 0 ) { g_free( conf->oper_pass ); conf->oper_pass = g_strdup( ini->value ); } else if( g_strcasecmp( ini->key, "hostname" ) == 0 ) { g_free( conf->hostname ); conf->hostname = g_strdup( ini->value ); } else if( g_strcasecmp( ini->key, "configdir" ) == 0 ) { g_free( conf->configdir ); conf->configdir = g_strdup( ini->value ); } else if( g_strcasecmp( ini->key, "motdfile" ) == 0 ) { g_free( conf->motdfile ); conf->motdfile = g_strdup( ini->value ); } else if( g_strcasecmp( ini->key, "account_storage" ) == 0 ) { g_free( conf->primary_storage ); conf->primary_storage = g_strdup( ini->value ); } else if( g_strcasecmp( ini->key, "account_storage_migrate" ) == 0 ) { g_strfreev( conf->migrate_storage ); conf->migrate_storage = g_strsplit_set( ini->value, " \t,;", -1 ); } else if( g_strcasecmp( ini->key, "pinginterval" ) == 0 ) { if( sscanf( ini->value, "%d", &i ) != 1 ) { fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value ); return 0; } conf->ping_interval = i; } else if( g_strcasecmp( ini->key, "pingtimeout" ) == 0 ) { if( sscanf( ini->value, "%d", &i ) != 1 ) { fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value ); return 0; } conf->ping_timeout = i; } else if( g_strcasecmp( ini->key, "proxy" ) == 0 ) { url_t *url = g_new0( url_t, 1 ); if( !url_set( url, ini->value ) ) { fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value ); g_free( url ); return 0; } strncpy( proxyhost, url->host, sizeof( proxyhost ) ); strncpy( proxyuser, url->user, sizeof( proxyuser ) ); strncpy( proxypass, url->pass, sizeof( proxypass ) ); proxyport = url->port; if( url->proto == PROTO_HTTP ) proxytype = PROXY_HTTP; else if( url->proto == PROTO_SOCKS4 ) proxytype = PROXY_SOCKS4; else if( url->proto == PROTO_SOCKS5 ) proxytype = PROXY_SOCKS5; g_free( url ); } else if( g_strcasecmp( ini->key, "user" ) == 0 ) { g_free( conf->user ); conf->user = g_strdup( ini->value ); } else if( g_strcasecmp( ini->key, "ft_max_size" ) == 0 ) { size_t ft_max_size; if( sscanf( ini->value, "%zu", &ft_max_size ) != 1 ) { fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value ); return 0; } conf->ft_max_size = ft_max_size; } else if( g_strcasecmp( ini->key, "ft_max_kbps" ) == 0 ) { if( sscanf( ini->value, "%d", &i ) != 1 ) { fprintf( stderr, "Invalid %s value: %s\n", ini->key, ini->value ); return 0; } conf->ft_max_kbps = i; } else if( g_strcasecmp( ini->key, "ft_listen" ) == 0 ) { g_free( conf->ft_listen ); conf->ft_listen = g_strdup( ini->value ); } else if( g_strcasecmp( ini->key, "protocols" ) == 0 ) { g_strfreev( conf->protocols ); conf->protocols = g_strsplit_set( ini->value, " \t,;", -1 ); } else if( g_strcasecmp( ini->key, "cafile" ) == 0 ) { g_free( conf->cafile ); conf->cafile = g_strdup( ini->value ); } else { fprintf( stderr, "Error: Unknown setting `%s` in configuration file (line %d).\n", ini->key, ini->line ); return 0; /* For now just ignore unknown keys... */ } } else if( g_strcasecmp( ini->section, "defaults" ) != 0 ) { fprintf( stderr, "Error: Unknown section [%s] in configuration file (line %d). " "BitlBee configuration must be put in a [settings] section!\n", ini->section, ini->line ); return 0; } } ini_close( ini ); return 1; } void conf_loaddefaults( irc_t *irc ) { ini_t *ini; ini = ini_open( global.conf_file ); if( ini == NULL ) return; while( ini_read( ini ) ) { if( g_strcasecmp( ini->section, "defaults" ) == 0 ) { set_t *s = set_find( &irc->b->set, ini->key ); if( s ) { if( s->def ) g_free( s->def ); s->def = g_strdup( ini->value ); } } } ini_close( ini ); } bitlbee-3.2.1/irc_commands.c0000644000175000017500000005326412245474076015332 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* IRC commands */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" #include "help.h" #include "ipc.h" static void irc_cmd_pass( irc_t *irc, char **cmd ) { if( irc->status & USTATUS_LOGGED_IN ) { char *send_cmd[] = { "identify", cmd[1], NULL }; /* We're already logged in, this client seems to send the PASS command last. (Possibly it won't send it at all if it turns out we don't require it, which will break this feature.) Try to identify using the given password. */ root_command( irc, send_cmd ); return; } /* Handling in pre-logged-in state, first see if this server is password-protected: */ else if( global.conf->auth_pass && ( strncmp( global.conf->auth_pass, "md5:", 4 ) == 0 ? md5_verify_password( cmd[1], global.conf->auth_pass + 4 ) == 0 : strcmp( cmd[1], global.conf->auth_pass ) == 0 ) ) { irc->status |= USTATUS_AUTHORIZED; irc_check_login( irc ); } else if( global.conf->auth_pass ) { irc_send_num( irc, 464, ":Incorrect password" ); } else { /* Remember the password and try to identify after USER/NICK. */ irc_setpass( irc, cmd[1] ); irc_check_login( irc ); } } static void irc_cmd_user( irc_t *irc, char **cmd ) { irc->user->user = g_strdup( cmd[1] ); irc->user->fullname = g_strdup( cmd[4] ); irc_check_login( irc ); } static void irc_cmd_nick( irc_t *irc, char **cmd ) { irc_user_t *iu; if( ( iu = irc_user_by_name( irc, cmd[1] ) ) && iu != irc->user ) { irc_send_num( irc, 433, "%s :This nick is already in use", cmd[1] ); } else if( !nick_ok( NULL, cmd[1] ) ) { /* [SH] Invalid characters. */ irc_send_num( irc, 432, "%s :This nick contains invalid characters", cmd[1] ); } else if( irc->status & USTATUS_LOGGED_IN ) { /* WATCH OUT: iu from the first if reused here to check if the new nickname is the same (other than case, possibly). If it is, no need to reset identify-status. */ if( ( irc->status & USTATUS_IDENTIFIED ) && iu != irc->user ) { irc_setpass( irc, NULL ); irc->status &= ~USTATUS_IDENTIFIED; irc_umode_set( irc, "-R", 1 ); irc_rootmsg( irc, "Changing nicks resets your identify status. " "Re-identify or register a new account if you want " "your configuration to be saved. See \x02help " "nick_changes\x02." ); } if( strcmp( cmd[1], irc->user->nick ) != 0 ) irc_user_set_nick( irc->user, cmd[1] ); } else { g_free( irc->user->nick ); irc->user->nick = g_strdup( cmd[1] ); irc_check_login( irc ); } } static void irc_cmd_quit( irc_t *irc, char **cmd ) { if( cmd[1] && *cmd[1] ) irc_abort( irc, 0, "Quit: %s", cmd[1] ); else irc_abort( irc, 0, "Leaving..." ); } static void irc_cmd_ping( irc_t *irc, char **cmd ) { irc_write( irc, ":%s PONG %s :%s", irc->root->host, irc->root->host, cmd[1]?cmd[1]:irc->root->host ); } static void irc_cmd_pong( irc_t *irc, char **cmd ) { /* We could check the value we get back from the user, but in fact we don't care, we're just happy s/he's still alive. */ irc->last_pong = gettime(); irc->pinging = 0; } static void irc_cmd_join( irc_t *irc, char **cmd ) { char *comma, *s = cmd[1]; while( s ) { irc_channel_t *ic; if( ( comma = strchr( s, ',' ) ) ) *comma = '\0'; if( ( ic = irc_channel_by_name( irc, s ) ) == NULL && ( ic = irc_channel_new( irc, s ) ) ) { if( strcmp( set_getstr( &ic->set, "type" ), "control" ) != 0 ) { /* Autoconfiguration is for control channels only ATM. */ } else if( bee_group_by_name( ic->irc->b, ic->name + 1, FALSE ) ) { set_setstr( &ic->set, "group", ic->name + 1 ); set_setstr( &ic->set, "fill_by", "group" ); } else if( set_setstr( &ic->set, "protocol", ic->name + 1 ) ) { set_setstr( &ic->set, "fill_by", "protocol" ); } else if( set_setstr( &ic->set, "account", ic->name + 1 ) ) { set_setstr( &ic->set, "fill_by", "account" ); } else { /* The set commands above will run this already, but if we didn't hit any, we have to fill the channel with the default population. */ bee_irc_channel_update( ic->irc, ic, NULL ); } } else if( ic == NULL ) { irc_send_num( irc, 479, "%s :Invalid channel name", s ); goto next; } if( ic->flags & IRC_CHANNEL_JOINED ) /* Dude, you're already there... RFC doesn't have any reply for that though? */ goto next; if( ic->f->join && !ic->f->join( ic ) ) /* The story is: FALSE either means the handler showed an error message, or is doing some work before the join should be confirmed. (In the latter case, the caller should take care of that confirmation.) TRUE means all's good, let the user join the channel right away. */ goto next; irc_channel_add_user( ic, irc->user ); next: if( comma ) { s = comma + 1; *comma = ','; } else break; } } static void irc_cmd_names( irc_t *irc, char **cmd ) { irc_channel_t *ic; if( cmd[1] && ( ic = irc_channel_by_name( irc, cmd[1] ) ) ) irc_send_names( ic ); /* With no args, we should show /names of all chans. Make the code below work well if necessary. else { GSList *l; for( l = irc->channels; l; l = l->next ) irc_send_names( l->data ); } */ } static void irc_cmd_part( irc_t *irc, char **cmd ) { irc_channel_t *ic; if( ( ic = irc_channel_by_name( irc, cmd[1] ) ) == NULL ) { irc_send_num( irc, 403, "%s :No such channel", cmd[1] ); } else if( irc_channel_del_user( ic, irc->user, IRC_CDU_PART, cmd[2] ) ) { if( ic->f->part ) ic->f->part( ic, NULL ); } else { irc_send_num( irc, 442, "%s :You're not on that channel", cmd[1] ); } } static void irc_cmd_whois( irc_t *irc, char **cmd ) { char *nick = cmd[1]; irc_user_t *iu = irc_user_by_name( irc, nick ); if( iu ) irc_send_whois( iu ); else irc_send_num( irc, 401, "%s :Nick does not exist", nick ); } static void irc_cmd_whowas( irc_t *irc, char **cmd ) { /* For some reason irssi tries a whowas when whois fails. We can ignore this, but then the user never gets a "user not found" message from irssi which is a bit annoying. So just respond with not-found and irssi users will get better error messages */ irc_send_num( irc, 406, "%s :Nick does not exist", cmd[1] ); irc_send_num( irc, 369, "%s :End of WHOWAS", cmd[1] ); } static void irc_cmd_motd( irc_t *irc, char **cmd ) { irc_send_motd( irc ); } static void irc_cmd_mode( irc_t *irc, char **cmd ) { if( irc_channel_name_ok( cmd[1] ) ) { irc_channel_t *ic; if( ( ic = irc_channel_by_name( irc, cmd[1] ) ) == NULL ) irc_send_num( irc, 403, "%s :No such channel", cmd[1] ); else if( cmd[2] ) { if( *cmd[2] == '+' || *cmd[2] == '-' ) irc_send_num( irc, 477, "%s :Can't change channel modes", cmd[1] ); else if( *cmd[2] == 'b' ) irc_send_num( irc, 368, "%s :No bans possible", cmd[1] ); } else irc_send_num( irc, 324, "%s +%s", cmd[1], ic->mode ); } else { if( nick_cmp( NULL, cmd[1], irc->user->nick ) == 0 ) { if( cmd[2] ) irc_umode_set( irc, cmd[2], 0 ); else irc_send_num( irc, 221, "+%s", irc->umode ); } else irc_send_num( irc, 502, ":Don't touch their modes" ); } } static void irc_cmd_who( irc_t *irc, char **cmd ) { char *channel = cmd[1]; irc_channel_t *ic; irc_user_t *iu; if( !channel || *channel == '0' || *channel == '*' || !*channel ) irc_send_who( irc, irc->users, "**" ); else if( ( ic = irc_channel_by_name( irc, channel ) ) ) irc_send_who( irc, ic->users, channel ); else if( ( iu = irc_user_by_name( irc, channel ) ) ) { /* Tiny hack! */ GSList *l = g_slist_append( NULL, iu ); irc_send_who( irc, l, channel ); g_slist_free( l ); } else irc_send_num( irc, 403, "%s :No such channel", channel ); } static void irc_cmd_privmsg( irc_t *irc, char **cmd ) { irc_channel_t *ic; irc_user_t *iu; if( !cmd[2] ) { irc_send_num( irc, 412, ":No text to send" ); return; } /* Don't treat CTCP actions as real CTCPs, just convert them right now. */ if( g_strncasecmp( cmd[2], "\001ACTION", 7 ) == 0 ) { cmd[2] += 4; memcpy( cmd[2], "/me", 3 ); if( cmd[2][strlen(cmd[2])-1] == '\001' ) cmd[2][strlen(cmd[2])-1] = '\0'; } if( irc_channel_name_ok( cmd[1] ) && ( ic = irc_channel_by_name( irc, cmd[1] ) ) ) { if( cmd[2][0] == '\001' ) { /* CTCPs to channels? Nah. Maybe later. */ } else if( ic->f->privmsg ) ic->f->privmsg( ic, cmd[2] ); } else if( ( iu = irc_user_by_name( irc, cmd[1] ) ) ) { if( cmd[2][0] == '\001' ) { char **ctcp; if( iu->f->ctcp == NULL ) return; if( cmd[2][strlen(cmd[2])-1] == '\001' ) cmd[2][strlen(cmd[2])-1] = '\0'; ctcp = split_command_parts( cmd[2] + 1 ); iu->f->ctcp( iu, ctcp ); } else if( iu->f->privmsg ) { iu->last_channel = NULL; iu->f->privmsg( iu, cmd[2] ); } } else { irc_send_num( irc, 401, "%s :No such nick/channel", cmd[1] ); } } static void irc_cmd_notice( irc_t *irc, char **cmd ) { irc_user_t *iu; if( !cmd[2] ) { irc_send_num( irc, 412, ":No text to send" ); return; } /* At least for now just echo. IIRC some IRC clients use self-notices for lag checks, so try to support that. */ if( nick_cmp( NULL, cmd[1], irc->user->nick ) == 0 ) irc_send_msg( irc->user, "NOTICE", irc->user->nick, cmd[2], NULL ); else if( ( iu = irc_user_by_name( irc, cmd[1] ) ) ) iu->f->privmsg( iu, cmd[2] ); } static void irc_cmd_nickserv( irc_t *irc, char **cmd ) { /* [SH] This aliases the NickServ command to PRIVMSG root */ /* [TV] This aliases the NS command to PRIVMSG root as well */ root_command( irc, cmd + 1 ); } static void irc_cmd_oper_hack( irc_t *irc, char **cmd ); static void irc_cmd_oper( irc_t *irc, char **cmd ) { /* Very non-standard evil but useful/secure hack, see below. */ if( irc->status & OPER_HACK_ANY ) return irc_cmd_oper_hack( irc, cmd ); if( global.conf->oper_pass && ( strncmp( global.conf->oper_pass, "md5:", 4 ) == 0 ? md5_verify_password( cmd[2], global.conf->oper_pass + 4 ) == 0 : strcmp( cmd[2], global.conf->oper_pass ) == 0 ) ) { irc_umode_set( irc, "+o", 1 ); irc_send_num( irc, 381, ":Password accepted" ); } else { irc_send_num( irc, 491, ":Incorrect password" ); } } static void irc_cmd_oper_hack( irc_t *irc, char **cmd ) { char *password = g_strjoinv( " ", cmd + 2 ); /* /OPER can now also be used to enter IM/identify passwords without echoing. It's a hack but the extra password security is worth it. */ if( irc->status & OPER_HACK_ACCOUNT_ADD ) { account_t *a; for( a = irc->b->accounts; a; a = a->next ) if( strcmp( a->pass, PASSWORD_PENDING ) == 0 ) { set_setstr( &a->set, "password", password ); irc_rootmsg( irc, "Password added to IM account " "%s", a->tag ); /* The IRC client may expect this. 491 suggests the OPER password was wrong, so the client won't expect a +o. It may however repeat the password prompt. We'll see. */ irc_send_num( irc, 491, ":Password added to IM account " "%s", a->tag ); } } else if( irc->status & OPER_HACK_IDENTIFY ) { char *send_cmd[] = { "identify", password, NULL, NULL }; irc->status &= ~OPER_HACK_IDENTIFY; if( irc->status & OPER_HACK_IDENTIFY_NOLOAD ) { send_cmd[1] = "-noload"; send_cmd[2] = password; } else if( irc->status & OPER_HACK_IDENTIFY_FORCE ) { send_cmd[1] = "-force"; send_cmd[2] = password; } irc_send_num( irc, 491, ":Trying to identify" ); root_command( irc, send_cmd ); } else if( irc->status & OPER_HACK_REGISTER ) { char *send_cmd[] = { "register", password, NULL }; irc_send_num( irc, 491, ":Trying to identify" ); root_command( irc, send_cmd ); } irc->status &= ~OPER_HACK_ANY; g_free( password ); } static void irc_cmd_invite( irc_t *irc, char **cmd ) { irc_channel_t *ic; irc_user_t *iu; if( ( iu = irc_user_by_name( irc, cmd[1] ) ) == NULL ) { irc_send_num( irc, 401, "%s :No such nick", cmd[1] ); return; } else if( ( ic = irc_channel_by_name( irc, cmd[2] ) ) == NULL ) { irc_send_num( irc, 403, "%s :No such channel", cmd[2] ); return; } if( !ic->f->invite ) irc_send_num( irc, 482, "%s :Can't invite people here", cmd[2] ); else if( ic->f->invite( ic, iu ) ) irc_send_num( irc, 341, "%s %s", iu->nick, ic->name ); } static void irc_cmd_userhost( irc_t *irc, char **cmd ) { int i; /* [TV] Usable USERHOST-implementation according to RFC1459. Without this, mIRC shows an error while connecting, and the used way of rejecting breaks standards. */ for( i = 1; cmd[i]; i ++ ) { irc_user_t *iu = irc_user_by_name( irc, cmd[i] ); if( iu ) irc_send_num( irc, 302, ":%s=%c%s@%s", iu->nick, irc_user_get_away( iu ) ? '-' : '+', iu->user, iu->host ); } } static void irc_cmd_ison( irc_t *irc, char **cmd ) { char buff[IRC_MAX_LINE]; int lenleft, i; buff[0] = '\0'; /* [SH] Leave room for : and \0 */ lenleft = IRC_MAX_LINE - 2; for( i = 1; cmd[i]; i ++ ) { char *this, *next; this = cmd[i]; while( *this ) { irc_user_t *iu; if( ( next = strchr( this, ' ' ) ) ) *next = 0; if( ( iu = irc_user_by_name( irc, this ) ) && iu->bu && iu->bu->flags & BEE_USER_ONLINE ) { lenleft -= strlen( iu->nick ) + 1; if( lenleft < 0 ) break; strcat( buff, iu->nick ); strcat( buff, " " ); } if( next ) { *next = ' '; this = next + 1; } else { break; } } /* *sigh* */ if( lenleft < 0 ) break; } if( strlen( buff ) > 0 ) buff[strlen(buff)-1] = '\0'; irc_send_num( irc, 303, ":%s", buff ); } static void irc_cmd_watch( irc_t *irc, char **cmd ) { int i; /* Obviously we could also mark a user structure as being watched, but what if the WATCH command is sent right after connecting? The user won't exist yet then... */ for( i = 1; cmd[i]; i ++ ) { char *nick; irc_user_t *iu; if( !cmd[i][0] || !cmd[i][1] ) break; nick = g_strdup( cmd[i] + 1 ); nick_lc( irc, nick ); iu = irc_user_by_name( irc, nick ); if( cmd[i][0] == '+' ) { if( !g_hash_table_lookup( irc->watches, nick ) ) g_hash_table_insert( irc->watches, nick, nick ); if( iu && iu->bu && iu->bu->flags & BEE_USER_ONLINE ) irc_send_num( irc, 604, "%s %s %s %d :%s", iu->nick, iu->user, iu->host, (int) time( NULL ), "is online" ); else irc_send_num( irc, 605, "%s %s %s %d :%s", nick, "*", "*", (int) time( NULL ), "is offline" ); } else if( cmd[i][0] == '-' ) { gpointer okey, ovalue; if( g_hash_table_lookup_extended( irc->watches, nick, &okey, &ovalue ) ) { g_hash_table_remove( irc->watches, okey ); g_free( okey ); irc_send_num( irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching" ); } } } } static void irc_cmd_topic( irc_t *irc, char **cmd ) { irc_channel_t *ic = irc_channel_by_name( irc, cmd[1] ); const char *new = cmd[2]; if( ic == NULL ) { irc_send_num( irc, 403, "%s :No such channel", cmd[1] ); } else if( new ) { if( ic->f->topic == NULL ) irc_send_num( irc, 482, "%s :Can't change this channel's topic", ic->name ); else if( ic->f->topic( ic, new ) ) irc_send_topic( ic, TRUE ); } else { irc_send_topic( ic, FALSE ); } } static void irc_cmd_away( irc_t *irc, char **cmd ) { if( cmd[1] && *cmd[1] ) { char away[strlen(cmd[1])+1]; int i, j; /* Copy away string, but skip control chars. Mainly because Jabber really doesn't like them. */ for( i = j = 0; cmd[1][i]; i ++ ) if( (unsigned char) ( away[j] = cmd[1][i] ) >= ' ' ) j ++; away[j] = '\0'; irc_send_num( irc, 306, ":You're now away: %s", away ); set_setstr( &irc->b->set, "away", away ); } else { irc_send_num( irc, 305, ":Welcome back" ); set_setstr( &irc->b->set, "away", NULL ); } } static void irc_cmd_list( irc_t *irc, char **cmd ) { GSList *l; for( l = irc->channels; l; l = l->next ) { irc_channel_t *ic = l->data; irc_send_num( irc, 322, "%s %d :%s", ic->name, g_slist_length( ic->users ), ic->topic ? : "" ); } irc_send_num( irc, 323, ":%s", "End of /LIST" ); } static void irc_cmd_version( irc_t *irc, char **cmd ) { irc_send_num( irc, 351, "%s-%s. %s :%s/%s ", PACKAGE, BITLBEE_VERSION, irc->root->host, ARCH, CPU ); } static void irc_cmd_completions( irc_t *irc, char **cmd ) { help_t *h; set_t *s; int i; irc_send_msg_raw( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS OK" ); for( i = 0; root_commands[i].command; i ++ ) irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS %s", root_commands[i].command ); for( h = global.help; h; h = h->next ) irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS help %s", h->title ); for( s = irc->b->set; s; s = s->next ) irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS set %s", s->key ); irc_send_msg_raw( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS END" ); } static void irc_cmd_rehash( irc_t *irc, char **cmd ) { if( global.conf->runmode == RUNMODE_INETD ) ipc_master_cmd_rehash( NULL, NULL ); else ipc_to_master( cmd ); irc_send_num( irc, 382, "%s :Rehashing", global.conf_file ); } static const command_t irc_commands[] = { { "pass", 1, irc_cmd_pass, 0 }, { "user", 4, irc_cmd_user, IRC_CMD_PRE_LOGIN }, { "nick", 1, irc_cmd_nick, 0 }, { "quit", 0, irc_cmd_quit, 0 }, { "ping", 0, irc_cmd_ping, 0 }, { "pong", 0, irc_cmd_pong, IRC_CMD_LOGGED_IN }, { "join", 1, irc_cmd_join, IRC_CMD_LOGGED_IN }, { "names", 1, irc_cmd_names, IRC_CMD_LOGGED_IN }, { "part", 1, irc_cmd_part, IRC_CMD_LOGGED_IN }, { "whois", 1, irc_cmd_whois, IRC_CMD_LOGGED_IN }, { "whowas", 1, irc_cmd_whowas, IRC_CMD_LOGGED_IN }, { "motd", 0, irc_cmd_motd, IRC_CMD_LOGGED_IN }, { "mode", 1, irc_cmd_mode, IRC_CMD_LOGGED_IN }, { "who", 0, irc_cmd_who, IRC_CMD_LOGGED_IN }, { "privmsg", 1, irc_cmd_privmsg, IRC_CMD_LOGGED_IN }, { "notice", 1, irc_cmd_notice, IRC_CMD_LOGGED_IN }, { "nickserv", 1, irc_cmd_nickserv, IRC_CMD_LOGGED_IN }, { "ns", 1, irc_cmd_nickserv, IRC_CMD_LOGGED_IN }, { "away", 0, irc_cmd_away, IRC_CMD_LOGGED_IN }, { "version", 0, irc_cmd_version, IRC_CMD_LOGGED_IN }, { "completions", 0, irc_cmd_completions, IRC_CMD_LOGGED_IN }, { "userhost", 1, irc_cmd_userhost, IRC_CMD_LOGGED_IN }, { "ison", 1, irc_cmd_ison, IRC_CMD_LOGGED_IN }, { "watch", 1, irc_cmd_watch, IRC_CMD_LOGGED_IN }, { "invite", 2, irc_cmd_invite, IRC_CMD_LOGGED_IN }, { "topic", 1, irc_cmd_topic, IRC_CMD_LOGGED_IN }, { "oper", 2, irc_cmd_oper, IRC_CMD_LOGGED_IN }, { "list", 0, irc_cmd_list, IRC_CMD_LOGGED_IN }, { "die", 0, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, { "deaf", 0, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, { "wallops", 1, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, { "wall", 1, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, { "rehash", 0, irc_cmd_rehash, IRC_CMD_OPER_ONLY }, { "restart", 0, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, { "kill", 2, NULL, IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER }, { NULL } }; void irc_exec( irc_t *irc, char *cmd[] ) { int i, n_arg; if( !cmd[0] ) return; for( i = 0; irc_commands[i].command; i++ ) if( g_strcasecmp( irc_commands[i].command, cmd[0] ) == 0 ) { /* There should be no typo in the next line: */ for( n_arg = 0; cmd[n_arg]; n_arg ++ ); n_arg --; if( irc_commands[i].flags & IRC_CMD_PRE_LOGIN && irc->status & USTATUS_LOGGED_IN ) { irc_send_num( irc, 462, ":Only allowed before logging in" ); } else if( irc_commands[i].flags & IRC_CMD_LOGGED_IN && !( irc->status & USTATUS_LOGGED_IN ) ) { irc_send_num( irc, 451, ":Register first" ); } else if( irc_commands[i].flags & IRC_CMD_OPER_ONLY && !strchr( irc->umode, 'o' ) ) { irc_send_num( irc, 481, ":Permission denied - You're not an IRC operator" ); } else if( n_arg < irc_commands[i].required_parameters ) { irc_send_num( irc, 461, "%s :Need more parameters", cmd[0] ); } else if( irc_commands[i].flags & IRC_CMD_TO_MASTER ) { /* IPC doesn't make sense in inetd mode, but the function will catch that. */ ipc_to_master( cmd ); } else { irc_commands[i].execute( irc, cmd ); } return; } if( irc->status & USTATUS_LOGGED_IN ) irc_send_num( irc, 421, "%s :Unknown command", cmd[0] ); } bitlbee-3.2.1/ipc.h0000644000175000017500000000434112245474076013444 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2010 Wilmer van der Gaast and others * \********************************************************************/ /* IPC - communication between BitlBee processes */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" struct bitlbee_child { pid_t pid; int ipc_fd; gint ipc_inpa; char *host; char *nick; char *realname; char *password; /* For takeovers: */ struct bitlbee_child *to_child; int to_fd; }; gboolean ipc_master_read( gpointer data, gint source, b_input_condition cond ); gboolean ipc_child_read( gpointer data, gint source, b_input_condition cond ); void ipc_master_free_one( struct bitlbee_child *child ); void ipc_master_free_fd( int fd ); void ipc_master_free_all(); void ipc_child_disable(); gboolean ipc_child_identify( irc_t *irc ); void ipc_to_master( char **cmd ); void ipc_to_master_str( char *format, ... ) G_GNUC_PRINTF( 1, 2 ); void ipc_to_children( char **cmd ); void ipc_to_children_str( char *format, ... ) G_GNUC_PRINTF( 1, 2 ); /* We need this function in inetd mode, so let's just make it non-static. */ void ipc_master_cmd_rehash( irc_t *data, char **cmd ); char *ipc_master_save_state(); int ipc_master_load_state( char *statefile ); int ipc_master_listen_socket(); extern GSList *child_list; bitlbee-3.2.1/nick.c0000644000175000017500000002433312245474076013613 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* Some stuff to fetch, save and handle nicknames for your buddies */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" /* Character maps, _lc_[x] == _uc_[x] (but uppercase), according to the RFC's. With one difference, we allow dashes. These are used to do uc/lc conversions and strip invalid chars. */ static char *nick_lc_chars = "0123456789abcdefghijklmnopqrstuvwxyz{}^`-_|"; static char *nick_uc_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[]~`-_\\"; /* Store handles in lower case and strip spaces, because AIM is braindead. */ static char *clean_handle( const char *orig ) { char *new = g_malloc( strlen( orig ) + 1 ); int i = 0; do { if (*orig != ' ') new[i++] = tolower( *orig ); } while (*(orig++)); return new; } void nick_set_raw( account_t *acc, const char *handle, const char *nick ) { char *store_handle, *store_nick = g_malloc( MAX_NICK_LENGTH + 1 ); irc_t *irc = (irc_t *) acc->bee->ui_data; store_handle = clean_handle( handle ); store_nick[MAX_NICK_LENGTH] = '\0'; strncpy( store_nick, nick, MAX_NICK_LENGTH ); nick_strip( irc, store_nick ); g_hash_table_replace( acc->nicks, store_handle, store_nick ); } void nick_set( bee_user_t *bu, const char *nick ) { nick_set_raw( bu->ic->acc, bu->handle, nick ); } char *nick_get( bee_user_t *bu ) { static char nick[MAX_NICK_LENGTH+1]; char *store_handle, *found_nick; irc_t *irc = (irc_t *) bu->bee->ui_data; memset( nick, 0, MAX_NICK_LENGTH + 1 ); store_handle = clean_handle( bu->handle ); /* Find out if we stored a nick for this person already. If not, try to generate a sane nick automatically. */ if( ( found_nick = g_hash_table_lookup( bu->ic->acc->nicks, store_handle ) ) ) { strncpy( nick, found_nick, MAX_NICK_LENGTH ); } else if( ( found_nick = nick_gen( bu ) ) ) { strncpy( nick, found_nick, MAX_NICK_LENGTH ); g_free( found_nick ); } else { /* Keep this fallback since nick_gen() can return NULL in some cases. */ char *s; g_snprintf( nick, MAX_NICK_LENGTH, "%s", bu->handle ); if( ( s = strchr( nick, '@' ) ) ) while( *s ) *(s++) = 0; nick_strip( irc, nick ); if( set_getbool( &bu->bee->set, "lcnicks" ) ) nick_lc( irc, nick ); } g_free( store_handle ); /* Make sure the nick doesn't collide with an existing one by adding underscores and that kind of stuff, if necessary. */ nick_dedupe( bu, nick ); return nick; } char *nick_gen( bee_user_t *bu ) { gboolean ok = FALSE; /* Set to true once the nick contains something unique. */ GString *ret = g_string_sized_new( MAX_NICK_LENGTH + 1 ); char *rets; irc_t *irc = (irc_t *) bu->bee->ui_data; char *fmt = set_getstr( &bu->ic->acc->set, "nick_format" ) ? : set_getstr( &bu->bee->set, "nick_format" ); while( fmt && *fmt && ret->len < MAX_NICK_LENGTH ) { char *part = NULL, chop = '\0', *asc = NULL, *s; int len = INT_MAX; if( *fmt != '%' ) { g_string_append_c( ret, *fmt ); fmt ++; continue; } fmt ++; while( *fmt ) { /* -char means chop off everything from char */ if( *fmt == '-' ) { chop = fmt[1]; if( chop == '\0' ) { g_string_free( ret, TRUE ); return NULL; } fmt += 2; } else if( isdigit( *fmt ) ) { len = 0; /* Grab a number. */ while( isdigit( *fmt ) ) len = len * 10 + ( *(fmt++) - '0' ); } else if( g_strncasecmp( fmt, "nick", 4 ) == 0 ) { part = bu->nick ? : bu->handle; fmt += 4; ok |= TRUE; break; } else if( g_strncasecmp( fmt, "handle", 6 ) == 0 ) { part = bu->handle; fmt += 6; ok |= TRUE; break; } else if( g_strncasecmp( fmt, "full_name", 9 ) == 0 ) { part = bu->fullname; fmt += 9; ok |= part && *part; break; } else if( g_strncasecmp( fmt, "first_name", 10 ) == 0 ) { part = bu->fullname; fmt += 10; ok |= part && *part; chop = ' '; break; } else if( g_strncasecmp( fmt, "group", 5 ) == 0 ) { part = bu->group ? bu->group->name : NULL; fmt += 5; break; } else if( g_strncasecmp( fmt, "account", 7 ) == 0 ) { part = bu->ic->acc->tag; fmt += 7; break; } else { g_string_free( ret, TRUE ); return NULL; } } if( !part ) continue; /* Credits to Josay_ in #bitlbee for this idea. //TRANSLIT should do lossy/approximate conversions, so letters with accents don't just get stripped. Note that it depends on LC_CTYPE being set to something other than C/POSIX. */ if( !( irc && irc->status & IRC_UTF8_NICKS ) ) part = asc = g_convert_with_fallback( part, -1, "ASCII//TRANSLIT", "UTF-8", "", NULL, NULL, NULL ); if( chop && ( s = strchr( part, chop ) ) ) len = MIN( len, s - part ); if( part ) { if( len < INT_MAX ) g_string_append_len( ret, part, len ); else g_string_append( ret, part ); } g_free( asc ); } rets = g_string_free( ret, FALSE ); if( ok && rets && *rets ) { nick_strip( irc, rets ); rets[MAX_NICK_LENGTH] = '\0'; return rets; } g_free( rets ); return NULL; } void nick_dedupe( bee_user_t *bu, char nick[MAX_NICK_LENGTH+1] ) { irc_t *irc = (irc_t*) bu->bee->ui_data; int inf_protection = 256; irc_user_t *iu; /* Now, find out if the nick is already in use at the moment, and make subtle changes to make it unique. */ while( !nick_ok( irc, nick ) || ( ( iu = irc_user_by_name( irc, nick ) ) && iu->bu != bu ) ) { if( strlen( nick ) < ( MAX_NICK_LENGTH - 1 ) ) { nick[strlen(nick)+1] = 0; nick[strlen(nick)] = '_'; } else { nick[0] ++; } if( inf_protection-- == 0 ) { g_snprintf( nick, MAX_NICK_LENGTH + 1, "xx%x", rand() ); irc_rootmsg( irc, "Warning: Something went wrong while trying " "to generate a nickname for contact %s on %s.", bu->handle, bu->ic->acc->tag ); irc_rootmsg( irc, "This might be a bug in BitlBee, or the result " "of a faulty nick_format setting. Will use %s " "instead.", nick ); break; } } } /* Just check if there is a nickname set for this buddy or if we'd have to generate one. */ int nick_saved( bee_user_t *bu ) { char *store_handle, *found; store_handle = clean_handle( bu->handle ); found = g_hash_table_lookup( bu->ic->acc->nicks, store_handle ); g_free( store_handle ); return found != NULL; } void nick_del( bee_user_t *bu ) { g_hash_table_remove( bu->ic->acc->nicks, bu->handle ); } void nick_strip( irc_t *irc, char *nick ) { int len = 0; if( irc && ( irc->status & IRC_UTF8_NICKS ) ) { gunichar c; char *p = nick, *n, tmp[strlen(nick)+1]; while( p && *p ) { c = g_utf8_get_char_validated( p, -1 ); n = g_utf8_find_next_char( p, NULL ); if( ( c < 0x7f && !( strchr( nick_lc_chars, c ) || strchr( nick_uc_chars, c ) ) ) || !g_unichar_isgraph( c ) ) { strcpy( tmp, n ); strcpy( p, tmp ); } else p = n; } if( p ) len = p - nick; } else { int i; for( i = len = 0; nick[i] && len < MAX_NICK_LENGTH; i++ ) { if( strchr( nick_lc_chars, nick[i] ) || strchr( nick_uc_chars, nick[i] ) ) { nick[len] = nick[i]; len++; } } } if( isdigit( nick[0] ) ) { char *orig; /* First character of a nick can't be a digit, so insert an underscore if necessary. */ orig = g_strdup( nick ); g_snprintf( nick, MAX_NICK_LENGTH, "_%s", orig ); g_free( orig ); len ++; } while( len <= MAX_NICK_LENGTH ) nick[len++] = '\0'; } gboolean nick_ok( irc_t *irc, const char *nick ) { const char *s; /* Empty/long nicks are not allowed, nor numbers at [0] */ if( !*nick || isdigit( nick[0] ) || strlen( nick ) > MAX_NICK_LENGTH ) return 0; if( irc && ( irc->status & IRC_UTF8_NICKS ) ) { gunichar c; const char *p = nick, *n; while( p && *p ) { c = g_utf8_get_char_validated( p, -1 ); n = g_utf8_find_next_char( p, NULL ); if( ( c < 0x7f && !( strchr( nick_lc_chars, c ) || strchr( nick_uc_chars, c ) ) ) || !g_unichar_isgraph( c ) ) { return FALSE; } p = n; } } else { for( s = nick; *s; s ++ ) if( !strchr( nick_lc_chars, *s ) && !strchr( nick_uc_chars, *s ) ) return FALSE; } return TRUE; } int nick_lc( irc_t *irc, char *nick ) { static char tab[128] = { 0 }; int i; if( tab['A'] == 0 ) for( i = 0; nick_lc_chars[i]; i ++ ) { tab[(int)nick_uc_chars[i]] = nick_lc_chars[i]; tab[(int)nick_lc_chars[i]] = nick_lc_chars[i]; } if( irc && ( irc->status & IRC_UTF8_NICKS ) ) { gchar *down = g_utf8_strdown( nick, -1 ); if( strlen( down ) > strlen( nick ) ) { /* Well crap. Corrupt it if we have to. */ down[strlen(nick)] = '\0'; } strcpy( nick, down ); g_free( down ); } for( i = 0; nick[i]; i ++ ) if( nick[i] < 0x7f ) nick[i] = tab[(int)nick[i]]; return nick_ok( irc, nick ); } int nick_cmp( irc_t *irc, const char *a, const char *b ) { char aa[1024] = "", bb[1024] = ""; strncpy( aa, a, sizeof( aa ) - 1 ); strncpy( bb, b, sizeof( bb ) - 1 ); if( nick_lc( irc, aa ) && nick_lc( irc, bb ) ) { return( strcmp( aa, bb ) ); } else { return( -1 ); /* Hmm... Not a clear answer.. :-/ */ } } bitlbee-3.2.1/log.h0000644000175000017500000000353512245474076013456 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2005 Wilmer van der Gaast and others * \********************************************************************/ /* Logging services for the bee */ /* 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 _LOG_H #define _LOG_H typedef enum { LOGLVL_INFO, LOGLVL_WARNING, LOGLVL_ERROR, #ifdef DEBUG LOGLVL_DEBUG, #endif } loglvl_t; typedef enum { LOGOUTPUT_NULL, LOGOUTPUT_IRC, LOGOUTPUT_SYSLOG, LOGOUTPUT_CONSOLE, } logoutput_t; typedef struct log_t { void (*error)(int level, const char *logmessage); void (*warning)(int level, const char *logmessage); void (*informational)(int level, const char *logmessage); #ifdef DEBUG void (*debug)(int level, const char *logmessage); #endif } log_t; void log_init(void); void log_link(int level, int output); void log_message(int level, const char *message, ...) G_GNUC_PRINTF( 2, 3 ); void log_error(const char *functionname); #endif bitlbee-3.2.1/utils/0000755000175000017500000000000012245474076013656 5ustar wilmerwilmerbitlbee-3.2.1/utils/README0000644000175000017500000000233012245474076014534 0ustar wilmerwilmerThis directory contains tiny additional programs which you might just like or need to run BitlBee: * bitlbeed.c If you want to run BitlBee on a machine you don't have root access to, this utility will help you. Compiling it is easy: 'gcc bitlbeed.c -o bitlbeed', you don't need any special flags. Use 'bitlbeed -h' to get more help. For example, 'bitlbeed -p6669 -n1 /home/wilmer/bin/bitlbee' will start listening on TCP port 6669 (on any interface, you might not want that!) and connect the specified BitlBee program to this socket as soon as someone connects. The -n1 makes sure only one person can be connected at once. Of course this program can be used for other programs too, not just BitlBee. * convert_purple.py Converts libpurple configs into something BitlBee can use, so you don't have to re-add all your accounts by hand. * BitlBee-specific Irssi scripts for: tab completion, typing notifica- tions, auto-away and more, by Tijmen Ruizendaal . There are too many scripts to include them all with BitlBee (and keep them up-to-date), so you should get them from Tijmen's site: http://the-timing.nl/stuff/irssi-bitlbee Please do send your sources if you write anything useful for the Bee! bitlbee-3.2.1/utils/convert_purple.py0000755000175000017500000000643412245474076017311 0ustar wilmerwilmer#!/usr/bin/python # # Part of BitlBee. Reads a libpurple accounts.xml file and generates some # commands/XML that BitlBee understands. For easy migration from Pidgin/ # Finch/whatever to BitlBee, be it a public server or your own. # # Licensed under the GPL2 like the rest of BitlBee. # # Copyright 2010 Wilmer van der Gaast # import getopt import getpass import os import subprocess import sys import xml.dom.minidom BITLBEE = '/usr/sbin/bitlbee' def parse_purple(f): protomap = { 'msn-pecan': 'msn', 'aim': 'oscar', 'icq': 'oscar', } supported = ('msn', 'jabber', 'oscar', 'yahoo', 'twitter') accs = list() if os.path.isdir(f): f = f + '/accounts.xml' xt = xml.dom.minidom.parse(f) for acc in xt.getElementsByTagName('account')[1:]: protocol = acc.getElementsByTagName('protocol')[0].firstChild.wholeText name = acc.getElementsByTagName('name')[0].firstChild.wholeText try: password = acc.getElementsByTagName('password')[0].firstChild.wholeText except IndexError: password = '' if protocol.startswith('prpl-'): protocol = protocol[5:] if name.endswith('/'): name = name[:-1] if protocol in protomap: protocol = protomap[protocol] if protocol not in supported: print 'Warning: protocol probably not supported by BitlBee: ' + protocol accs.append((protocol, name, password)) return accs def print_commands(accs): print 'To copy all your Pidgin accounts to BitlBee, just copy-paste the following' print 'commands into your &bitlbee channel:' print for acc in accs: print 'account add %s %s "%s"' % acc def bitlbee_x(*args): bb = subprocess.Popen([BITLBEE, '-x'] + list(args), stdout=subprocess.PIPE) return bb.stdout.read().strip() def print_xml(accs): try: bitlbee_x('hash', 'blaataap') except: print "Can't find/use BitlBee binary. It has to be a 1.2.5 binary or higher." print usage() print 'BitlBee .xml files are encrypted using the identify password. Please type your' print 'preferred identify password.' user = getpass.getuser() pwd = getpass.getpass() root = xml.dom.minidom.Element('user') root.setAttribute('nick', user) root.setAttribute('password', bitlbee_x('hash', pwd)) root.setAttribute('version', '1') for acc in accs: accx = xml.dom.minidom.Element('account') accx.setAttribute('protocol', acc[0]) accx.setAttribute('handle', acc[1]) accx.setAttribute('password', bitlbee_x('enc', pwd, acc[2])) accx.setAttribute('autoconnect', '1') root.appendChild(accx) print print 'Write the following XML data to a file called %s.xml (rename it if' % user.lower() print 'you want to use a different nickname). It should be in the directory where' print 'your BitlBee account files are stored (most likely /var/lib/bitlbee).' print print root.toprettyxml() def usage(): print 'Usage: %s [-f ] [-b ] [-x]' % sys.argv[0] print print 'Generates "account add" commands by default. -x generates a .xml file instead.' print 'The accounts file can normally be found in ~/.purple/.' sys.exit(os.EX_USAGE) try: flags = dict(getopt.getopt(sys.argv[1:], 'f:b:x')[0]) except getopt.GetoptError: usage() if '-f' not in flags: usage() if '-b' in flags: BITLBEE = flags['-b'] parsed = parse_purple(flags['-f']) if '-x' in flags: print_xml(parsed) else: print_commands(parsed) bitlbee-3.2.1/utils/bitlbee-ctl.pl0000755000175000017500000000200112245474076016375 0ustar wilmerwilmer#!/usr/bin/perl # Simple front-end to BitlBee's administration commands # Copyright (C) 2006 Jelmer Vernooij use IO::Socket; use Getopt::Long; use strict; use warnings; my $opt_help; my $opt_socketfile = "/var/run/bitlbee"; sub ShowHelp { print "bitlbee-ctl.pl [options] command ... Available options: --ipc-socket=SOCKET Override path to IPC socket [$opt_socketfile] --help Show this help message Available commands: die "; exit (0); } GetOptions ( 'help|h|?' => \&ShowHelp, 'ipc-socket=s' => \$opt_socketfile ) or exit(1); my $client = IO::Socket::UNIX->new(Peer => $opt_socketfile, Type => SOCK_STREAM, Timeout => 10); if (not $client) { print "Error connecting to $opt_socketfile: $@\n"; exit(1); } my $cmd = shift @ARGV; if (not defined($cmd)) { print "Usage: bitlbee-ctl.pl [options] command ...\n"; exit(1); } if ($cmd eq "die") { $client->send("DIE\r\n"); } else { print "No such command: $cmd\n"; exit(1); } $client->close(); bitlbee-3.2.1/utils/bitlbeed.c0000644000175000017500000002630212245474076015577 0ustar wilmerwilmer/****************************************************************\ * * * bitlbeed.c * * * * A tiny daemon to allow you to run The Bee as a non-root user * * (without access to /etc/inetd.conf or whatever) * * * * Copyright 2002-2004 Wilmer van der Gaast * * * * Licensed under the GNU General Public License * * * * Modified by M. Dennis, 20040627 * \****************************************************************/ /* ChangeLog: 2004-06-27: Added support for AF_LOCAL (UNIX domain) sockets Renamed log to do_log to fix conflict warning Changed protocol to 0 (6 is not supported?) Added error check for socket() Added a no-fork (debug) mode 2004-05-15: Added rate limiting 2003-12-26: Added the SO_REUSEADDR sockopt, logging and CPU-time limiting for clients using setrlimit(), fixed the execv() call 2002-11-29: Added the timeout so old child processes clean up faster 2002-11-28: First version */ #define SELECT_TIMEOUT 2 #define MAX_LOG_LEN 128 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct settings { char local; char debug; char *interface; signed int port; unsigned char max_conn; int seconds; int rate_seconds; int rate_times; int rate_ignore; char **call; } settings_t; typedef struct ipstats { unsigned int ip; time_t rate_start; int rate_times; time_t rate_ignore; struct ipstats *next; } ipstats_t; FILE *logfile; ipstats_t *ipstats; settings_t *set_load( int argc, char *argv[] ); void do_log( char *fmt, ... ); ipstats_t *ip_get( char *ip_txt ); int main( int argc, char *argv[] ) { const int rebind_on = 1; settings_t *set; int serv_fd, serv_len; struct sockaddr_in serv_addr; struct sockaddr_un local_addr; pid_t st; if( !( set = set_load( argc, argv ) ) ) return( 1 ); if( !logfile ) if( !( logfile = fopen( "/dev/null", "w" ) ) ) { perror( "fopen" ); return( 1 ); } fcntl( fileno( logfile ), F_SETFD, FD_CLOEXEC ); if( set->local ) serv_fd = socket( PF_LOCAL, SOCK_STREAM, 0 ); else serv_fd = socket( PF_INET, SOCK_STREAM, 0 ); if( serv_fd < 0 ) { perror( "socket" ); return( 1 ); } setsockopt( serv_fd, SOL_SOCKET, SO_REUSEADDR, &rebind_on, sizeof( rebind_on ) ); fcntl( serv_fd, F_SETFD, FD_CLOEXEC ); if (set->local) { local_addr.sun_family = AF_LOCAL; strncpy( local_addr.sun_path, set->interface, sizeof( local_addr.sun_path ) - 1 ); local_addr.sun_path[sizeof( local_addr.sun_path ) - 1] = '\0'; /* warning - don't let untrusted users run this program if it is setuid/setgid! Arbitrary file deletion risk! */ unlink( set->interface ); if( bind( serv_fd, (struct sockaddr *) &local_addr, SUN_LEN( &local_addr ) ) != 0 ) { perror( "bind" ); return( 1 ); } chmod( set->interface, S_IRWXO|S_IRWXG|S_IRWXU ); } else { serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr( set->interface ); serv_addr.sin_port = htons( set->port ); serv_len = sizeof( serv_addr ); if( bind( serv_fd, (struct sockaddr *) &serv_addr, serv_len ) != 0 ) { perror( "bind" ); return( 1 ); } } if( listen( serv_fd, set->max_conn ) != 0 ) { perror( "listen" ); return( 1 ); } if ( ! set->debug ) { st = fork(); if( st < 0 ) { perror( "fork" ); return( 1 ); } else if( st > 0 ) { return( 0 ); } setsid(); close( 0 ); close( 1 ); close( 2 ); } do_log( "bitlbeed running" ); /* The Daemon */ while( 1 ) { int cli_fd, cli_len, i, st; struct sockaddr_in cli_addr; struct sockaddr_un cli_local; ipstats_t *ip; char *cli_txt; pid_t child; static int running = 0; fd_set rd; struct timeval tm; /* accept() only returns after someone connects. To clean up old processes (by running waitpid()) it's better to use select() with a timeout. */ FD_ZERO( &rd ); FD_SET( serv_fd, &rd ); tm.tv_sec = SELECT_TIMEOUT; tm.tv_usec = 0; if( select( serv_fd + 1, &rd, NULL, NULL, &tm ) > 0 ) { if (set->local) { cli_len = SUN_LEN( &cli_local ); cli_fd = accept( serv_fd, (struct sockaddr *) &cli_local, &cli_len ); cli_txt = "127.0.0.1"; } else { cli_len = sizeof( cli_addr ); cli_fd = accept( serv_fd, (struct sockaddr *) &cli_addr, &cli_len ); cli_txt = inet_ntoa( cli_addr.sin_addr ); } ip = ip_get( cli_txt ); if( set->rate_times == 0 || time( NULL ) > ip->rate_ignore ) { /* We want this socket on stdout and stderr too! */ dup( cli_fd ); dup( cli_fd ); if( ( child = fork() ) == 0 ) { if( set->seconds ) { struct rlimit li; li.rlim_cur = (rlim_t) set->seconds; li.rlim_max = (rlim_t) set->seconds + 1; setrlimit( RLIMIT_CPU, &li ); } execv( set->call[0], set->call ); do_log( "Error while executing %s!", set->call[0] ); return( 1 ); } running ++; close( 0 ); close( 1 ); close( 2 ); do_log( "Started child process for client %s (PID=%d), got %d clients now", cli_txt, child, running ); if( time( NULL ) < ( ip->rate_start + set->rate_seconds ) ) { ip->rate_times ++; if( ip->rate_times >= set->rate_times ) { do_log( "Client %s crossed the limit; ignoring for the next %d seconds", cli_txt, set->rate_ignore ); ip->rate_ignore = time( NULL ) + set->rate_ignore; ip->rate_start = 0; } } else { ip->rate_start = time( NULL ); ip->rate_times = 1; } } else { do_log( "Ignoring connection from %s", cli_txt ); close( cli_fd ); } } /* If the max. number of connection is reached, don't accept new connections until one expires -> Not always WNOHANG Cleaning up child processes is a good idea anyway... :-) */ while( ( i = waitpid( 0, &st, ( ( running < set->max_conn ) || ( set->max_conn == 0 ) ) ? WNOHANG : 0 ) ) > 0 ) { running --; if( WIFEXITED( st ) ) { do_log( "Child process (PID=%d) exited normally with status %d. %d Clients left now", i, WEXITSTATUS( st ), running ); } else if( WIFSIGNALED( st ) ) { do_log( "Child process (PID=%d) killed by signal %d. %d Clients left now", i, WTERMSIG( st ), running ); } else { /* Should not happen AFAIK... */ do_log( "Child process (PID=%d) stopped for unknown reason, %d clients left now", i, running ); } } } return( 0 ); } settings_t *set_load( int argc, char *argv[] ) { settings_t *set; int opt, i; set = malloc( sizeof( settings_t ) ); memset( set, 0, sizeof( settings_t ) ); set->interface = NULL; /* will be filled in later */ set->port = 6667; set->local = 0; set->debug = 0; set->rate_seconds = 600; set->rate_times = 5; set->rate_ignore = 900; while( ( opt = getopt( argc, argv, "i:p:n:t:l:r:hud" ) ) >= 0 ) { if( opt == 'i' ) { set->interface = strdup( optarg ); } else if( opt == 'p' ) { if( ( sscanf( optarg, "%d", &i ) != 1 ) || ( i <= 0 ) || ( i > 65535 ) ) { fprintf( stderr, "Invalid port number: %s\n", optarg ); return( NULL ); } set->port = i; } else if( opt == 'n' ) { if( ( sscanf( optarg, "%d", &i ) != 1 ) || ( i < 0 ) ) { fprintf( stderr, "Invalid number of connections: %s\n", optarg ); return( NULL ); } set->max_conn = i; } else if( opt == 't' ) { if( ( sscanf( optarg, "%d", &i ) != 1 ) || ( i < 0 ) || ( i > 600 ) ) { fprintf( stderr, "Invalid number of seconds: %s\n", optarg ); return( NULL ); } set->seconds = i; } else if( opt == 'l' ) { if( !( logfile = fopen( optarg, "a" ) ) ) { perror( "fopen" ); fprintf( stderr, "Error opening logfile, giving up.\n" ); return( NULL ); } setbuf( logfile, NULL ); } else if( opt == 'r' ) { if( sscanf( optarg, "%d,%d,%d", &set->rate_seconds, &set->rate_times, &set->rate_ignore ) != 3 ) { fprintf( stderr, "Invalid argument to -r.\n" ); return( NULL ); } } else if( opt == 'u' ) set->local = 1; else if( opt == 'd' ) set->debug = 1; else if( opt == 'h' ) { printf( "Usage: %s [-i ] [-p ] [-n ] [-r x,y,z] ...\n" " ... \n" "A simple inetd-like daemon to have a program listening on a TCP socket without\n" "needing root access to the machine\n" "\n" " -i Specify the interface (by IP address) to listen on.\n" " (Default: 0.0.0.0 (any interface))\n" " -p Port number to listen on. (Default: 6667)\n" " -n Maximum number of connections. (Default: 0 (unlimited))\n" " -t Specify the maximum number of CPU seconds per process.\n" " (Default: 0 (unlimited))\n" " -l Specify a logfile. (Default: none)\n" " -r Rate limiting: Ignore a host for z seconds when it connects for more\n" " than y times in x seconds. (Default: 600,5,900. Disable: 0,0,0)\n" " -u Use a local socket, by default /tmp/bitlbee (override with -i )\n" " -d Don't fork for listening (for debugging purposes)\n" " -h This information\n", argv[0] ); return( NULL ); } } if( set->interface == NULL ) set->interface = (set->local) ? "/tmp/bitlbee" : "0.0.0.0"; if( optind == argc ) { fprintf( stderr, "Missing program parameter!\n" ); return( NULL ); } /* The remaining arguments are the executable and its arguments */ set->call = malloc( ( argc - optind + 1 ) * sizeof( char* ) ); memcpy( set->call, argv + optind, sizeof( char* ) * ( argc - optind ) ); set->call[argc-optind] = NULL; return( set ); } void do_log( char *fmt, ... ) { va_list params; char line[MAX_LOG_LEN]; time_t tm; int l; memset( line, 0, MAX_LOG_LEN ); tm = time( NULL ); strcpy( line, ctime( &tm ) ); l = strlen( line ); line[l-1] = ' '; va_start( params, fmt ); vsnprintf( line + l, MAX_LOG_LEN - l - 2, fmt, params ); va_end( params ); strcat( line, "\n" ); fprintf( logfile, "%s", line ); } ipstats_t *ip_get( char *ip_txt ) { unsigned int ip; ipstats_t *l; int p[4]; sscanf( ip_txt, "%d.%d.%d.%d", p + 0, p + 1, p + 2, p + 3 ); ip = ( p[0] << 24 ) | ( p[1] << 16 ) | ( p[2] << 8 ) | ( p[3] ); for( l = ipstats; l; l = l->next ) { if( l->ip == ip ) return( l ); } if( ipstats ) { for( l = ipstats; l->next; l = l->next ); l->next = malloc( sizeof( ipstats_t ) ); l = l->next; } else { l = malloc( sizeof( ipstats_t ) ); ipstats = l; } memset( l, 0, sizeof( ipstats_t ) ); l->ip = ip; return( l ); } bitlbee-3.2.1/.vimrc0000644000175000017500000000043212245474076013636 0ustar wilmerwilmer" vim can use per directory configuration files. To enable that neat feature only two little lines are needed in your ~/.vimrc: " set exrc " enable per-directory .vimrc files " set secure " disable unsafe commands in local .vimrc files set ts=8 set noexpandtab bitlbee-3.2.1/otr.h0000644000175000017500000000450412245474076013476 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2008 Wilmer van der Gaast and others * \********************************************************************/ /* OTR support (cf. http://www.cypherpunks.ca/otr/) 2008, Sven Moritz Hallberg (c) and funded by stonedcoder.org */ /* 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 BITLBEE_PROTOCOLS_OTR_H #define BITLBEE_PROTOCOLS_OTR_H #include "bitlbee.h" // forward decls to avoid mutual dependencies struct irc; struct im_connection; struct account; #include #include #include /* representing a keygen job */ typedef struct kg { char *accountname; char *protocol; struct kg *next; } kg_t; /* struct to encapsulate our book keeping stuff */ typedef struct otr { OtrlUserState us; pid_t keygen; /* pid of keygen slave (0 if none) */ FILE *to; /* pipe to keygen slave */ FILE *from; /* pipe from keygen slave */ /* active keygen job (NULL if none) */ char *sent_accountname; char *sent_protocol; /* keygen jobs waiting to be sent to slave */ kg_t *todo; } otr_t; /* called from main() */ void otr_init(void); /* called by storage_* functions */ void otr_load(struct irc *irc); void otr_save(struct irc *irc); void otr_remove(const char *nick); void otr_rename(const char *onick, const char *nnick); /* called from account_add() */ int otr_check_for_key(struct account *a); #endif bitlbee-3.2.1/COPYING0000644000175000017500000004311012245474076013550 0ustar wilmerwilmer GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. bitlbee-3.2.1/init/0000755000175000017500000000000012245474076013461 5ustar wilmerwilmerbitlbee-3.2.1/init/bitlbee.socket0000644000175000017500000000016512245474076016303 0ustar wilmerwilmer[Unit] Conflicts=bitlbee.service [Socket] ListenStream=127.0.0.1:6667 Accept=yes [Install] WantedBy=sockets.target bitlbee-3.2.1/init/bitlbee@.service.in0000644000175000017500000000022112245474076017151 0ustar wilmerwilmer[Unit] Description=BitlBee Per-Connection Server After=syslog.target [Service] ExecStart=@sbindir@/bitlbee -I StandardInput=socket User=bitlbee bitlbee-3.2.1/init/bitlbee.service.in0000644000175000017500000000022112245474076017051 0ustar wilmerwilmer[Unit] Description=BitlBee IRC/IM gateway After=syslog.target [Service] ExecStart=@sbindir@/bitlbee -F -n [Install] WantedBy=multi-user.target bitlbee-3.2.1/protocols/0000755000175000017500000000000012245477444014544 5ustar wilmerwilmerbitlbee-3.2.1/protocols/ft.h0000644000175000017500000001335012245474076015326 0ustar wilmerwilmer/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2006 Marijn Kruisselbrink and others * \********************************************************************/ /* Generic file transfer header */ /* 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 _FT_H #define _FT_H /* * One buffer is needed for each transfer. The receiver stores a message * in it and gives it to the sender. The sender will stall the receiver * till the buffer has been sent out. */ #define FT_BUFFER_SIZE 2048 typedef enum { FT_STATUS_LISTENING = 1, FT_STATUS_TRANSFERRING = 2, FT_STATUS_FINISHED = 4, FT_STATUS_CANCELED = 8, FT_STATUS_CONNECTING = 16 } file_status_t; /* * This structure holds all irc specific information regarding an incoming (from the point of view of * the irc client) file transfer. New instances of this struct should only be created by calling the * imcb_file_send_start() method, which will initialize most of the fields. The data field and the various * methods are zero-initialized. Instances will automatically be deleted once the transfer is completed, * canceled, or the connection to the irc client has been lost (note that also if only the irc connection * and not the file transfer connection is lost, the file transfer will still be canceled and freed). * * The following (poor ascii-art) diagram illustrates what methods are called for which status-changes: * * /-----------\ /----------\ * -------> | LISTENING | -----------------> | CANCELED | * \-----------/ [canceled,]free \----------/ * | * | accept * V * /------ /-------------\ /------------------------\ * out_of_data | | TRANSFERING | -----------------> | TRANSFERING | CANCELED | * \-----> \-------------/ [canceled,]free \------------------------/ * | * | finished,free * V * /------------------------\ * | TRANSFERING | FINISHED | * \------------------------/ */ typedef struct file_transfer { /* Are we sending something? */ int sending; /* * The current status of this file transfer. */ file_status_t status; /* * file size */ size_t file_size; /* * Number of bytes that have been successfully transferred. */ size_t bytes_transferred; /* * Time started. Used to calculate kb/s. */ time_t started; /* * file name */ char *file_name; /* * A unique local ID for this file transfer. */ unsigned int local_id; /* * IM-protocol specific data associated with this file transfer. */ gpointer data; struct im_connection *ic; /* * Private data. */ gpointer priv; /* * If set, called after succesful connection setup. */ void (*accept) ( struct file_transfer *file ); /* * If set, called when the transfer is canceled or finished. * Subsequently, this structure will be freed. * */ void (*free) ( struct file_transfer *file ); /* * If set, called when the transfer is finished and successful. */ void (*finished) ( struct file_transfer *file ); /* * If set, called when the transfer is canceled. * ( canceled either by the transfer implementation or by * a call to imcb_file_canceled ) */ void (*canceled) ( struct file_transfer *file, char *reason ); /* * called by the sending side to indicate that it is writable. * The callee should check if data is available and call the * function(as seen below) if that is the case. */ gboolean (*write_request) ( struct file_transfer *file ); /* * When sending files, protocols register this function to receive data. * This should only be called once after write_request is called. The caller * should not read more data until write_request is called again. This technique * avoids buffering. */ gboolean (*write) (struct file_transfer *file, char *buffer, unsigned int len ); /* The send buffer associated with this transfer. * Since receivers always wait for a write_request call one is enough. */ char buffer[FT_BUFFER_SIZE]; } file_transfer_t; /* * This starts a file transfer from bitlbee to the user. */ file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *user_nick, char *file_name, size_t file_size ); /* * This should be called by a protocol when the transfer is canceled. Note that * the canceled() and free() callbacks given in file will be called by this function. */ void imcb_file_canceled( struct im_connection *ic, file_transfer_t *file, char *reason ); gboolean imcb_file_recv_start( struct im_connection *ic, file_transfer_t *ft ); void imcb_file_finished( struct im_connection *ic, file_transfer_t *file ); #endif bitlbee-3.2.1/protocols/Makefile0000644000175000017500000000246412245474076016210 0ustar wilmerwilmer########################### ## Makefile for BitlBee ## ## ## ## Copyright 2002 Lintux ## ########################### ### DEFINITIONS -include ../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)protocols/ endif # [SH] Program variables objects = account.o bee.o bee_chat.o bee_ft.o bee_user.o nogaim.o # [SH] The next two lines should contain the directory name (in $(subdirs)) # and the name of the object file, which should be linked into # protocols.o (in $(subdirobjs)). These need to be in order, i.e. the # first object file should be in the first directory. subdirs = $(PROTOCOLS) subdirobjs = $(PROTOOBJS) # Expansion of variables subdirobjs := $(join $(subdirs),$(addprefix /,$(subdirobjs))) LFLAGS += -r # [SH] Phony targets all: protocols.o check: all lcov: check gcov: gcov *.c .PHONY: all clean distclean $(subdirs) clean: $(subdirs) rm -f *.o $(OUTFILE) core distclean: clean $(subdirs) rm -rf .depend $(subdirs): @$(MAKE) -C $@ $(MAKECMDGOALS) ### MAIN PROGRAM protocols.o: $(objects) $(subdirs) @echo '*' Linking protocols.o @$(LD) $(LFLAGS) $(objects) $(subdirobjs) -o protocols.o $(objects): ../Makefile.settings Makefile $(objects): %.o: $(_SRCDIR_)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $(CFLAGS_BITLBEE) $< -o $@ -include .depend/*.d bitlbee-3.2.1/protocols/purple/0000755000175000017500000000000012245477444016053 5ustar wilmerwilmerbitlbee-3.2.1/protocols/purple/Makefile0000644000175000017500000000145312245474076017514 0ustar wilmerwilmer########################### ## Makefile for BitlBee ## ## ## ## Copyright 2002 Lintux ## ########################### ### DEFINITIONS -include ../../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)protocols/purple/ endif # [SH] Program variables objects = ft.o purple.o CFLAGS += -Wall $(PURPLE_CFLAGS) LFLAGS += -r # [SH] Phony targets all: purple_mod.o check: all lcov: check gcov: gcov *.c .PHONY: all clean distclean clean: rm -f *.o core distclean: clean rm -rf .depend ### MAIN PROGRAM $(objects): ../../Makefile.settings Makefile $(objects): %.o: $(_SRCDIR_)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $(CFLAGS_BITLBEE) $< -o $@ purple_mod.o: $(objects) @echo '*' Linking purple_mod.o @$(LD) $(LFLAGS) $(objects) -o purple_mod.o -include .depend/*.d bitlbee-3.2.1/protocols/purple/ft-direct.c0000644000175000017500000001573412245474076020110 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * libpurple module - File transfer stuff * * * * Copyright 2009-2010 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ /* This code tries to do direct file transfers, i.e. without caching the file locally on disk first. Since libpurple can only do this since version 2.6.0 and even then very unreliably (and not with all IM modules), I'm canning this code for now. */ #include "bitlbee.h" #include #include #include struct prpl_xfer_data { PurpleXfer *xfer; file_transfer_t *ft; gint ready_timer; char *buf; int buf_len; }; static file_transfer_t *next_ft; struct im_connection *purple_ic_by_pa( PurpleAccount *pa ); /* Glorious hack: We seem to have to remind at least some libpurple plugins that we're ready because this info may get lost if we give it too early. So just do it ten times a second. :-/ */ static gboolean prplcb_xfer_write_request_cb( gpointer data, gint fd, b_input_condition cond ) { struct prpl_xfer_data *px = data; purple_xfer_ui_ready( px->xfer ); return purple_xfer_get_type( px->xfer ) == PURPLE_XFER_RECEIVE; } static gboolean prpl_xfer_write_request( struct file_transfer *ft ) { struct prpl_xfer_data *px = ft->data; px->ready_timer = b_timeout_add( 100, prplcb_xfer_write_request_cb, px ); return TRUE; } static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len ) { struct prpl_xfer_data *px = ft->data; px->buf = g_memdup( buffer, len ); px->buf_len = len; //purple_xfer_ui_ready( px->xfer ); px->ready_timer = b_timeout_add( 0, prplcb_xfer_write_request_cb, px ); return TRUE; } static void prpl_xfer_accept( struct file_transfer *ft ) { struct prpl_xfer_data *px = ft->data; purple_xfer_request_accepted( px->xfer, NULL ); prpl_xfer_write_request( ft ); } static void prpl_xfer_canceled( struct file_transfer *ft, char *reason ) { struct prpl_xfer_data *px = ft->data; purple_xfer_request_denied( px->xfer ); } static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond ) { PurpleXfer *xfer = data; struct im_connection *ic = purple_ic_by_pa( xfer->account ); struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 ); PurpleBuddy *buddy; const char *who; buddy = purple_find_buddy( xfer->account, xfer->who ); who = buddy ? purple_buddy_get_name( buddy ) : xfer->who; /* TODO(wilmer): After spreading some more const goodness in BitlBee, remove the evil cast below. */ px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size ); px->ft->data = px; px->xfer = data; px->xfer->ui_data = px; px->ft->accept = prpl_xfer_accept; px->ft->canceled = prpl_xfer_canceled; px->ft->write_request = prpl_xfer_write_request; return FALSE; } static void prplcb_xfer_new( PurpleXfer *xfer ) { if( purple_xfer_get_type( xfer ) == PURPLE_XFER_RECEIVE ) { /* This should suppress the stupid file dialog. */ purple_xfer_set_local_filename( xfer, "/tmp/wtf123" ); /* Sadly the xfer struct is still empty ATM so come back after the caller is done. */ b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer ); } else { struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 ); px->ft = next_ft; px->ft->data = px; px->xfer = xfer; px->xfer->ui_data = px; purple_xfer_set_filename( xfer, px->ft->file_name ); purple_xfer_set_size( xfer, px->ft->file_size ); next_ft = NULL; } } static void prplcb_xfer_progress( PurpleXfer *xfer, double percent ) { fprintf( stderr, "prplcb_xfer_dbg 0x%p %f\n", xfer, percent ); } static void prplcb_xfer_dbg( PurpleXfer *xfer ) { fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer ); } static gssize prplcb_xfer_write( PurpleXfer *xfer, const guchar *buffer, gssize size ) { struct prpl_xfer_data *px = xfer->ui_data; gboolean st; fprintf( stderr, "xfer_write %d %d\n", size, px->buf_len ); b_event_remove( px->ready_timer ); px->ready_timer = 0; st = px->ft->write( px->ft, (char*) buffer, size ); if( st && xfer->bytes_remaining == size ) imcb_file_finished( px->ft ); return st ? size : 0; } gssize prplcb_xfer_read( PurpleXfer *xfer, guchar **buffer, gssize size ) { struct prpl_xfer_data *px = xfer->ui_data; fprintf( stderr, "xfer_read %d %d\n", size, px->buf_len ); if( px->buf ) { *buffer = px->buf; px->buf = NULL; px->ft->write_request( px->ft ); return px->buf_len; } return 0; } PurpleXferUiOps bee_xfer_uiops = { prplcb_xfer_new, prplcb_xfer_dbg, prplcb_xfer_dbg, prplcb_xfer_progress, prplcb_xfer_dbg, prplcb_xfer_dbg, prplcb_xfer_write, prplcb_xfer_read, prplcb_xfer_dbg, }; static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond ); void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle ) { PurpleAccount *pa = ic->proto_data; struct prpl_xfer_data *px; /* xfer_new() will pick up this variable. It's a hack but we're not multi-threaded anyway. */ next_ft = ft; serv_send_file( purple_account_get_connection( pa ), handle, ft->file_name ); ft->write = prpl_xfer_write; px = ft->data; imcb_file_recv_start( ft ); px->ready_timer = b_timeout_add( 100, prplcb_xfer_send_cb, px ); } static gboolean prplcb_xfer_send_cb( gpointer data, gint fd, b_input_condition cond ) { struct prpl_xfer_data *px = data; if( px->ft->status & FT_STATUS_TRANSFERRING ) { fprintf( stderr, "The ft, it is ready...\n" ); px->ft->write_request( px->ft ); return FALSE; } return TRUE; } bitlbee-3.2.1/protocols/purple/ft.c0000644000175000017500000002305112245474076016627 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * libpurple module - File transfer stuff * * * * Copyright 2009-2010 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ /* Do file transfers via disk for now, since libpurple was really designed for straight-to/from disk fts and is only just learning how to pass the file contents the the UI instead (2.6.0 and higher it seems, and with varying levels of success). */ #include "bitlbee.h" #include #include #include struct prpl_xfer_data { PurpleXfer *xfer; file_transfer_t *ft; struct im_connection *ic; int fd; char *fn, *handle; gboolean ui_wants_data; }; static file_transfer_t *next_ft; struct im_connection *purple_ic_by_pa( PurpleAccount *pa ); static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond ); static gboolean prpl_xfer_write_request( struct file_transfer *ft ); /* Receiving files (IM->UI): */ static void prpl_xfer_accept( struct file_transfer *ft ) { struct prpl_xfer_data *px = ft->data; purple_xfer_request_accepted( px->xfer, NULL ); prpl_xfer_write_request( ft ); } static void prpl_xfer_canceled( struct file_transfer *ft, char *reason ) { struct prpl_xfer_data *px = ft->data; purple_xfer_request_denied( px->xfer ); } static void prplcb_xfer_new( PurpleXfer *xfer ) { if( purple_xfer_get_type( xfer ) == PURPLE_XFER_RECEIVE ) { struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 ); xfer->ui_data = px; px->xfer = xfer; px->fn = mktemp( g_strdup( "/tmp/bitlbee-purple-ft.XXXXXX" ) ); px->fd = -1; px->ic = purple_ic_by_pa( xfer->account ); purple_xfer_set_local_filename( xfer, px->fn ); /* Sadly the xfer struct is still empty ATM so come back after the caller is done. */ b_timeout_add( 0, prplcb_xfer_new_send_cb, xfer ); } else { struct file_transfer *ft = next_ft; struct prpl_xfer_data *px = ft->data; xfer->ui_data = px; px->xfer = xfer; next_ft = NULL; } } static gboolean prplcb_xfer_new_send_cb( gpointer data, gint fd, b_input_condition cond ) { PurpleXfer *xfer = data; struct im_connection *ic = purple_ic_by_pa( xfer->account ); struct prpl_xfer_data *px = xfer->ui_data; PurpleBuddy *buddy; const char *who; buddy = purple_find_buddy( xfer->account, xfer->who ); who = buddy ? purple_buddy_get_name( buddy ) : xfer->who; /* TODO(wilmer): After spreading some more const goodness in BitlBee, remove the evil cast below. */ px->ft = imcb_file_send_start( ic, (char*) who, xfer->filename, xfer->size ); px->ft->data = px; px->ft->accept = prpl_xfer_accept; px->ft->canceled = prpl_xfer_canceled; px->ft->write_request = prpl_xfer_write_request; return FALSE; } gboolean try_write_to_ui( gpointer data, gint fd, b_input_condition cond ) { struct file_transfer *ft = data; struct prpl_xfer_data *px = ft->data; struct stat fs; off_t tx_bytes; /* If we don't have the file opened yet, there's no data so wait. */ if( px->fd < 0 || !px->ui_wants_data ) return FALSE; tx_bytes = lseek( px->fd, 0, SEEK_CUR ); fstat( px->fd, &fs ); if( fs.st_size > tx_bytes ) { char buf[1024]; size_t n = MIN( fs.st_size - tx_bytes, sizeof( buf ) ); if( read( px->fd, buf, n ) == n && ft->write( ft, buf, n ) ) { px->ui_wants_data = FALSE; } else { purple_xfer_cancel_local( px->xfer ); imcb_file_canceled( px->ic, ft, "Read error" ); } } if( lseek( px->fd, 0, SEEK_CUR ) == px->xfer->size ) { /*purple_xfer_end( px->xfer );*/ imcb_file_finished( px->ic, ft ); } return FALSE; } /* UI calls this when its buffer is empty and wants more data to send to the user. */ static gboolean prpl_xfer_write_request( struct file_transfer *ft ) { struct prpl_xfer_data *px = ft->data; px->ui_wants_data = TRUE; try_write_to_ui( ft, 0, 0 ); return FALSE; } /* Generic (IM<>UI): */ static void prplcb_xfer_destroy( PurpleXfer *xfer ) { struct prpl_xfer_data *px = xfer->ui_data; g_free( px->fn ); g_free( px->handle ); if( px->fd >= 0 ) close( px->fd ); g_free( px ); } static void prplcb_xfer_progress( PurpleXfer *xfer, double percent ) { struct prpl_xfer_data *px = xfer->ui_data; if( px == NULL ) return; if( purple_xfer_get_type( xfer ) == PURPLE_XFER_SEND ) { if( *px->fn ) { char *slash; unlink( px->fn ); if( ( slash = strrchr( px->fn, '/' ) ) ) { *slash = '\0'; rmdir( px->fn ); } *px->fn = '\0'; } return; } if( px->fd == -1 && percent > 0 ) { /* Weeeeeeeee, we're getting data! That means the file exists by now so open it and start sending to the UI. */ px->fd = open( px->fn, O_RDONLY ); /* Unlink it now, because we don't need it after this. */ unlink( px->fn ); } if( percent < 1 ) try_write_to_ui( px->ft, 0, 0 ); else /* Another nice problem: If we have the whole file, it only gets closed when we return. Problem: There may still be stuff buffered and not written, we'll only see it after the caller close()s the file. So poll the file after that. */ b_timeout_add( 0, try_write_to_ui, px->ft ); } static void prplcb_xfer_cancel_remote( PurpleXfer *xfer ) { struct prpl_xfer_data *px = xfer->ui_data; if( px->ft ) imcb_file_canceled( px->ic, px->ft, "Canceled by remote end" ); else /* px->ft == NULL for sends, because of the two stages. :-/ */ imcb_error( px->ic, "File transfer cancelled by remote end" ); } static void prplcb_xfer_dbg( PurpleXfer *xfer ) { fprintf( stderr, "prplcb_xfer_dbg 0x%p\n", xfer ); } /* Sending files (UI->IM): */ static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len ); static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond ); void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle ) { struct prpl_xfer_data *px = g_new0( struct prpl_xfer_data, 1 ); char *dir, *basename; ft->data = px; px->ft = ft; dir = g_strdup( "/tmp/bitlbee-purple-ft.XXXXXX" ); if( !mkdtemp( dir ) ) { imcb_error( ic, "Could not create temporary file for file transfer" ); g_free( px ); g_free( dir ); return; } if( ( basename = strrchr( ft->file_name, '/' ) ) ) basename++; else basename = ft->file_name; px->fn = g_strdup_printf( "%s/%s", dir, basename ); px->fd = open( px->fn, O_WRONLY | O_CREAT, 0600 ); g_free( dir ); if( px->fd < 0 ) { imcb_error( ic, "Could not create temporary file for file transfer" ); g_free( px ); g_free( px->fn ); return; } px->ic = ic; px->handle = g_strdup( handle ); imcb_log( ic, "Due to libpurple limitations, the file has to be cached locally before proceeding with the actual file transfer. Please wait..." ); b_timeout_add( 0, purple_transfer_request_cb, ft ); } static void purple_transfer_forward( struct file_transfer *ft ) { struct prpl_xfer_data *px = ft->data; PurpleAccount *pa = px->ic->proto_data; /* xfer_new() will pick up this variable. It's a hack but we're not multi-threaded anyway. */ next_ft = ft; serv_send_file( purple_account_get_connection( pa ), px->handle, px->fn ); } static gboolean purple_transfer_request_cb( gpointer data, gint fd, b_input_condition cond ) { file_transfer_t *ft = data; struct prpl_xfer_data *px = ft->data; if( ft->write == NULL ) { ft->write = prpl_xfer_write; imcb_file_recv_start( px->ic, ft ); } ft->write_request( ft ); return FALSE; } static gboolean prpl_xfer_write( struct file_transfer *ft, char *buffer, unsigned int len ) { struct prpl_xfer_data *px = ft->data; if( write( px->fd, buffer, len ) != len ) { imcb_file_canceled( px->ic, ft, "Error while writing temporary file" ); return FALSE; } if( lseek( px->fd, 0, SEEK_CUR ) >= ft->file_size ) { close( px->fd ); px->fd = -1; purple_transfer_forward( ft ); imcb_file_finished( px->ic, ft ); px->ft = NULL; } else b_timeout_add( 0, purple_transfer_request_cb, ft ); return TRUE; } PurpleXferUiOps bee_xfer_uiops = { prplcb_xfer_new, prplcb_xfer_destroy, NULL, /* prplcb_xfer_add, */ prplcb_xfer_progress, prplcb_xfer_dbg, prplcb_xfer_cancel_remote, NULL, NULL, prplcb_xfer_dbg, }; bitlbee-3.2.1/protocols/purple/purple.c0000644000175000017500000011366212245474076017535 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * libpurple module - Main file * * * * Copyright 2009-2012 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include "bitlbee.h" #include "help.h" #include #include #include GSList *purple_connections; /* This makes me VERY sad... :-( But some libpurple callbacks come in without any context so this is the only way to get that. Don't want to support libpurple in daemon mode anyway. */ static bee_t *local_bee; static char *set_eval_display_name( set_t *set, char *value ); struct im_connection *purple_ic_by_pa( PurpleAccount *pa ) { GSList *i; for( i = purple_connections; i; i = i->next ) if( ((struct im_connection *)i->data)->proto_data == pa ) return i->data; return NULL; } static struct im_connection *purple_ic_by_gc( PurpleConnection *gc ) { return purple_ic_by_pa( purple_connection_get_account( gc ) ); } static gboolean purple_menu_cmp( const char *a, const char *b ) { while( *a && *b ) { while( *a == '_' ) a ++; while( *b == '_' ) b ++; if( tolower( *a ) != tolower( *b ) ) return FALSE; a ++; b ++; } return ( *a == '\0' && *b == '\0' ); } static void purple_init( account_t *acc ) { PurplePlugin *prpl = purple_plugins_find_with_id( (char*) acc->prpl->data ); PurplePluginProtocolInfo *pi = prpl->info->extra_info; PurpleAccount *pa; GList *i, *st; set_t *s; char help_title[64]; GString *help; static gboolean dir_fixed = FALSE; /* Layer violation coming up: Making an exception for libpurple here. Dig in the IRC state a bit to get a username. Ideally we should check if s/he identified but this info doesn't seem *that* important. It's just that fecking libpurple can't *not* store this shit. Remember that libpurple is not really meant to be used on public servers anyway! */ if( !dir_fixed ) { irc_t *irc = acc->bee->ui_data; char *dir; dir = g_strdup_printf( "%s/purple/%s", global.conf->configdir, irc->user->nick ); purple_util_set_user_dir( dir ); g_free( dir ); purple_blist_load(); purple_prefs_load(); dir_fixed = TRUE; } help = g_string_new( "" ); g_string_printf( help, "BitlBee libpurple module %s (%s).\n\nSupported settings:", (char*) acc->prpl->name, prpl->info->name ); if( pi->user_splits ) { GList *l; g_string_append_printf( help, "\n* username: Username" ); for( l = pi->user_splits; l; l = l->next ) g_string_append_printf( help, "%c%s", purple_account_user_split_get_separator( l->data ), purple_account_user_split_get_text( l->data ) ); } /* Convert all protocol_options into per-account setting variables. */ for( i = pi->protocol_options; i; i = i->next ) { PurpleAccountOption *o = i->data; const char *name; char *def = NULL; set_eval eval = NULL; void *eval_data = NULL; GList *io = NULL; GSList *opts = NULL; name = purple_account_option_get_setting( o ); switch( purple_account_option_get_type( o ) ) { case PURPLE_PREF_STRING: def = g_strdup( purple_account_option_get_default_string( o ) ); g_string_append_printf( help, "\n* %s (%s), %s, default: %s", name, purple_account_option_get_text( o ), "string", def ); break; case PURPLE_PREF_INT: def = g_strdup_printf( "%d", purple_account_option_get_default_int( o ) ); eval = set_eval_int; g_string_append_printf( help, "\n* %s (%s), %s, default: %s", name, purple_account_option_get_text( o ), "integer", def ); break; case PURPLE_PREF_BOOLEAN: if( purple_account_option_get_default_bool( o ) ) def = g_strdup( "true" ); else def = g_strdup( "false" ); eval = set_eval_bool; g_string_append_printf( help, "\n* %s (%s), %s, default: %s", name, purple_account_option_get_text( o ), "boolean", def ); break; case PURPLE_PREF_STRING_LIST: def = g_strdup( purple_account_option_get_default_list_value( o ) ); g_string_append_printf( help, "\n* %s (%s), %s, default: %s", name, purple_account_option_get_text( o ), "list", def ); g_string_append( help, "\n Possible values: " ); for( io = purple_account_option_get_list( o ); io; io = io->next ) { PurpleKeyValuePair *kv = io->data; opts = g_slist_append( opts, kv->value ); /* TODO: kv->value is not a char*, WTF? */ if( strcmp( kv->value, kv->key ) != 0 ) g_string_append_printf( help, "%s (%s), ", (char*) kv->value, kv->key ); else g_string_append_printf( help, "%s, ", (char*) kv->value ); } g_string_truncate( help, help->len - 2 ); eval = set_eval_list; eval_data = opts; break; default: /** No way to talk to the user right now, invent one when this becomes important. irc_rootmsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n", name, purple_account_option_get_type( o ) ); */ g_string_append_printf( help, "\n* [%s] UNSUPPORTED (type %d)", name, purple_account_option_get_type( o ) ); name = NULL; } if( name != NULL ) { s = set_add( &acc->set, name, def, eval, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; s->eval_data = eval_data; g_free( def ); } } g_snprintf( help_title, sizeof( help_title ), "purple %s", (char*) acc->prpl->name ); help_add_mem( &global.help, help_title, help->str ); g_string_free( help, TRUE ); s = set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc ); s->flags |= ACC_SET_ONLINE_ONLY; if( pi->options & OPT_PROTO_MAIL_CHECK ) { s = set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; } if( strcmp( prpl->info->name, "Gadu-Gadu" ) == 0 ) s = set_add( &acc->set, "gg_sync_contacts", "true", set_eval_bool, acc ); /* Go through all away states to figure out if away/status messages are possible. */ pa = purple_account_new( acc->user, (char*) acc->prpl->data ); for( st = purple_account_get_status_types( pa ); st; st = st->next ) { PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data ); if( prim == PURPLE_STATUS_AVAILABLE ) { if( purple_status_type_get_attr( st->data, "message" ) ) acc->flags |= ACC_FLAG_STATUS_MESSAGE; } else if( prim != PURPLE_STATUS_OFFLINE ) { if( purple_status_type_get_attr( st->data, "message" ) ) acc->flags |= ACC_FLAG_AWAY_MESSAGE; } } purple_accounts_remove( pa ); } static void purple_sync_settings( account_t *acc, PurpleAccount *pa ) { PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id ); PurplePluginProtocolInfo *pi = prpl->info->extra_info; GList *i; for( i = pi->protocol_options; i; i = i->next ) { PurpleAccountOption *o = i->data; const char *name; set_t *s; name = purple_account_option_get_setting( o ); s = set_find( &acc->set, name ); if( s->value == NULL ) continue; switch( purple_account_option_get_type( o ) ) { case PURPLE_PREF_STRING: case PURPLE_PREF_STRING_LIST: purple_account_set_string( pa, name, set_getstr( &acc->set, name ) ); break; case PURPLE_PREF_INT: purple_account_set_int( pa, name, set_getint( &acc->set, name ) ); break; case PURPLE_PREF_BOOLEAN: purple_account_set_bool( pa, name, set_getbool( &acc->set, name ) ); break; default: break; } } if( pi->options & OPT_PROTO_MAIL_CHECK ) purple_account_set_check_mail( pa, set_getbool( &acc->set, "mail_notifications" ) ); } static void purple_login( account_t *acc ) { struct im_connection *ic = imcb_new( acc ); PurpleAccount *pa; if( ( local_bee != NULL && local_bee != acc->bee ) || ( global.conf->runmode == RUNMODE_DAEMON && !getenv( "BITLBEE_DEBUG" ) ) ) { imcb_error( ic, "Daemon mode detected. Do *not* try to use libpurple in daemon mode! " "Please use inetd or ForkDaemon mode instead." ); imc_logout( ic, FALSE ); return; } local_bee = acc->bee; /* For now this is needed in the _connected() handlers if using GLib event handling, to make sure we're not handling events on dead connections. */ purple_connections = g_slist_prepend( purple_connections, ic ); ic->proto_data = pa = purple_account_new( acc->user, (char*) acc->prpl->data ); purple_account_set_password( pa, acc->pass ); purple_sync_settings( acc, pa ); purple_account_set_enabled( pa, "BitlBee", TRUE ); } static void purple_logout( struct im_connection *ic ) { PurpleAccount *pa = ic->proto_data; purple_account_set_enabled( pa, "BitlBee", FALSE ); purple_connections = g_slist_remove( purple_connections, ic ); purple_accounts_remove( pa ); } static int purple_buddy_msg( struct im_connection *ic, char *who, char *message, int flags ) { PurpleConversation *conv; if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, who, ic->proto_data ) ) == NULL ) { conv = purple_conversation_new( PURPLE_CONV_TYPE_IM, ic->proto_data, who ); } purple_conv_im_send( purple_conversation_get_im_data( conv ), message ); return 1; } static GList *purple_away_states( struct im_connection *ic ) { PurpleAccount *pa = ic->proto_data; GList *st, *ret = NULL; for( st = purple_account_get_status_types( pa ); st; st = st->next ) { PurpleStatusPrimitive prim = purple_status_type_get_primitive( st->data ); if( prim != PURPLE_STATUS_AVAILABLE && prim != PURPLE_STATUS_OFFLINE ) ret = g_list_append( ret, (void*) purple_status_type_get_name( st->data ) ); } return ret; } static void purple_set_away( struct im_connection *ic, char *state_txt, char *message ) { PurpleAccount *pa = ic->proto_data; GList *status_types = purple_account_get_status_types( pa ), *st; PurpleStatusType *pst = NULL; GList *args = NULL; for( st = status_types; st; st = st->next ) { pst = st->data; if( state_txt == NULL && purple_status_type_get_primitive( pst ) == PURPLE_STATUS_AVAILABLE ) break; if( state_txt != NULL && g_strcasecmp( state_txt, purple_status_type_get_name( pst ) ) == 0 ) break; } if( message && purple_status_type_get_attr( pst, "message" ) ) { args = g_list_append( args, "message" ); args = g_list_append( args, message ); } purple_account_set_status_list( pa, st ? purple_status_type_get_id( pst ) : "away", TRUE, args ); g_list_free( args ); } static char *set_eval_display_name( set_t *set, char *value ) { account_t *acc = set->data; struct im_connection *ic = acc->ic; if( ic ) imcb_log( ic, "Changing display_name not currently supported with libpurple!" ); return NULL; } /* Bad bad gadu-gadu, not saving buddy list by itself */ static void purple_gg_buddylist_export( PurpleConnection *gc ) { struct im_connection *ic = purple_ic_by_gc( gc ); if( set_getstr( &ic->acc->set, "gg_sync_contacts" ) ) { GList *actions = gc->prpl->info->actions( gc->prpl, gc ); GList *p; for( p = g_list_first(actions); p; p = p->next ) { if( ((PurplePluginAction*)p->data) && purple_menu_cmp( ((PurplePluginAction*)p->data)->label, "Upload buddylist to Server" ) == 0) { PurplePluginAction action; action.plugin = gc->prpl; action.context = gc; action.user_data = NULL; ((PurplePluginAction*)p->data)->callback(&action); break; } } g_list_free( actions ); } } static void purple_gg_buddylist_import( PurpleConnection *gc ) { struct im_connection *ic = purple_ic_by_gc( gc ); if( set_getstr( &ic->acc->set, "gg_sync_contacts" ) ) { GList *actions = gc->prpl->info->actions( gc->prpl, gc ); GList *p; for( p = g_list_first(actions); p; p = p->next ) { if( ((PurplePluginAction*)p->data) && purple_menu_cmp( ((PurplePluginAction*)p->data)->label, "Download buddylist from Server" ) == 0 ) { PurplePluginAction action; action.plugin = gc->prpl; action.context = gc; action.user_data = NULL; ((PurplePluginAction*)p->data)->callback(&action); break; } } g_list_free( actions ); } } static void purple_add_buddy( struct im_connection *ic, char *who, char *group ) { PurpleBuddy *pb; PurpleGroup *pg = NULL; if( group && !( pg = purple_find_group( group ) ) ) { pg = purple_group_new( group ); purple_blist_add_group( pg, NULL ); } pb = purple_buddy_new( (PurpleAccount*) ic->proto_data, who, NULL ); purple_blist_add_buddy( pb, NULL, pg, NULL ); purple_account_add_buddy( (PurpleAccount*) ic->proto_data, pb ); purple_gg_buddylist_export( ((PurpleAccount*)ic->proto_data)->gc ); } static void purple_remove_buddy( struct im_connection *ic, char *who, char *group ) { PurpleBuddy *pb; pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who ); if( pb != NULL ) { PurpleGroup *group; group = purple_buddy_get_group( pb ); purple_account_remove_buddy( (PurpleAccount*) ic->proto_data, pb, group ); purple_blist_remove_buddy( pb ); } purple_gg_buddylist_export( ((PurpleAccount*)ic->proto_data)->gc ); } static void purple_add_permit( struct im_connection *ic, char *who ) { PurpleAccount *pa = ic->proto_data; purple_privacy_permit_add( pa, who, FALSE ); } static void purple_add_deny( struct im_connection *ic, char *who ) { PurpleAccount *pa = ic->proto_data; purple_privacy_deny_add( pa, who, FALSE ); } static void purple_rem_permit( struct im_connection *ic, char *who ) { PurpleAccount *pa = ic->proto_data; purple_privacy_permit_remove( pa, who, FALSE ); } static void purple_rem_deny( struct im_connection *ic, char *who ) { PurpleAccount *pa = ic->proto_data; purple_privacy_deny_remove( pa, who, FALSE ); } static void purple_get_info( struct im_connection *ic, char *who ) { serv_get_info( purple_account_get_connection( ic->proto_data ), who ); } static void purple_keepalive( struct im_connection *ic ) { } static int purple_send_typing( struct im_connection *ic, char *who, int flags ) { PurpleTypingState state = PURPLE_NOT_TYPING; PurpleAccount *pa = ic->proto_data; if( flags & OPT_TYPING ) state = PURPLE_TYPING; else if( flags & OPT_THINKING ) state = PURPLE_TYPED; serv_send_typing( purple_account_get_connection( pa ), who, state ); return 1; } static void purple_chat_msg( struct groupchat *gc, char *message, int flags ) { PurpleConversation *pc = gc->data; purple_conv_chat_send( purple_conversation_get_chat_data( pc ), message ); } struct groupchat *purple_chat_with( struct im_connection *ic, char *who ) { /* No, "of course" this won't work this way. Or in fact, it almost does, but it only lets you send msgs to it, you won't receive any. Instead, we have to click the virtual menu item. PurpleAccount *pa = ic->proto_data; PurpleConversation *pc; PurpleConvChat *pcc; struct groupchat *gc; gc = imcb_chat_new( ic, "BitlBee-libpurple groupchat" ); gc->data = pc = purple_conversation_new( PURPLE_CONV_TYPE_CHAT, pa, "BitlBee-libpurple groupchat" ); pc->ui_data = gc; pcc = PURPLE_CONV_CHAT( pc ); purple_conv_chat_add_user( pcc, ic->acc->user, "", 0, TRUE ); purple_conv_chat_invite_user( pcc, who, "Please join my chat", FALSE ); //purple_conv_chat_add_user( pcc, who, "", 0, TRUE ); */ /* There went my nice afternoon. :-( */ PurpleAccount *pa = ic->proto_data; PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id ); PurplePluginProtocolInfo *pi = prpl->info->extra_info; PurpleBuddy *pb = purple_find_buddy( (PurpleAccount*) ic->proto_data, who ); PurpleMenuAction *mi; GList *menu; void (*callback)(PurpleBlistNode *, gpointer); /* FFFFFFFFFFFFFUUUUUUUUUUUUUU */ if( !pb || !pi || !pi->blist_node_menu ) return NULL; menu = pi->blist_node_menu( &pb->node ); while( menu ) { mi = menu->data; if( purple_menu_cmp( mi->label, "initiate chat" ) || purple_menu_cmp( mi->label, "initiate conference" ) ) break; menu = menu->next; } if( menu == NULL ) return NULL; /* Call the fucker. */ callback = (void*) mi->callback; callback( &pb->node, menu->data ); return NULL; } void purple_chat_invite( struct groupchat *gc, char *who, char *message ) { PurpleConversation *pc = gc->data; PurpleConvChat *pcc = PURPLE_CONV_CHAT( pc ); serv_chat_invite( purple_account_get_connection( gc->ic->proto_data ), purple_conv_chat_get_id( pcc ), message && *message ? message : "Please join my chat", who ); } void purple_chat_leave( struct groupchat *gc ) { PurpleConversation *pc = gc->data; purple_conversation_destroy( pc ); } struct groupchat *purple_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets ) { PurpleAccount *pa = ic->proto_data; PurplePlugin *prpl = purple_plugins_find_with_id( pa->protocol_id ); PurplePluginProtocolInfo *pi = prpl->info->extra_info; GHashTable *chat_hash; PurpleConversation *conv; GList *info, *l; if( !pi->chat_info || !pi->chat_info_defaults || !( info = pi->chat_info( purple_account_get_connection( pa ) ) ) ) { imcb_error( ic, "Joining chatrooms not supported by this protocol" ); return NULL; } if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_CHAT, room, pa ) ) ) purple_conversation_destroy( conv ); chat_hash = pi->chat_info_defaults( purple_account_get_connection( pa ), room ); for( l = info; l; l = l->next ) { struct proto_chat_entry *pce = l->data; if( strcmp( pce->identifier, "handle" ) == 0 ) g_hash_table_replace( chat_hash, "handle", g_strdup( nick ) ); else if( strcmp( pce->identifier, "password" ) == 0 ) g_hash_table_replace( chat_hash, "password", g_strdup( password ) ); else if( strcmp( pce->identifier, "passwd" ) == 0 ) g_hash_table_replace( chat_hash, "passwd", g_strdup( password ) ); } serv_join_chat( purple_account_get_connection( pa ), chat_hash ); return NULL; } void purple_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *handle ); static void purple_ui_init(); GHashTable *prplcb_ui_info() { static GHashTable *ret; if( ret == NULL ) { ret = g_hash_table_new(g_str_hash, g_str_equal); g_hash_table_insert( ret, "name", "BitlBee" ); g_hash_table_insert( ret, "version", BITLBEE_VERSION ); } return ret; } static PurpleCoreUiOps bee_core_uiops = { NULL, NULL, purple_ui_init, NULL, prplcb_ui_info, }; static void prplcb_conn_progress( PurpleConnection *gc, const char *text, size_t step, size_t step_count ) { struct im_connection *ic = purple_ic_by_gc( gc ); imcb_log( ic, "%s", text ); } static void prplcb_conn_connected( PurpleConnection *gc ) { struct im_connection *ic = purple_ic_by_gc( gc ); const char *dn; set_t *s; imcb_connected( ic ); if( ( dn = purple_connection_get_display_name( gc ) ) && ( s = set_find( &ic->acc->set, "display_name" ) ) ) { g_free( s->value ); s->value = g_strdup( dn ); } // user list needs to be requested for Gadu-Gadu purple_gg_buddylist_import( gc ); if( gc->flags & PURPLE_CONNECTION_HTML ) ic->flags |= OPT_DOES_HTML; } static void prplcb_conn_disconnected( PurpleConnection *gc ) { struct im_connection *ic = purple_ic_by_gc( gc ); if( ic != NULL ) { imc_logout( ic, !gc->wants_to_die ); } } static void prplcb_conn_notice( PurpleConnection *gc, const char *text ) { struct im_connection *ic = purple_ic_by_gc( gc ); if( ic != NULL ) imcb_log( ic, "%s", text ); } static void prplcb_conn_report_disconnect_reason( PurpleConnection *gc, PurpleConnectionError reason, const char *text ) { struct im_connection *ic = purple_ic_by_gc( gc ); /* PURPLE_CONNECTION_ERROR_NAME_IN_USE means concurrent login, should probably handle that. */ if( ic != NULL ) imcb_error( ic, "%s", text ); } static PurpleConnectionUiOps bee_conn_uiops = { prplcb_conn_progress, prplcb_conn_connected, prplcb_conn_disconnected, prplcb_conn_notice, NULL, NULL, NULL, prplcb_conn_report_disconnect_reason, }; static void prplcb_blist_update( PurpleBuddyList *list, PurpleBlistNode *node ) { if( node->type == PURPLE_BLIST_BUDDY_NODE ) { PurpleBuddy *bud = (PurpleBuddy*) node; PurpleGroup *group = purple_buddy_get_group( bud ); struct im_connection *ic = purple_ic_by_pa( bud->account ); PurpleStatus *as; int flags = 0; if( ic == NULL ) return; if( bud->server_alias ) imcb_rename_buddy( ic, bud->name, bud->server_alias ); else if( bud->alias ) imcb_rename_buddy( ic, bud->name, bud->alias ); if( group ) imcb_add_buddy( ic, bud->name, purple_group_get_name( group ) ); flags |= purple_presence_is_online( bud->presence ) ? OPT_LOGGED_IN : 0; flags |= purple_presence_is_available( bud->presence ) ? 0 : OPT_AWAY; as = purple_presence_get_active_status( bud->presence ); imcb_buddy_status( ic, bud->name, flags, purple_status_get_name( as ), purple_status_get_attr_string( as, "message" ) ); imcb_buddy_times( ic, bud->name, purple_presence_get_login_time( bud->presence ), purple_presence_get_idle_time( bud->presence ) ); } } static void prplcb_blist_new( PurpleBlistNode *node ) { if( node->type == PURPLE_BLIST_BUDDY_NODE ) { PurpleBuddy *bud = (PurpleBuddy*) node; struct im_connection *ic = purple_ic_by_pa( bud->account ); if( ic == NULL ) return; imcb_add_buddy( ic, bud->name, NULL ); prplcb_blist_update( NULL, node ); } } static void prplcb_blist_remove( PurpleBuddyList *list, PurpleBlistNode *node ) { /* PurpleBuddy *bud = (PurpleBuddy*) node; if( node->type == PURPLE_BLIST_BUDDY_NODE ) { struct im_connection *ic = purple_ic_by_pa( bud->account ); if( ic == NULL ) return; imcb_remove_buddy( ic, bud->name, NULL ); } */ } static PurpleBlistUiOps bee_blist_uiops = { NULL, prplcb_blist_new, NULL, prplcb_blist_update, prplcb_blist_remove, }; void prplcb_conv_new( PurpleConversation *conv ) { if( conv->type == PURPLE_CONV_TYPE_CHAT ) { struct im_connection *ic = purple_ic_by_pa( conv->account ); struct groupchat *gc; gc = imcb_chat_new( ic, conv->name ); conv->ui_data = gc; gc->data = conv; /* libpurple brokenness: Whatever. Show that we join right away, there's no clear "This is you!" signaling in _add_users so don't even try. */ imcb_chat_add_buddy( gc, gc->ic->acc->user ); } } void prplcb_conv_free( PurpleConversation *conv ) { struct groupchat *gc = conv->ui_data; imcb_chat_free( gc ); } void prplcb_conv_add_users( PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals ) { struct groupchat *gc = conv->ui_data; GList *b; for( b = cbuddies; b; b = b->next ) { PurpleConvChatBuddy *pcb = b->data; imcb_chat_add_buddy( gc, pcb->name ); } } void prplcb_conv_del_users( PurpleConversation *conv, GList *cbuddies ) { struct groupchat *gc = conv->ui_data; GList *b; for( b = cbuddies; b; b = b->next ) imcb_chat_remove_buddy( gc, b->data, "" ); } void prplcb_conv_chat_msg( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime ) { struct groupchat *gc = conv->ui_data; PurpleBuddy *buddy; /* ..._SEND means it's an outgoing message, no need to echo those. */ if( flags & PURPLE_MESSAGE_SEND ) return; buddy = purple_find_buddy( conv->account, who ); if( buddy != NULL ) who = purple_buddy_get_name( buddy ); imcb_chat_msg( gc, who, (char*) message, 0, mtime ); } static void prplcb_conv_im( PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime ) { struct im_connection *ic = purple_ic_by_pa( conv->account ); PurpleBuddy *buddy; /* ..._SEND means it's an outgoing message, no need to echo those. */ if( flags & PURPLE_MESSAGE_SEND ) return; buddy = purple_find_buddy( conv->account, who ); if( buddy != NULL ) who = purple_buddy_get_name( buddy ); imcb_buddy_msg( ic, (char*) who, (char*) message, 0, mtime ); } /* No, this is not a ui_op but a signal. */ static void prplcb_buddy_typing( PurpleAccount *account, const char *who, gpointer null ) { PurpleConversation *conv; PurpleConvIm *im; int state; if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, who, account ) ) == NULL ) return; im = PURPLE_CONV_IM(conv); switch( purple_conv_im_get_typing_state( im ) ) { case PURPLE_TYPING: state = OPT_TYPING; break; case PURPLE_TYPED: state = OPT_THINKING; break; default: state = 0; } imcb_buddy_typing( purple_ic_by_pa( account ), who, state ); } static PurpleConversationUiOps bee_conv_uiops = { prplcb_conv_new, /* create_conversation */ prplcb_conv_free, /* destroy_conversation */ prplcb_conv_chat_msg, /* write_chat */ prplcb_conv_im, /* write_im */ NULL, /* write_conv */ prplcb_conv_add_users, /* chat_add_users */ NULL, /* chat_rename_user */ prplcb_conv_del_users, /* chat_remove_users */ NULL, /* chat_update_user */ NULL, /* present */ NULL, /* has_focus */ NULL, /* custom_smiley_add */ NULL, /* custom_smiley_write */ NULL, /* custom_smiley_close */ NULL, /* send_confirm */ }; struct prplcb_request_action_data { void *user_data, *bee_data; PurpleRequestActionCb yes, no; int yes_i, no_i; }; static void prplcb_request_action_yes( void *data ) { struct prplcb_request_action_data *pqad = data; if( pqad->yes ) pqad->yes( pqad->user_data, pqad->yes_i ); g_free( pqad ); } static void prplcb_request_action_no( void *data ) { struct prplcb_request_action_data *pqad = data; if( pqad->no ) pqad->no( pqad->user_data, pqad->no_i ); g_free( pqad ); } static void *prplcb_request_action( const char *title, const char *primary, const char *secondary, int default_action, PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data, size_t action_count, va_list actions ) { struct prplcb_request_action_data *pqad; int i; char *q; pqad = g_new0( struct prplcb_request_action_data, 1 ); for( i = 0; i < action_count; i ++ ) { char *caption; void *fn; caption = va_arg( actions, char* ); fn = va_arg( actions, void* ); if( strstr( caption, "Accept" ) || strstr( caption, "OK" ) ) { pqad->yes = fn; pqad->yes_i = i; } else if( strstr( caption, "Reject" ) || strstr( caption, "Cancel" ) ) { pqad->no = fn; pqad->no_i = i; } } pqad->user_data = user_data; /* TODO: IRC stuff here :-( */ q = g_strdup_printf( "Request: %s\n\n%s\n\n%s", title, primary, secondary ); pqad->bee_data = query_add( local_bee->ui_data, purple_ic_by_pa( account ), q, prplcb_request_action_yes, prplcb_request_action_no, g_free, pqad ); g_free( q ); return pqad; } /* static void prplcb_request_test() { fprintf( stderr, "bla\n" ); } */ static PurpleRequestUiOps bee_request_uiops = { NULL, NULL, prplcb_request_action, NULL, NULL, NULL, NULL, }; static void prplcb_privacy_permit_added( PurpleAccount *account, const char *name ) { struct im_connection *ic = purple_ic_by_pa( account ); if( !g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) ic->permit = g_slist_prepend( ic->permit, g_strdup( name ) ); } static void prplcb_privacy_permit_removed( PurpleAccount *account, const char *name ) { struct im_connection *ic = purple_ic_by_pa( account ); void *n; n = g_slist_find_custom( ic->permit, name, (GCompareFunc) ic->acc->prpl->handle_cmp ); ic->permit = g_slist_remove( ic->permit, n ); } static void prplcb_privacy_deny_added( PurpleAccount *account, const char *name ) { struct im_connection *ic = purple_ic_by_pa( account ); if( !g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) ic->deny = g_slist_prepend( ic->deny, g_strdup( name ) ); } static void prplcb_privacy_deny_removed( PurpleAccount *account, const char *name ) { struct im_connection *ic = purple_ic_by_pa( account ); void *n; n = g_slist_find_custom( ic->deny, name, (GCompareFunc) ic->acc->prpl->handle_cmp ); ic->deny = g_slist_remove( ic->deny, n ); } static PurplePrivacyUiOps bee_privacy_uiops = { prplcb_privacy_permit_added, prplcb_privacy_permit_removed, prplcb_privacy_deny_added, prplcb_privacy_deny_removed, }; static void prplcb_debug_print( PurpleDebugLevel level, const char *category, const char *arg_s ) { fprintf( stderr, "DEBUG %s: %s", category, arg_s ); } static PurpleDebugUiOps bee_debug_uiops = { prplcb_debug_print, }; static guint prplcb_ev_timeout_add( guint interval, GSourceFunc func, gpointer udata ) { return b_timeout_add( interval, (b_event_handler) func, udata ); } static guint prplcb_ev_input_add( int fd, PurpleInputCondition cond, PurpleInputFunction func, gpointer udata ) { return b_input_add( fd, cond | B_EV_FLAG_FORCE_REPEAT, (b_event_handler) func, udata ); } static gboolean prplcb_ev_remove( guint id ) { b_event_remove( (gint) id ); return TRUE; } static PurpleEventLoopUiOps glib_eventloops = { prplcb_ev_timeout_add, prplcb_ev_remove, prplcb_ev_input_add, prplcb_ev_remove, }; static void *prplcb_notify_email( PurpleConnection *gc, const char *subject, const char *from, const char *to, const char *url ) { struct im_connection *ic = purple_ic_by_gc( gc ); imcb_log( ic, "Received e-mail from %s for %s: %s <%s>", from, to, subject, url ); return NULL; } static void *prplcb_notify_userinfo( PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info ) { struct im_connection *ic = purple_ic_by_gc( gc ); GString *info = g_string_new( "" ); GList *l = purple_notify_user_info_get_entries( user_info ); char *key; const char *value; int n; while( l ) { PurpleNotifyUserInfoEntry *e = l->data; switch( purple_notify_user_info_entry_get_type( e ) ) { case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR: case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER: key = g_strdup( purple_notify_user_info_entry_get_label( e ) ); value = purple_notify_user_info_entry_get_value( e ); if( key ) { strip_html( key ); g_string_append_printf( info, "%s: ", key ); if( value ) { n = strlen( value ) - 1; while( isspace( value[n] ) ) n --; g_string_append_len( info, value, n + 1 ); } g_string_append_c( info, '\n' ); g_free( key ); } break; case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK: g_string_append( info, "------------------------\n" ); break; } l = l->next; } imcb_log( ic, "User %s info:\n%s", who, info->str ); g_string_free( info, TRUE ); return NULL; } static PurpleNotifyUiOps bee_notify_uiops = { NULL, prplcb_notify_email, NULL, NULL, NULL, NULL, prplcb_notify_userinfo, }; static void *prplcb_account_request_authorize( PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *message, gboolean on_list, PurpleAccountRequestAuthorizationCb authorize_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data ) { struct im_connection *ic = purple_ic_by_pa( account ); char *q; if( alias ) q = g_strdup_printf( "%s (%s) wants to add you to his/her contact " "list. (%s)", alias, remote_user, message ); else q = g_strdup_printf( "%s wants to add you to his/her contact " "list. (%s)", remote_user, message ); imcb_ask_with_free( ic, q, user_data, authorize_cb, deny_cb, NULL ); g_free( q ); return NULL; } static PurpleAccountUiOps bee_account_uiops = { NULL, NULL, NULL, prplcb_account_request_authorize, NULL, }; extern PurpleXferUiOps bee_xfer_uiops; static void purple_ui_init() { purple_connections_set_ui_ops( &bee_conn_uiops ); purple_blist_set_ui_ops( &bee_blist_uiops ); purple_conversations_set_ui_ops( &bee_conv_uiops ); purple_request_set_ui_ops( &bee_request_uiops ); purple_privacy_set_ui_ops( &bee_privacy_uiops ); purple_notify_set_ui_ops( &bee_notify_uiops ); purple_accounts_set_ui_ops( &bee_account_uiops ); purple_xfers_set_ui_ops( &bee_xfer_uiops ); if( getenv( "BITLBEE_DEBUG" ) ) purple_debug_set_ui_ops( &bee_debug_uiops ); } void purple_initmodule() { struct prpl funcs; GList *prots; GString *help; char *dir; if( B_EV_IO_READ != PURPLE_INPUT_READ || B_EV_IO_WRITE != PURPLE_INPUT_WRITE ) { /* FIXME FIXME FIXME FIXME FIXME :-) */ exit( 1 ); } dir = g_strdup_printf( "%s/purple", global.conf->configdir ); purple_util_set_user_dir( dir ); g_free( dir ); purple_debug_set_enabled( FALSE ); purple_core_set_ui_ops( &bee_core_uiops ); purple_eventloop_set_ui_ops( &glib_eventloops ); if( !purple_core_init( "BitlBee") ) { /* Initializing the core failed. Terminate. */ fprintf( stderr, "libpurple initialization failed.\n" ); abort(); } if( proxytype != PROXY_NONE ) { PurpleProxyInfo *pi = purple_global_proxy_get_info(); switch( proxytype ) { case PROXY_SOCKS4: purple_proxy_info_set_type( pi, PURPLE_PROXY_SOCKS4 ); break; case PROXY_SOCKS5: purple_proxy_info_set_type( pi, PURPLE_PROXY_SOCKS5 ); break; case PROXY_HTTP: purple_proxy_info_set_type( pi, PURPLE_PROXY_HTTP ); break; } purple_proxy_info_set_host( pi, proxyhost ); purple_proxy_info_set_port( pi, proxyport ); purple_proxy_info_set_username( pi, proxyuser ); purple_proxy_info_set_password( pi, proxypass ); } purple_set_blist( purple_blist_new() ); /* No, really. So far there were ui_ops for everything, but now suddenly one needs to use signals for typing notification stuff. :-( */ purple_signal_connect( purple_conversations_get_handle(), "buddy-typing", &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL ); purple_signal_connect( purple_conversations_get_handle(), "buddy-typed", &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL ); purple_signal_connect( purple_conversations_get_handle(), "buddy-typing-stopped", &funcs, PURPLE_CALLBACK(prplcb_buddy_typing), NULL ); memset( &funcs, 0, sizeof( funcs ) ); funcs.login = purple_login; funcs.init = purple_init; funcs.logout = purple_logout; funcs.buddy_msg = purple_buddy_msg; funcs.away_states = purple_away_states; funcs.set_away = purple_set_away; funcs.add_buddy = purple_add_buddy; funcs.remove_buddy = purple_remove_buddy; funcs.add_permit = purple_add_permit; funcs.add_deny = purple_add_deny; funcs.rem_permit = purple_rem_permit; funcs.rem_deny = purple_rem_deny; funcs.get_info = purple_get_info; funcs.keepalive = purple_keepalive; funcs.send_typing = purple_send_typing; funcs.handle_cmp = g_strcasecmp; /* TODO(wilmer): Set these only for protocols that support them? */ funcs.chat_msg = purple_chat_msg; funcs.chat_with = purple_chat_with; funcs.chat_invite = purple_chat_invite; funcs.chat_leave = purple_chat_leave; funcs.chat_join = purple_chat_join; funcs.transfer_request = purple_transfer_request; help = g_string_new( "BitlBee libpurple module supports the following IM protocols:\n" ); /* Add a protocol entry to BitlBee's structures for every protocol supported by this libpurple instance. */ for( prots = purple_plugins_get_protocols(); prots; prots = prots->next ) { PurplePlugin *prot = prots->data; struct prpl *ret; /* If we already have this one (as a native module), don't add a libpurple duplicate. */ if( find_protocol( prot->info->id ) ) continue; ret = g_memdup( &funcs, sizeof( funcs ) ); ret->name = ret->data = prot->info->id; if( strncmp( ret->name, "prpl-", 5 ) == 0 ) ret->name += 5; register_protocol( ret ); g_string_append_printf( help, "\n* %s (%s)", ret->name, prot->info->name ); /* libpurple doesn't define a protocol called OSCAR, but we need it to be compatible with normal BitlBee. */ if( g_strcasecmp( prot->info->id, "prpl-aim" ) == 0 ) { ret = g_memdup( &funcs, sizeof( funcs ) ); ret->name = "oscar"; ret->data = prot->info->id; register_protocol( ret ); } } g_string_append( help, "\n\nFor used protocols, more information about available " "settings can be found using \x02help purple \x02 " "(create an account using that protocol first!)" ); /* Add a simple dynamically-generated help item listing all the supported protocols. */ help_add_mem( &global.help, "purple", help->str ); g_string_free( help, TRUE ); } bitlbee-3.2.1/protocols/bee_chat.c0000644000175000017500000001507412245474076016447 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2010 Wilmer van der Gaast and others * \********************************************************************/ /* Stuff to handle rooms */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle ) { struct groupchat *c = g_new0( struct groupchat, 1 ); bee_t *bee = ic->bee; /* This one just creates the conversation structure, user won't see anything yet until s/he is joined to the conversation. (This allows you to add other already present participants first.) */ ic->groupchats = g_slist_prepend( ic->groupchats, c ); c->ic = ic; c->title = g_strdup( handle ); c->topic = g_strdup_printf( "BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", c->title ); if( set_getbool( &ic->bee->set, "debug" ) ) imcb_log( ic, "Creating new conversation: (id=%p,handle=%s)", c, handle ); if( bee->ui->chat_new ) bee->ui->chat_new( bee, c ); return c; } void imcb_chat_name_hint( struct groupchat *c, const char *name ) { bee_t *bee = c->ic->bee; if( bee->ui->chat_name_hint ) bee->ui->chat_name_hint( bee, c, name ); } void imcb_chat_free( struct groupchat *c ) { struct im_connection *ic = c->ic; bee_t *bee = ic->bee; GList *ir; if( bee->ui->chat_free ) bee->ui->chat_free( bee, c ); if( set_getbool( &ic->bee->set, "debug" ) ) imcb_log( ic, "You were removed from conversation %p", c ); ic->groupchats = g_slist_remove( ic->groupchats, c ); for( ir = c->in_room; ir; ir = ir->next ) g_free( ir->data ); g_list_free( c->in_room ); g_free( c->title ); g_free( c->topic ); g_free( c ); } void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t flags, time_t sent_at ) { struct im_connection *ic = c->ic; bee_t *bee = ic->bee; bee_user_t *bu; char *s; /* Gaim sends own messages through this too. IRC doesn't want this, so kill them */ if( g_strcasecmp( who, ic->acc->user ) == 0 ) return; bu = bee_user_by_handle( bee, ic, who ); s = set_getstr( &ic->bee->set, "strip_html" ); if( ( g_strcasecmp( s, "always" ) == 0 ) || ( ( ic->flags & OPT_DOES_HTML ) && s ) ) strip_html( msg ); if( bu && bee->ui->chat_msg ) bee->ui->chat_msg( bee, c, bu, msg, sent_at ); else imcb_chat_log( c, "Message from unknown participant %s: %s", who, msg ); } void imcb_chat_log( struct groupchat *c, char *format, ... ) { struct im_connection *ic = c->ic; bee_t *bee = ic->bee; va_list params; char *text; if( !bee->ui->chat_log ) return; va_start( params, format ); text = g_strdup_vprintf( format, params ); va_end( params ); bee->ui->chat_log( bee, c, text ); g_free( text ); } void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at ) { struct im_connection *ic = c->ic; bee_t *bee = ic->bee; bee_user_t *bu; if( !bee->ui->chat_topic ) return; if( who == NULL) bu = NULL; else if( g_strcasecmp( who, ic->acc->user ) == 0 ) bu = bee->user; else bu = bee_user_by_handle( bee, ic, who ); if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) || ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) ) strip_html( topic ); bee->ui->chat_topic( bee, c, topic, bu ); } void imcb_chat_add_buddy( struct groupchat *c, const char *handle ) { struct im_connection *ic = c->ic; bee_t *bee = ic->bee; bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); gboolean me; if( set_getbool( &c->ic->bee->set, "debug" ) ) imcb_log( c->ic, "User %s added to conversation %p", handle, c ); me = ic->acc->prpl->handle_cmp( handle, ic->acc->user ) == 0; /* Most protocols allow people to join, even when they're not in your contact list. Try to handle that here */ if( !me && !bu ) bu = bee_user_new( bee, ic, handle, BEE_USER_LOCAL ); /* Add the handle to the room userlist */ /* TODO: Use bu instead of a string */ c->in_room = g_list_append( c->in_room, g_strdup( handle ) ); if( bee->ui->chat_add_user ) bee->ui->chat_add_user( bee, c, me ? bee->user : bu ); if( me ) c->joined = 1; } void imcb_chat_remove_buddy( struct groupchat *c, const char *handle, const char *reason ) { struct im_connection *ic = c->ic; bee_t *bee = ic->bee; bee_user_t *bu = NULL; if( set_getbool( &bee->set, "debug" ) ) imcb_log( ic, "User %s removed from conversation %p (%s)", handle, c, reason ? reason : "" ); /* It might be yourself! */ if( g_strcasecmp( handle, ic->acc->user ) == 0 ) { if( c->joined == 0 ) return; bu = bee->user; c->joined = 0; } else { bu = bee_user_by_handle( bee, ic, handle ); } if( bee->ui->chat_remove_user && bu ) bee->ui->chat_remove_user( bee, c, bu ); } int bee_chat_msg( bee_t *bee, struct groupchat *c, const char *msg, int flags ) { struct im_connection *ic = c->ic; char *buf = NULL; if( ( ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "", 6 ) != 0 ) ) { buf = escape_html( msg ); msg = buf; } else buf = g_strdup( msg ); ic->acc->prpl->chat_msg( c, buf, flags ); g_free( buf ); return 1; } struct groupchat *bee_chat_by_title( bee_t *bee, struct im_connection *ic, const char *title ) { struct groupchat *c; GSList *l; for( l = ic->groupchats; l; l = l->next ) { c = l->data; if( strcmp( c->title, title ) == 0 ) return c; } return NULL; } void imcb_chat_invite( struct im_connection *ic, const char *name, const char *who, const char *msg ) { bee_user_t *bu = bee_user_by_handle( ic->bee, ic, who ); if( bu && ic->bee->ui->chat_invite ) ic->bee->ui->chat_invite( ic->bee, bu, name, msg ); } bitlbee-3.2.1/protocols/bee.h0000644000175000017500000002063312245474076015452 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2010 Wilmer van der Gaast and others * \********************************************************************/ /* Stuff to handle, save and search buddies */ /* 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 __BEE_H__ #define __BEE_H__ struct bee_ui_funcs; struct groupchat; typedef struct bee { /* Settings. See set.h for how these work. The UI can add its own settings here. */ struct set *set; GSList *users; /* struct bee_user */ GSList *groups; /* struct bee_group */ struct account *accounts; /* TODO(wilmer): Use GSList here too? */ /* Symbolic, to refer to the local user (who has no real bee_user object). Not to be used by anything except so far imcb_chat_add/ remove_buddy(). */ struct bee_user *user; /* Fill in the callbacks for events you care about. */ const struct bee_ui_funcs *ui; /* And this one will be passed to every callback for any state the UI may want to keep. */ void *ui_data; } bee_t; bee_t *bee_new(); void bee_free( bee_t *b ); /* TODO(wilmer): Kill at least the OPT_ flags that have an equivalent here. */ typedef enum { BEE_USER_ONLINE = 1, /* Compatibility with old OPT_LOGGED_IN flag */ BEE_USER_AWAY = 4, /* Compatibility with old OPT_AWAY flag */ BEE_USER_MOBILE = 8, /* Compatibility with old OPT_MOBILE flag */ BEE_USER_LOCAL = 256, /* Locally-added contacts (not in real contact list) */ } bee_user_flags_t; typedef struct bee_user { struct im_connection *ic; char *handle; char *fullname; char *nick; struct bee_group *group; bee_user_flags_t flags; char *status; /* NULL means available, anything else is an away state. */ char *status_msg; /* Status and/or away message. */ /* Set using imcb_buddy_times(). */ time_t login_time, idle_time; bee_t *bee; void *ui_data; void *data; /* Can be used by the IM module. */ } bee_user_t; /* This one's mostly used so save space and make it easier (cheaper) to compare groups of contacts, etc. */ typedef struct bee_group { char *key; /* Lower case version of the name. */ char *name; } bee_group_t; typedef struct bee_ui_funcs { void (*imc_connected)( struct im_connection *ic ); void (*imc_disconnected)( struct im_connection *ic ); gboolean (*user_new)( bee_t *bee, struct bee_user *bu ); gboolean (*user_free)( bee_t *bee, struct bee_user *bu ); /* Set the fullname first, then call this one to notify the UI. */ gboolean (*user_fullname)( bee_t *bee, bee_user_t *bu ); gboolean (*user_nick_hint)( bee_t *bee, bee_user_t *bu, const char *hint ); /* Notify the UI when an existing user is moved between groups. */ gboolean (*user_group)( bee_t *bee, bee_user_t *bu ); /* State info is already updated, old is provided in case the UI needs a diff. */ gboolean (*user_status)( bee_t *bee, struct bee_user *bu, struct bee_user *old ); /* On every incoming message. sent_at = 0 means unknown. */ gboolean (*user_msg)( bee_t *bee, bee_user_t *bu, const char *msg, time_t sent_at ); /* Flags currently defined (OPT_TYPING/THINKING) in nogaim.h. */ gboolean (*user_typing)( bee_t *bee, bee_user_t *bu, guint32 flags ); /* CTCP-like stuff (buddy action) response */ gboolean (*user_action_response)( bee_t *bee, bee_user_t *bu, const char *action, char * const args[], void *data ); /* Called at creation time. Don't show to the user until s/he is added using chat_add_user(). UI state can be stored via c->data. */ gboolean (*chat_new)( bee_t *bee, struct groupchat *c ); gboolean (*chat_free)( bee_t *bee, struct groupchat *c ); /* System messages of any kind. */ gboolean (*chat_log)( bee_t *bee, struct groupchat *c, const char *text ); gboolean (*chat_msg)( bee_t *bee, struct groupchat *c, bee_user_t *bu, const char *msg, time_t sent_at ); gboolean (*chat_add_user)( bee_t *bee, struct groupchat *c, bee_user_t *bu ); gboolean (*chat_remove_user)( bee_t *bee, struct groupchat *c, bee_user_t *bu ); gboolean (*chat_topic)( bee_t *bee, struct groupchat *c, const char *new_topic, bee_user_t *bu ); gboolean (*chat_name_hint)( bee_t *bee, struct groupchat *c, const char *name ); gboolean (*chat_invite)( bee_t *bee, bee_user_t *bu, const char *name, const char *msg ); struct file_transfer* (*ft_in_start)( bee_t *bee, bee_user_t *bu, const char *file_name, size_t file_size ); gboolean (*ft_out_start)( struct im_connection *ic, struct file_transfer *ft ); void (*ft_close)( struct im_connection *ic, struct file_transfer *ft ); void (*ft_finished)( struct im_connection *ic, struct file_transfer *ft ); } bee_ui_funcs_t; /* bee.c */ bee_t *bee_new(); void bee_free( bee_t *b ); /* bee_user.c */ bee_user_t *bee_user_new( bee_t *bee, struct im_connection *ic, const char *handle, bee_user_flags_t flags ); int bee_user_free( bee_t *bee, bee_user_t *bu ); bee_user_t *bee_user_by_handle( bee_t *bee, struct im_connection *ic, const char *handle ); int bee_user_msg( bee_t *bee, bee_user_t *bu, const char *msg, int flags ); bee_group_t *bee_group_by_name( bee_t *bee, const char *name, gboolean creat ); void bee_group_free( bee_t *bee ); /* Callbacks from IM modules to core: */ /* Buddy activity */ /* To manipulate the status of a handle. * - flags can be |='d with OPT_* constants. You will need at least: * OPT_LOGGED_IN and OPT_AWAY. * - 'state' and 'message' can be NULL */ G_MODULE_EXPORT void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message ); G_MODULE_EXPORT void imcb_buddy_status_msg( struct im_connection *ic, const char *handle, const char *message ); G_MODULE_EXPORT void imcb_buddy_times( struct im_connection *ic, const char *handle, time_t login, time_t idle ); /* Call when a handle says something. 'flags' and 'sent_at may be just 0. */ G_MODULE_EXPORT void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, guint32 flags, time_t sent_at ); /* bee_chat.c */ /* These two functions are to create a group chat. * - imcb_chat_new(): the 'handle' parameter identifies the chat, like the * channel name on IRC. * - After you have a groupchat pointer, you should add the handles, finally * the user her/himself. At that point the group chat will be visible to the * user, too. */ G_MODULE_EXPORT struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle ); G_MODULE_EXPORT void imcb_chat_name_hint( struct groupchat *c, const char *name ); G_MODULE_EXPORT void imcb_chat_free( struct groupchat *c ); /* To tell BitlBee 'who' said 'msg' in 'c'. 'flags' and 'sent_at' can be 0. */ G_MODULE_EXPORT void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, guint32 flags, time_t sent_at ); /* System messages specific to a groupchat, so they can be displayed in the right context. */ G_MODULE_EXPORT void imcb_chat_log( struct groupchat *c, char *format, ... ); /* To tell BitlBee 'who' changed the topic of 'c' to 'topic'. */ G_MODULE_EXPORT void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at ); G_MODULE_EXPORT void imcb_chat_add_buddy( struct groupchat *c, const char *handle ); /* To remove a handle from a group chat. Reason can be NULL. */ G_MODULE_EXPORT void imcb_chat_remove_buddy( struct groupchat *c, const char *handle, const char *reason ); G_MODULE_EXPORT int bee_chat_msg( bee_t *bee, struct groupchat *c, const char *msg, int flags ); G_MODULE_EXPORT struct groupchat *bee_chat_by_title( bee_t *bee, struct im_connection *ic, const char *title ); G_MODULE_EXPORT void imcb_chat_invite( struct im_connection *ic, const char *name, const char *who, const char *msg ); #endif /* __BEE_H__ */ bitlbee-3.2.1/protocols/account.c0000644000175000017500000002510512245474076016345 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 Wilmer van der Gaast and others * \********************************************************************/ /* Account management functions */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" #include "account.h" static char *set_eval_nick_source( set_t *set, char *value ); account_t *account_add( bee_t *bee, struct prpl *prpl, char *user, char *pass ) { account_t *a; set_t *s; char tag[strlen(prpl->name)+10]; if( bee->accounts ) { for( a = bee->accounts; a->next; a = a->next ); a = a->next = g_new0( account_t, 1 ); } else { bee->accounts = a = g_new0 ( account_t, 1 ); } a->prpl = prpl; a->user = g_strdup( user ); a->pass = g_strdup( pass ); a->auto_connect = 1; a->bee = bee; s = set_add( &a->set, "auto_connect", "true", set_eval_account, a ); s->flags |= SET_NOSAVE; s = set_add( &a->set, "auto_reconnect", "true", set_eval_bool, a ); s = set_add( &a->set, "nick_format", NULL, NULL, a ); s->flags |= SET_NULL_OK; s = set_add( &a->set, "nick_source", "handle", set_eval_nick_source, a ); s->flags |= SET_NOSAVE; /* Just for bw compatibility! */ s = set_add( &a->set, "password", NULL, set_eval_account, a ); s->flags |= SET_NOSAVE | SET_NULL_OK | SET_PASSWORD; s = set_add( &a->set, "tag", NULL, set_eval_account, a ); s->flags |= SET_NOSAVE; s = set_add( &a->set, "username", NULL, set_eval_account, a ); s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY; set_setstr( &a->set, "username", user ); /* Hardcode some more clever tag guesses. */ strcpy( tag, prpl->name ); if( strcmp( prpl->name, "oscar" ) == 0 ) { if( isdigit( a->user[0] ) ) strcpy( tag, "icq" ); else strcpy( tag, "aim" ); } else if( strcmp( prpl->name, "jabber" ) == 0 ) { if( strstr( a->user, "@gmail.com" ) || strstr( a->user, "@googlemail.com" ) ) strcpy( tag, "gtalk" ); else if( strstr( a->user, "@chat.facebook.com" ) ) strcpy( tag, "fb" ); } if( account_by_tag( bee, tag ) ) { char *numpos = tag + strlen( tag ); int i; for( i = 2; i < 10000; i ++ ) { sprintf( numpos, "%d", i ); if( !account_by_tag( bee, tag ) ) break; } } set_setstr( &a->set, "tag", tag ); a->nicks = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, g_free ); /* This function adds some more settings (and might want to do more things that have to be done now, although I can't think of anything. */ if( prpl->init ) prpl->init( a ); s = set_add( &a->set, "away", NULL, set_eval_account, a ); s->flags |= SET_NULL_OK; if( a->flags & ACC_FLAG_STATUS_MESSAGE ) { s = set_add( &a->set, "status", NULL, set_eval_account, a ); s->flags |= SET_NULL_OK; } return a; } char *set_eval_account( set_t *set, char *value ) { account_t *acc = set->data; /* Double-check: We refuse to edit on-line accounts. */ if( set->flags & ACC_SET_OFFLINE_ONLY && acc->ic ) return SET_INVALID; if( strcmp( set->key, "server" ) == 0 ) { g_free( acc->server ); if( value && *value ) { acc->server = g_strdup( value ); return value; } else { acc->server = g_strdup( set->def ); return g_strdup( set->def ); } } else if( strcmp( set->key, "username" ) == 0 ) { g_free( acc->user ); acc->user = g_strdup( value ); return value; } else if( strcmp( set->key, "password" ) == 0 ) { /* set -del should be allowed now, but I don't want to have any NULL pointers to have to deal with. */ if( !value ) value = ""; g_free( acc->pass ); acc->pass = g_strdup( value ); return NULL; /* password shouldn't be visible in plaintext! */ } else if( strcmp( set->key, "tag" ) == 0 ) { account_t *oa; /* Enforce uniqueness. */ if( ( oa = account_by_tag( acc->bee, value ) ) && oa != acc ) return SET_INVALID; g_free( acc->tag ); acc->tag = g_strdup( value ); return value; } else if( strcmp( set->key, "auto_connect" ) == 0 ) { if( !is_bool( value ) ) return SET_INVALID; acc->auto_connect = bool2int( value ); return value; } else if( strcmp( set->key, "away" ) == 0 || strcmp( set->key, "status" ) == 0 ) { if( acc->ic && acc->ic->flags & OPT_LOGGED_IN ) { /* If we're currently on-line, set the var now already (bit of a hack) and send an update. */ g_free( set->value ); set->value = g_strdup( value ); imc_away_send_update( acc->ic ); } return value; } return SET_INVALID; } /* For bw compatibility, have this write-only setting. */ static char *set_eval_nick_source( set_t *set, char *value ) { account_t *a = set->data; if( strcmp( value, "full_name" ) == 0 ) set_setstr( &a->set, "nick_format", "%full_name" ); else if( strcmp( value, "first_name" ) == 0 ) set_setstr( &a->set, "nick_format", "%first_name" ); else set_setstr( &a->set, "nick_format", "%-@nick" ); return value; } account_t *account_get( bee_t *bee, const char *id ) { account_t *a, *ret = NULL; char *handle, *s; int nr; /* Tags get priority above anything else. */ if( ( a = account_by_tag( bee, id ) ) ) return a; /* This checks if the id string ends with (...) */ if( ( handle = strchr( id, '(' ) ) && ( s = strchr( handle, ')' ) ) && s[1] == 0 ) { struct prpl *proto; *s = *handle = 0; handle ++; if( ( proto = find_protocol( id ) ) ) { for( a = bee->accounts; a; a = a->next ) if( a->prpl == proto && a->prpl->handle_cmp( handle, a->user ) == 0 ) ret = a; } /* Restore the string. */ handle --; *handle = '('; *s = ')'; if( ret ) return ret; } if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 ) { for( a = bee->accounts; a; a = a->next ) if( ( nr-- ) == 0 ) return( a ); return( NULL ); } for( a = bee->accounts; a; a = a->next ) { if( g_strcasecmp( id, a->prpl->name ) == 0 ) { if( !ret ) ret = a; else return( NULL ); /* We don't want to match more than one... */ } else if( strstr( a->user, id ) ) { if( !ret ) ret = a; else return( NULL ); } } return( ret ); } account_t *account_by_tag( bee_t *bee, const char *tag ) { account_t *a; for( a = bee->accounts; a; a = a->next ) if( a->tag && g_strcasecmp( tag, a->tag ) == 0 ) return a; return NULL; } void account_del( bee_t *bee, account_t *acc ) { account_t *a, *l = NULL; if( acc->ic ) /* Caller should have checked, accounts still in use can't be deleted. */ return; for( a = bee->accounts; a; a = (l=a)->next ) if( a == acc ) { if( l ) l->next = a->next; else bee->accounts = a->next; /** FIXME for( c = bee->chatrooms; c; c = nc ) { nc = c->next; if( acc == c->acc ) chat_del( bee, c ); } */ while( a->set ) set_del( &a->set, a->set->key ); g_hash_table_destroy( a->nicks ); g_free( a->tag ); g_free( a->user ); g_free( a->pass ); g_free( a->server ); if( a->reconnect ) /* This prevents any reconnect still queued to happen */ cancel_auto_reconnect( a ); g_free( a ); break; } } static gboolean account_on_timeout( gpointer d, gint fd, b_input_condition cond ); void account_on( bee_t *bee, account_t *a ) { if( a->ic ) { /* Trying to enable an already-enabled account */ return; } cancel_auto_reconnect( a ); a->reconnect = 0; a->prpl->login( a ); if( a->ic && !( a->ic->flags & ( OPT_SLOW_LOGIN | OPT_LOGGED_IN ) ) ) a->ic->keepalive = b_timeout_add( 120000, account_on_timeout, a->ic ); } void account_off( bee_t *bee, account_t *a ) { imc_logout( a->ic, FALSE ); a->ic = NULL; if( a->reconnect ) { /* Shouldn't happen */ cancel_auto_reconnect( a ); } } static gboolean account_on_timeout( gpointer d, gint fd, b_input_condition cond ) { struct im_connection *ic = d; if( !( ic->flags & ( OPT_SLOW_LOGIN | OPT_LOGGED_IN ) ) ) { imcb_error( ic, "Connection timeout" ); imc_logout( ic, TRUE ); } return FALSE; } struct account_reconnect_delay { int start; char op; int step; int max; }; int account_reconnect_delay_parse( char *value, struct account_reconnect_delay *p ) { memset( p, 0, sizeof( *p ) ); /* A whole day seems like a sane "maximum maximum". */ p->max = 86400; /* Format: /[0-9]+([*+][0-9]+(<[0-9+])?)?/ */ while( *value && isdigit( *value ) ) p->start = p->start * 10 + *value++ - '0'; /* Sure, call me evil for implementing my own fscanf here, but it's dead simple and I immediately know where to continue parsing. */ if( *value == 0 ) /* If the string ends now, the delay is constant. */ return 1; else if( *value != '+' && *value != '*' ) /* Otherwise allow either a + or a * */ return 0; p->op = *value++; /* + or * the delay by this number every time. */ while( *value && isdigit( *value ) ) p->step = p->step * 10 + *value++ - '0'; if( *value == 0 ) /* Use the default maximum (one day). */ return 1; else if( *value != '<' ) return 0; p->max = 0; value ++; while( *value && isdigit( *value ) ) p->max = p->max * 10 + *value++ - '0'; return p->max > 0; } char *set_eval_account_reconnect_delay( set_t *set, char *value ) { struct account_reconnect_delay p; return account_reconnect_delay_parse( value, &p ) ? value : SET_INVALID; } int account_reconnect_delay( account_t *a ) { char *setting = set_getstr( &a->bee->set, "auto_reconnect_delay" ); struct account_reconnect_delay p; if( account_reconnect_delay_parse( setting, &p ) ) { if( a->auto_reconnect_delay == 0 ) a->auto_reconnect_delay = p.start; else if( p.op == '+' ) a->auto_reconnect_delay += p.step; else if( p.op == '*' ) a->auto_reconnect_delay *= p.step; if( a->auto_reconnect_delay > p.max ) a->auto_reconnect_delay = p.max; } else { a->auto_reconnect_delay = 0; } return a->auto_reconnect_delay; } bitlbee-3.2.1/protocols/yahoo/0000755000175000017500000000000012245477444015663 5ustar wilmerwilmerbitlbee-3.2.1/protocols/yahoo/Makefile0000644000175000017500000000157112245474076017325 0ustar wilmerwilmer########################### ## Makefile for BitlBee ## ## ## ## Copyright 2002 Lintux ## ########################### ### DEFINITIONS -include ../../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)protocols/yahoo/ endif # [SH] Program variables objects = yahoo.o libyahoo2.o yahoo_httplib.o yahoo_util.o CFLAGS += -DSTDC_HEADERS -DHAVE_STRING_H -DHAVE_STRCHR -DHAVE_MEMCPY -DHAVE_GLIB LFLAGS += -r # [SH] Phony targets all: yahoo_mod.o check: all lcov: check gcov: gcov *.c .PHONY: all clean distclean clean: rm -f *.o core distclean: clean rm -rf .depend ### MAIN PROGRAM $(objects): ../../Makefile.settings Makefile $(objects): %.o: $(_SRCDIR_)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $(CFLAGS_BITLBEE) $< -o $@ yahoo_mod.o: $(objects) @echo '*' Linking yahoo_mod.o @$(LD) $(LFLAGS) $(objects) -o yahoo_mod.o -include .depend/*.d bitlbee-3.2.1/protocols/yahoo/yahoo_debug.h0000644000175000017500000000276012245474076020324 0ustar wilmerwilmer/* * libyahoo2: yahoo_debug.h * * Copyright (C) 2002-2004, Philip S Tellis * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ extern int yahoo_log_message(char *fmt, ...); #define NOTICE(x) if(yahoo_get_log_level() >= YAHOO_LOG_NOTICE) { yahoo_log_message x; yahoo_log_message("\n"); } #define LOG(x) if(yahoo_get_log_level() >= YAHOO_LOG_INFO) { yahoo_log_message("%s:%d: ", __FILE__, __LINE__); \ yahoo_log_message x; \ yahoo_log_message("\n"); } #define WARNING(x) if(yahoo_get_log_level() >= YAHOO_LOG_WARNING) { yahoo_log_message("%s:%d: warning: ", __FILE__, __LINE__); \ yahoo_log_message x; \ yahoo_log_message("\n"); } #define DEBUG_MSG(x) if(yahoo_get_log_level() >= YAHOO_LOG_DEBUG) { yahoo_log_message("%s:%d: debug: ", __FILE__, __LINE__); \ yahoo_log_message x; \ yahoo_log_message("\n"); } bitlbee-3.2.1/protocols/yahoo/libyahoo2.c0000644000175000017500000037237412245474076017735 0ustar wilmerwilmer/* * libyahoo2: libyahoo2.c * * Some code copyright (C) 2002-2004, Philip S Tellis * YMSG16 code copyright (C) 2009, * Siddhesh Poyarekar * * Yahoo Search copyright (C) 2003, Konstantin Klyagin * * Much of this code was taken and adapted from the yahoo module for * gaim released under the GNU GPL. This code is also released under the * GNU GPL. * * This code is derivitive of Gaim * copyright (C) 1998-1999, Mark Spencer * 1998-1999, Adam Fritzler * 1998-2002, Rob Flynn * 2000-2002, Eric Warmenhoven * 2001-2002, Brian Macke * 2001, Anand Biligiri S * 2001, Valdis Kletnieks * 2002, Sean Egan * 2002, Toby Gray * * This library also uses code from other libraries, namely: * Portions from libfaim copyright 1998, 1999 Adam Fritzler * * Portions of Sylpheed copyright 2000-2002 Hiroyuki Yamamoto * * * YMSG16 authentication code based mostly on write-up at: * http://www.carbonize.co.uk/ymsg16.html * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #ifndef _WIN32 #include #endif #include #include #include #if STDC_HEADERS # include #else # if !HAVE_STRCHR # define strchr index # define strrchr rindex # endif char *strchr (), *strrchr (); # if !HAVE_MEMCPY # define memcpy(d, s, n) bcopy ((s), (d), (n)) # define memmove(d, s, n) bcopy ((s), (d), (n)) # endif #endif #include #ifdef __MINGW32__ # include #endif #include #include #include "sha1.h" #include "md5.h" #include "yahoo2.h" #include "yahoo_httplib.h" #include "yahoo_util.h" #include "yahoo2_callbacks.h" #include "yahoo_debug.h" #if defined(__MINGW32__) && !defined(HAVE_GLIB) #define snprintf _snprintf #define vsnprintf _vsnprintf #endif #include "base64.h" #include "http_client.h" #ifdef USE_STRUCT_CALLBACKS struct yahoo_callbacks *yc = NULL; void yahoo_register_callbacks(struct yahoo_callbacks *tyc) { yc = tyc; } #define YAHOO_CALLBACK(x) yc->x #else #define YAHOO_CALLBACK(x) x #endif static int yahoo_send_data(void *fd, void *data, int len); static void _yahoo_http_connected(int id, void *fd, int error, void *data); static void yahoo_connected(void *fd, int error, void *data); int yahoo_log_message(char *fmt, ...) { char out[1024]; va_list ap; va_start(ap, fmt); vsnprintf(out, sizeof(out), fmt, ap); va_end(ap); return YAHOO_CALLBACK(ext_yahoo_log) ("%s", out); } static enum yahoo_log_level log_level = YAHOO_LOG_NONE; enum yahoo_log_level yahoo_get_log_level() { return log_level; } int yahoo_set_log_level(enum yahoo_log_level level) { enum yahoo_log_level l = log_level; log_level = level; return l; } /* default values for servers */ static char *default_pager_hosts[] = { "scs.msg.yahoo.com", "scsa.msg.yahoo.com", "scsb.msg.yahoo.com", "scsc.msg.yahoo.com", NULL}; static int pager_port = 5050; static int fallback_ports[] = { 23, 25, 80, 20, 119, 8001, 8002, 5050, 0 }; static char filetransfer_host[] = "filetransfer.msg.yahoo.com"; static int filetransfer_port = 80; static char webcam_host[] = "webcam.yahoo.com"; static int webcam_port = 5100; static char webcam_description[] = ""; static char local_host[] = ""; static int conn_type = Y_WCM_DSL; static char profile_url[] = "http://profiles.yahoo.com/"; struct connect_callback_data { struct yahoo_data *yd; int tag; int i; int server_i; }; struct yahoo_pair { int key; char *value; }; struct yahoo_packet { unsigned short int service; unsigned int status; unsigned int id; YList *hash; }; struct yahoo_search_state { int lsearch_type; char *lsearch_text; int lsearch_gender; int lsearch_agerange; int lsearch_photo; int lsearch_yahoo_only; int lsearch_nstart; int lsearch_nfound; int lsearch_ntotal; }; struct data_queue { unsigned char *queue; int len; }; struct yahoo_input_data { struct yahoo_data *yd; struct yahoo_webcam *wcm; struct yahoo_webcam_data *wcd; struct yahoo_search_state *ys; void *fd; enum yahoo_connection_type type; unsigned char *rxqueue; int rxlen; int read_tag; YList *txqueues; int write_tag; }; struct yahoo_server_settings { char *pager_host; int pager_port; char *filetransfer_host; int filetransfer_port; char *webcam_host; int webcam_port; char *webcam_description; char *local_host; int conn_type; char **pager_host_list; }; static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over); static void yahoo_process_filetransfer(struct yahoo_input_data *yid, struct yahoo_packet *pkt); static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid, struct yahoo_packet *pkt); static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid, struct yahoo_packet *pkt); static void yahoo_https_auth(struct yahoo_input_data *yid, const char *seed, const char *sn); static void *_yahoo_default_server_settings() { struct yahoo_server_settings *yss = y_new0(struct yahoo_server_settings, 1); /* Give preference to the default host list * Make sure that only one of the two is set at any time */ yss->pager_host = NULL; yss->pager_host_list = default_pager_hosts; yss->pager_port = pager_port; yss->filetransfer_host = strdup(filetransfer_host); yss->filetransfer_port = filetransfer_port; yss->webcam_host = strdup(webcam_host); yss->webcam_port = webcam_port; yss->webcam_description = strdup(webcam_description); yss->local_host = strdup(local_host); yss->conn_type = conn_type; return yss; } static void *_yahoo_assign_server_settings(va_list ap) { struct yahoo_server_settings *yss = _yahoo_default_server_settings(); char *key; char *svalue; int nvalue; char **pvalue; while (1) { key = va_arg(ap, char *); if (key == NULL) break; if (!strcmp(key, "pager_host")) { svalue = va_arg(ap, char *); free(yss->pager_host); yss->pager_host = strdup(svalue); yss->pager_host_list = NULL; } else if (!strcmp(key, "pager_host_list")) { pvalue = va_arg(ap, char **); yss->pager_host_list = pvalue; free(yss->pager_host); yss->pager_host = NULL; } else if (!strcmp(key, "pager_port")) { nvalue = va_arg(ap, int); yss->pager_port = nvalue; } else if (!strcmp(key, "filetransfer_host")) { svalue = va_arg(ap, char *); free(yss->filetransfer_host); yss->filetransfer_host = strdup(svalue); } else if (!strcmp(key, "filetransfer_port")) { nvalue = va_arg(ap, int); yss->filetransfer_port = nvalue; } else if (!strcmp(key, "webcam_host")) { svalue = va_arg(ap, char *); free(yss->webcam_host); yss->webcam_host = strdup(svalue); } else if (!strcmp(key, "webcam_port")) { nvalue = va_arg(ap, int); yss->webcam_port = nvalue; } else if (!strcmp(key, "webcam_description")) { svalue = va_arg(ap, char *); free(yss->webcam_description); yss->webcam_description = strdup(svalue); } else if (!strcmp(key, "local_host")) { svalue = va_arg(ap, char *); free(yss->local_host); yss->local_host = strdup(svalue); } else if (!strcmp(key, "conn_type")) { nvalue = va_arg(ap, int); yss->conn_type = nvalue; } else { WARNING(("Unknown key passed to yahoo_init, " "perhaps you didn't terminate the list " "with NULL")); } } return yss; } static void yahoo_free_server_settings(struct yahoo_server_settings *yss) { if (!yss) return; free(yss->pager_host); free(yss->filetransfer_host); free(yss->webcam_host); free(yss->webcam_description); free(yss->local_host); free(yss); } static YList *conns = NULL; static YList *inputs = NULL; static int last_id = 0; static void add_to_list(struct yahoo_data *yd) { conns = y_list_prepend(conns, yd); } static struct yahoo_data *find_conn_by_id(int id) { YList *l; for (l = conns; l; l = y_list_next(l)) { struct yahoo_data *yd = l->data; if (yd->client_id == id) return yd; } return NULL; } static void del_from_list(struct yahoo_data *yd) { conns = y_list_remove(conns, yd); } /* call repeatedly to get the next one */ /* static struct yahoo_input_data * find_input_by_id(int id) { YList *l; for(l = inputs; l; l = y_list_next(l)) { struct yahoo_input_data *yid = l->data; if(yid->yd->client_id == id) return yid; } return NULL; } */ #if 0 static struct yahoo_input_data *find_input_by_id_and_webcam_user(int id, const char *who) { YList *l; LOG(("find_input_by_id_and_webcam_user")); for (l = inputs; l; l = y_list_next(l)) { struct yahoo_input_data *yid = l->data; if (yid->type == YAHOO_CONNECTION_WEBCAM && yid->yd->client_id == id && yid->wcm && ((who && yid->wcm->user && !strcmp(who, yid->wcm->user)) || !(yid->wcm->user && !who))) return yid; } return NULL; } #endif static struct yahoo_input_data *find_input_by_id_and_type(int id, enum yahoo_connection_type type) { YList *l; LOG(("find_input_by_id_and_type")); for (l = inputs; l; l = y_list_next(l)) { struct yahoo_input_data *yid = l->data; if (yid->type == type && yid->yd->client_id == id) return yid; } return NULL; } static struct yahoo_input_data *find_input_by_id_and_fd(int id, void *fd) { YList *l; LOG(("find_input_by_id_and_fd")); for (l = inputs; l; l = y_list_next(l)) { struct yahoo_input_data *yid = l->data; if (yid->fd == fd && yid->yd->client_id == id) return yid; } return NULL; } static int count_inputs_with_id(int id) { int c = 0; YList *l; LOG(("counting %d", id)); for (l = inputs; l; l = y_list_next(l)) { struct yahoo_input_data *yid = l->data; if (yid->yd->client_id == id) c++; } LOG(("%d", c)); return c; } /* Free a buddy list */ static void yahoo_free_buddies(YList *list) { YList *l; for (l = list; l; l = l->next) { struct yahoo_buddy *bud = l->data; if (!bud) continue; FREE(bud->group); FREE(bud->id); FREE(bud->real_name); if (bud->yab_entry) { FREE(bud->yab_entry->fname); FREE(bud->yab_entry->lname); FREE(bud->yab_entry->nname); FREE(bud->yab_entry->id); FREE(bud->yab_entry->email); FREE(bud->yab_entry->hphone); FREE(bud->yab_entry->wphone); FREE(bud->yab_entry->mphone); FREE(bud->yab_entry); } FREE(bud); l->data = bud = NULL; } y_list_free(list); } /* Free an identities list */ static void yahoo_free_identities(YList *list) { while (list) { YList *n = list; FREE(list->data); list = y_list_remove_link(list, list); y_list_free_1(n); } } /* Free webcam data */ static void yahoo_free_webcam(struct yahoo_webcam *wcm) { if (wcm) { FREE(wcm->user); FREE(wcm->server); FREE(wcm->key); FREE(wcm->description); FREE(wcm->my_ip); } FREE(wcm); } static void yahoo_free_data(struct yahoo_data *yd) { FREE(yd->user); FREE(yd->password); FREE(yd->cookie_y); FREE(yd->cookie_t); FREE(yd->cookie_b); FREE(yd->cookie_c); FREE(yd->login_cookie); FREE(yd->login_id); yahoo_free_buddies(yd->buddies); yahoo_free_buddies(yd->ignore); yahoo_free_identities(yd->identities); yahoo_free_server_settings(yd->server_settings); FREE(yd); } #define YAHOO_PACKET_HDRLEN (4 + 2 + 2 + 2 + 2 + 4 + 4) static struct yahoo_packet *yahoo_packet_new(enum yahoo_service service, enum ypacket_status status, int id) { struct yahoo_packet *pkt = y_new0(struct yahoo_packet, 1); pkt->service = service; pkt->status = status; pkt->id = id; return pkt; } static void yahoo_packet_hash(struct yahoo_packet *pkt, int key, const char *value) { struct yahoo_pair *pair = y_new0(struct yahoo_pair, 1); pair->key = key; pair->value = strdup(value); pkt->hash = y_list_append(pkt->hash, pair); } static int yahoo_packet_length(struct yahoo_packet *pkt) { YList *l; int len = 0; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; int tmp = pair->key; do { tmp /= 10; len++; } while (tmp); len += 2; len += strlen(pair->value); len += 2; } return len; } #define yahoo_put16(buf, data) ( \ (*(buf) = (unsigned char)((data)>>8)&0xff), \ (*((buf)+1) = (unsigned char)(data)&0xff), \ 2) #define yahoo_get16(buf) ((((*(buf))&0xff)<<8) + ((*((buf)+1)) & 0xff)) #define yahoo_put32(buf, data) ( \ (*((buf)) = (unsigned char)((data)>>24)&0xff), \ (*((buf)+1) = (unsigned char)((data)>>16)&0xff), \ (*((buf)+2) = (unsigned char)((data)>>8)&0xff), \ (*((buf)+3) = (unsigned char)(data)&0xff), \ 4) #define yahoo_get32(buf) ((((*(buf) )&0xff)<<24) + \ (((*((buf)+1))&0xff)<<16) + \ (((*((buf)+2))&0xff)<< 8) + \ (((*((buf)+3))&0xff))) static void yahoo_packet_read(struct yahoo_packet *pkt, unsigned char *data, int len) { int pos = 0; while (pos + 1 < len) { char *key, *value = NULL; int accept; int x; struct yahoo_pair *pair = y_new0(struct yahoo_pair, 1); key = malloc(len + 1); x = 0; while (pos + 1 < len) { if (data[pos] == 0xc0 && data[pos + 1] == 0x80) break; key[x++] = data[pos++]; } key[x] = 0; pos += 2; pair->key = strtol(key, NULL, 10); free(key); /* Libyahoo2 developer(s) don't seem to have the time to fix this problem, so for now try to work around it: Sometimes we receive an invalid packet with not any more data at this point. I don't know how to handle this in a clean way, but let's hope this is clean enough: */ if (pos + 1 < len) { accept = x; /* if x is 0 there was no key, so don't accept it */ if (accept) value = malloc(len - pos + 1); x = 0; while (pos + 1 < len) { if (data[pos] == 0xc0 && data[pos + 1] == 0x80) break; if (accept) value[x++] = data[pos++]; } if (accept) value[x] = 0; pos += 2; } else { accept = 0; } if (accept) { pair->value = strdup(value); FREE(value); pkt->hash = y_list_append(pkt->hash, pair); DEBUG_MSG(("Key: %d \tValue: %s", pair->key, pair->value)); } else { FREE(pair); } } } static void yahoo_packet_write(struct yahoo_packet *pkt, unsigned char *data) { YList *l; int pos = 0; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; unsigned char buf[100]; snprintf((char *)buf, sizeof(buf), "%d", pair->key); strcpy((char *)data + pos, (char *)buf); pos += strlen((char *)buf); data[pos++] = 0xc0; data[pos++] = 0x80; strcpy((char *)data + pos, pair->value); pos += strlen(pair->value); data[pos++] = 0xc0; data[pos++] = 0x80; } } static void yahoo_dump_unhandled(struct yahoo_packet *pkt) { YList *l; NOTICE(("Service: 0x%02x\tStatus: %d", pkt->service, pkt->status)); for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; NOTICE(("\t%d => %s", pair->key, pair->value)); } } static void yahoo_packet_dump(unsigned char *data, int len) { if (yahoo_get_log_level() >= YAHOO_LOG_DEBUG) { int i; for (i = 0; i < len; i++) { if ((i % 8 == 0) && i) YAHOO_CALLBACK(ext_yahoo_log) (" "); if ((i % 16 == 0) && i) YAHOO_CALLBACK(ext_yahoo_log) ("\n"); YAHOO_CALLBACK(ext_yahoo_log) ("%02x ", data[i]); } YAHOO_CALLBACK(ext_yahoo_log) ("\n"); for (i = 0; i < len; i++) { if ((i % 8 == 0) && i) YAHOO_CALLBACK(ext_yahoo_log) (" "); if ((i % 16 == 0) && i) YAHOO_CALLBACK(ext_yahoo_log) ("\n"); if (isprint(data[i])) YAHOO_CALLBACK(ext_yahoo_log) (" %c ", data[i]); else YAHOO_CALLBACK(ext_yahoo_log) (" . "); } YAHOO_CALLBACK(ext_yahoo_log) ("\n"); } } /* raw bytes in quasi-big-endian order to base 64 string (NUL-terminated) */ static void to_y64(unsigned char *out, const unsigned char *in, int inlen) { base64_encode_real(in, inlen, out, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-"); } static void yahoo_add_to_send_queue(struct yahoo_input_data *yid, void *data, int length) { struct data_queue *tx = y_new0(struct data_queue, 1); tx->queue = y_new0(unsigned char, length); tx->len = length; memcpy(tx->queue, data, length); yid->txqueues = y_list_append(yid->txqueues, tx); if (!yid->write_tag) yid->write_tag = YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd-> client_id, yid->fd, YAHOO_INPUT_WRITE, yid); } static void yahoo_send_packet(struct yahoo_input_data *yid, struct yahoo_packet *pkt, int extra_pad) { int pktlen = yahoo_packet_length(pkt); int len = YAHOO_PACKET_HDRLEN + pktlen; unsigned char *data; int pos = 0; if (yid->fd < 0) return; data = y_new0(unsigned char, len + 1); memcpy(data + pos, "YMSG", 4); pos += 4; pos += yahoo_put16(data + pos, YAHOO_PROTO_VER); /* version [latest 12 0x000c] */ pos += yahoo_put16(data + pos, 0x0000); /* HIWORD pkt length??? */ pos += yahoo_put16(data + pos, pktlen + extra_pad); /* LOWORD pkt length? */ pos += yahoo_put16(data + pos, pkt->service); /* service */ pos += yahoo_put32(data + pos, pkt->status); /* status [4bytes] */ pos += yahoo_put32(data + pos, pkt->id); /* session [4bytes] */ yahoo_packet_write(pkt, data + pos); yahoo_packet_dump(data, len); if (yid->type == YAHOO_CONNECTION_FT) yahoo_send_data(yid->fd, data, len); else yahoo_add_to_send_queue(yid, data, len); FREE(data); } static void yahoo_packet_free(struct yahoo_packet *pkt) { while (pkt->hash) { struct yahoo_pair *pair = pkt->hash->data; YList *tmp; FREE(pair->value); FREE(pair); tmp = pkt->hash; pkt->hash = y_list_remove_link(pkt->hash, pkt->hash); y_list_free_1(tmp); } FREE(pkt); } static int yahoo_send_data(void *fd, void *data, int len) { int ret; int e; if (fd == NULL) return -1; yahoo_packet_dump(data, len); do { ret = YAHOO_CALLBACK(ext_yahoo_write) (fd, data, len); } while (ret == -1 && errno == EINTR); e = errno; if (ret == -1) { LOG(("wrote data: ERR %s", strerror(errno))); } else { LOG(("wrote data: OK")); } errno = e; return ret; } void yahoo_close(int id) { struct yahoo_data *yd = find_conn_by_id(id); if (!yd) return; del_from_list(yd); yahoo_free_data(yd); if (id == last_id) last_id--; } static void yahoo_input_close(struct yahoo_input_data *yid) { inputs = y_list_remove(inputs, yid); LOG(("yahoo_input_close(read)")); YAHOO_CALLBACK(ext_yahoo_remove_handler) (yid->yd->client_id, yid->read_tag); LOG(("yahoo_input_close(write)")); YAHOO_CALLBACK(ext_yahoo_remove_handler) (yid->yd->client_id, yid->write_tag); yid->read_tag = yid->write_tag = 0; if (yid->fd) YAHOO_CALLBACK(ext_yahoo_close) (yid->fd); yid->fd = 0; FREE(yid->rxqueue); if (count_inputs_with_id(yid->yd->client_id) == 0) { LOG(("closing %d", yid->yd->client_id)); yahoo_close(yid->yd->client_id); } yahoo_free_webcam(yid->wcm); if (yid->wcd) FREE(yid->wcd); if (yid->ys) { FREE(yid->ys->lsearch_text); FREE(yid->ys); } FREE(yid); } static int is_same_bud(const void *a, const void *b) { const struct yahoo_buddy *subject = a; const struct yahoo_buddy *object = b; return strcmp(subject->id, object->id); } static char *getcookie(char *rawcookie) { char *cookie = NULL; char *tmpcookie; char *cookieend; if (strlen(rawcookie) < 2) return NULL; tmpcookie = strdup(rawcookie + 2); cookieend = strchr(tmpcookie, ';'); if (cookieend) *cookieend = '\0'; cookie = strdup(tmpcookie); FREE(tmpcookie); /* cookieend=NULL; not sure why this was there since the value is not preserved in the stack -dd */ return cookie; } static char *getlcookie(char *cookie) { char *tmp; char *tmpend; char *login_cookie = NULL; tmpend = strstr(cookie, "n="); if (tmpend) { tmp = strdup(tmpend + 2); tmpend = strchr(tmp, '&'); if (tmpend) *tmpend = '\0'; login_cookie = strdup(tmp); FREE(tmp); } return login_cookie; } static void yahoo_process_notify(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *msg = NULL; char *from = NULL; char *to = NULL; int stat = 0; int accept = 0; char *ind = NULL; YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 4) from = pair->value; if (pair->key == 5) to = pair->value; if (pair->key == 49) msg = pair->value; if (pair->key == 13) stat = atoi(pair->value); if (pair->key == 14) ind = pair->value; if (pair->key == 16) { /* status == -1 */ NOTICE((pair->value)); return; } } if (!msg) return; if (!strncasecmp(msg, "TYPING", strlen("TYPING"))) YAHOO_CALLBACK(ext_yahoo_typing_notify) (yd->client_id, to, from, stat); else if (!strncasecmp(msg, "GAME", strlen("GAME"))) YAHOO_CALLBACK(ext_yahoo_game_notify) (yd->client_id, to, from, stat, ind); else if (!strncasecmp(msg, "WEBCAMINVITE", strlen("WEBCAMINVITE"))) { if (!strcmp(ind, " ")) { YAHOO_CALLBACK(ext_yahoo_webcam_invite) (yd->client_id, to, from); } else { accept = atoi(ind); /* accept the invitation (-1 = deny 1 = accept) */ if (accept < 0) accept = 0; YAHOO_CALLBACK(ext_yahoo_webcam_invite_reply) (yd-> client_id, to, from, accept); } } else LOG(("Got unknown notification: %s", msg)); } static void yahoo_process_conference(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *msg = NULL; char *host = NULL; char *who = NULL; char *room = NULL; char *id = NULL; int utf8 = 0; YList *members = NULL; YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 50) host = pair->value; if (pair->key == 52) { /* invite */ members = y_list_append(members, strdup(pair->value)); } if (pair->key == 53) /* logon */ who = pair->value; if (pair->key == 54) /* decline */ who = pair->value; if (pair->key == 55) /* unavailable (status == 2) */ who = pair->value; if (pair->key == 56) /* logoff */ who = pair->value; if (pair->key == 57) room = pair->value; if (pair->key == 58) /* join message */ msg = pair->value; if (pair->key == 14) /* decline/conf message */ msg = pair->value; if (pair->key == 13) ; if (pair->key == 16) /* error */ msg = pair->value; if (pair->key == 1) /* my id */ id = pair->value; if (pair->key == 3) /* message sender */ who = pair->value; if (pair->key == 97) utf8 = atoi(pair->value); } if (!room) return; if (host) { for (l = members; l; l = l->next) { char *w = l->data; if (!strcmp(w, host)) break; } if (!l) members = y_list_append(members, strdup(host)); } /* invite, decline, join, left, message -> status == 1 */ switch (pkt->service) { case YAHOO_SERVICE_CONFINVITE: if (pkt->status == 2) ; else if (members) YAHOO_CALLBACK(ext_yahoo_got_conf_invite) (yd-> client_id, id, host, room, msg, members); else if (msg) YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id, msg, 0, E_CONFNOTAVAIL); break; case YAHOO_SERVICE_CONFADDINVITE: if (pkt->status == 1) YAHOO_CALLBACK(ext_yahoo_got_conf_invite) (yd-> client_id, id, host, room, msg, members); break; case YAHOO_SERVICE_CONFDECLINE: if (who) YAHOO_CALLBACK(ext_yahoo_conf_userdecline) (yd-> client_id, id, who, room, msg); break; case YAHOO_SERVICE_CONFLOGON: if (who) YAHOO_CALLBACK(ext_yahoo_conf_userjoin) (yd->client_id, id, who, room); break; case YAHOO_SERVICE_CONFLOGOFF: if (who) YAHOO_CALLBACK(ext_yahoo_conf_userleave) (yd->client_id, id, who, room); break; case YAHOO_SERVICE_CONFMSG: if (who) YAHOO_CALLBACK(ext_yahoo_conf_message) (yd->client_id, id, who, room, msg, utf8); break; } } static void yahoo_process_chat(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { char *msg = NULL; char *id = NULL; char *who = NULL; char *room = NULL; char *topic = NULL; YList *members = NULL; struct yahoo_chat_member *currentmember = NULL; int msgtype = 1; int utf8 = 0; int firstjoin = 0; int membercount = 0; int chaterr = 0; YList *l; yahoo_dump_unhandled(pkt); for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 1) { /* My identity */ id = pair->value; } if (pair->key == 104) { /* Room name */ room = pair->value; } if (pair->key == 105) { /* Room topic */ topic = pair->value; } if (pair->key == 108) { /* Number of members in this packet */ membercount = atoi(pair->value); } if (pair->key == 109) { /* message sender */ who = pair->value; if (pkt->service == YAHOO_SERVICE_CHATJOIN) { currentmember = y_new0(struct yahoo_chat_member, 1); currentmember->id = strdup(pair->value); members = y_list_append(members, currentmember); } } if (pair->key == 110) { /* age */ if (pkt->service == YAHOO_SERVICE_CHATJOIN) currentmember->age = atoi(pair->value); } if (pair->key == 113) { /* attribs */ if (pkt->service == YAHOO_SERVICE_CHATJOIN) currentmember->attribs = atoi(pair->value); } if (pair->key == 141) { /* alias */ if (pkt->service == YAHOO_SERVICE_CHATJOIN) currentmember->alias = strdup(pair->value); } if (pair->key == 142) { /* location */ if (pkt->service == YAHOO_SERVICE_CHATJOIN) currentmember->location = strdup(pair->value); } if (pair->key == 130) { /* first join */ firstjoin = 1; } if (pair->key == 117) { /* message */ msg = pair->value; } if (pair->key == 124) { /* Message type */ msgtype = atoi(pair->value); } if (pair->key == 114) { /* message error not sure what all the pair values mean */ /* but -1 means no session in room */ chaterr = atoi(pair->value); } } if (!room) { if (pkt->service == YAHOO_SERVICE_CHATLOGOUT) { /* yahoo originated chat logout */ YAHOO_CALLBACK(ext_yahoo_chat_yahoologout) (yid->yd-> client_id, id); return; } if (pkt->service == YAHOO_SERVICE_COMMENT && chaterr) { YAHOO_CALLBACK(ext_yahoo_chat_yahooerror) (yid->yd-> client_id, id); return; } WARNING(("We didn't get a room name, ignoring packet")); return; } switch (pkt->service) { case YAHOO_SERVICE_CHATJOIN: if (y_list_length(members) != membercount) { WARNING(("Count of members doesn't match No. of members we got")); } if (firstjoin && members) { YAHOO_CALLBACK(ext_yahoo_chat_join) (yid->yd->client_id, id, room, topic, members, yid->fd); } else if (who) { if (y_list_length(members) != 1) { WARNING(("Got more than 1 member on a normal join")); } /* this should only ever have one, but just in case */ while (members) { YList *n = members->next; currentmember = members->data; YAHOO_CALLBACK(ext_yahoo_chat_userjoin) (yid-> yd->client_id, id, room, currentmember); y_list_free_1(members); members = n; } } break; case YAHOO_SERVICE_CHATEXIT: if (who) { YAHOO_CALLBACK(ext_yahoo_chat_userleave) (yid->yd-> client_id, id, room, who); } break; case YAHOO_SERVICE_COMMENT: if (who) { YAHOO_CALLBACK(ext_yahoo_chat_message) (yid->yd-> client_id, id, who, room, msg, msgtype, utf8); } break; } } static void yahoo_process_message(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; YList *l; YList *messages = NULL; struct m { int i_31; int i_32; char *to; char *from; long tm; char *msg; int utf8; char *gunk; } *message = y_new0(struct m, 1); for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 1 || pair->key == 4) { if (!message->from) message->from = pair->value; } else if (pair->key == 5) message->to = pair->value; else if (pair->key == 15) message->tm = strtol(pair->value, NULL, 10); else if (pair->key == 97) message->utf8 = atoi(pair->value); /* This comes when the official client sends us a message */ else if (pair->key == 429) message->gunk = pair->value; /* user message *//* sys message */ else if (pair->key == 14 || pair->key == 16) message->msg = pair->value; else if (pair->key == 31) { if (message->i_31) { messages = y_list_append(messages, message); message = y_new0(struct m, 1); } message->i_31 = atoi(pair->value); } else if (pair->key == 32) message->i_32 = atoi(pair->value); else LOG(("yahoo_process_message: status: %d, key: %d, value: %s", pkt->status, pair->key, pair->value)); } messages = y_list_append(messages, message); for (l = messages; l; l = l->next) { message = l->data; if (pkt->service == YAHOO_SERVICE_SYSMESSAGE) { YAHOO_CALLBACK(ext_yahoo_system_message) (yd->client_id, message->to, message->from, message->msg); } else if (pkt->status <= 2 || pkt->status == 5) { /* Confirm message receipt if we got the gunk */ if(message->gunk) { struct yahoo_packet *outpkt; outpkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE_CONFIRM, YPACKET_STATUS_DEFAULT, 0); yahoo_packet_hash(outpkt, 1, yd->user); yahoo_packet_hash(outpkt, 5, message->from); yahoo_packet_hash(outpkt, 302, "430"); yahoo_packet_hash(outpkt, 430, message->gunk); yahoo_packet_hash(outpkt, 303, "430"); yahoo_packet_hash(outpkt, 450, "0"); yahoo_send_packet(yid, outpkt, 0); yahoo_packet_free(outpkt); } if (!strcmp(message->msg, "")) YAHOO_CALLBACK(ext_yahoo_got_buzz) (yd->client_id, message->to, message->from, message->tm); else YAHOO_CALLBACK(ext_yahoo_got_im) (yd->client_id, message->to, message->from, message->msg, message->tm, pkt->status, message->utf8); } else if (pkt->status == 0xffffffff) { YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id, message->msg, 0, E_SYSTEM); } FREE(message); } y_list_free(messages); } /* * Here's what multi-level packets look like. Data in brackets is the value. * * 3 level: * ======= * * 302 (318) - Beginning level 1 * 300 (318) - Begin level 2 * 302 (319) - End level 2 header * 300 (319) - Begin level 3 * 301 (319) - End level 3 * 303 (319) - End level 2 * 303 (318) - End level 1 * * 2 level: * ======= * * 302 (315) - Beginning level 1 * 300 (315) - Begin level 2 * 301 (315) - End level 2 * 303 (315) - End level 1 * */ static void yahoo_process_status(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { YList *l; struct yahoo_data *yd = yid->yd; struct yahoo_process_status_entry *u; YList *users = 0; if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) { YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, YAHOO_LOGIN_DUPL, NULL); return; } /* * Status updates may be spread accross multiple packets and not * even on buddy boundaries, so keeping some state is important. * So, continue where we left off, and only add a user entry to * the list once it's complete (301-315 End buddy). */ u = yd->half_user; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 300: /* Begin buddy */ if (!strcmp(pair->value, "315") && !u) { u = yd->half_user = y_new0(struct yahoo_process_status_entry, 1); } break; case 301: /* End buddy */ if (!strcmp(pair->value, "315") && u) { /* Sometimes user info comes in an odd format with no "begin buddy" but *with* an "end buddy". Don't add it twice. */ if (!y_list_find(users, u)) users = y_list_prepend(users, u); u = yd->half_user = NULL; } break; case 0: /* we won't actually do anything with this */ NOTICE(("key %d:%s", pair->key, pair->value)); break; case 1: /* we don't get the full buddy list here. */ if (!yd->logged_in) { yd->logged_in = 1; if (yd->current_status < 0) yd->current_status = yd->initial_status; YAHOO_CALLBACK(ext_yahoo_login_response) (yd-> client_id, YAHOO_LOGIN_OK, NULL); } break; case 8: /* how many online buddies we have */ NOTICE(("key %d:%s", pair->key, pair->value)); break; case 7: /* the current buddy */ if (!u) { /* This will only happen in case of a single level message */ u = y_new0(struct yahoo_process_status_entry, 1); users = y_list_prepend(users, u); } u->name = pair->value; break; case 10: /* state */ u->state = strtol(pair->value, NULL, 10); break; case 19: /* custom status message */ u->msg = pair->value; break; case 47: /* is it an away message or not. Not applicable for YMSG16 anymore */ u->away = atoi(pair->value); break; case 137: /* seconds idle */ u->idle = atoi(pair->value); break; case 11: /* this is the buddy's session id */ u->buddy_session = atoi(pair->value); break; case 17: /* in chat? */ u->f17 = atoi(pair->value); break; case 13: /* bitmask, bit 0 = pager, bit 1 = chat, bit 2 = game */ u->flags = atoi(pair->value); break; case 60: /* SMS -> 1 MOBILE USER */ /* sometimes going offline makes this 2, but invisible never sends it */ u->mobile = atoi(pair->value); break; case 138: u->f138 = atoi(pair->value); break; case 184: u->f184 = pair->value; break; case 192: u->f192 = atoi(pair->value); break; case 10001: u->f10001 = atoi(pair->value); break; case 10002: u->f10002 = atoi(pair->value); break; case 198: u->f198 = atoi(pair->value); break; case 197: u->f197 = pair->value; break; case 205: u->f205 = pair->value; break; case 213: u->f213 = atoi(pair->value); break; case 16: /* Custom error message */ YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id, pair->value, 0, E_CUSTOM); break; default: WARNING(("unknown status key %d:%s", pair->key, pair->value)); break; } } while (users) { YList *t = users; struct yahoo_process_status_entry *u = users->data; if (u->name != NULL) { if (pkt->service == YAHOO_SERVICE_LOGOFF /*|| u->flags == 0 No flags for YMSG16 */ ) { YAHOO_CALLBACK(ext_yahoo_status_changed) (yd-> client_id, u->name, YAHOO_STATUS_OFFLINE, NULL, 1, 0, 0); } else { /* Key 47 always seems to be 1 for YMSG16 */ if (!u->state) u->away = 0; else u->away = 1; YAHOO_CALLBACK(ext_yahoo_status_changed) (yd-> client_id, u->name, u->state, u->msg, u->away, u->idle, u->mobile); } } users = y_list_remove_link(users, users); y_list_free_1(t); FREE(u); } } static void yahoo_process_buddy_list(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; YList *l; int last_packet = 0; char *cur_group = NULL; struct yahoo_buddy *newbud = NULL; /* we could be getting multiple packets here */ for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 300: case 301: case 302: break; /* Separators. Our logic does not need them */ case 303: if (318 == atoi(pair->value)) last_packet = 1; break; case 65: cur_group = strdup(pair->value); break; case 7: newbud = y_new0(struct yahoo_buddy, 1); newbud->id = strdup(pair->value); if (cur_group) newbud->group = strdup(cur_group); else if (yd->buddies) { struct yahoo_buddy *lastbud = (struct yahoo_buddy *)y_list_nth(yd-> buddies, y_list_length(yd->buddies) - 1)->data; newbud->group = strdup(lastbud->group); } else newbud->group = strdup("Buddies"); yd->buddies = y_list_append(yd->buddies, newbud); break; } } /* we could be getting multiple packets here */ if (pkt->hash && !last_packet) return; YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id, yd->buddies); /* Logged in */ if (!yd->logged_in) { yd->logged_in = 1; if (yd->current_status < 0) yd->current_status = yd->initial_status; YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, YAHOO_LOGIN_OK, NULL); /* yahoo_set_away(yd->client_id, yd->initial_status, NULL, (yd->initial_status == YAHOO_STATUS_AVAILABLE) ? 0 : 1); yahoo_get_yab(yd->client_id); */ } } static void yahoo_process_list(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; YList *l; /* we could be getting multiple packets here */ for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 89: /* identities */ { char **identities = y_strsplit(pair->value, ",", -1); int i; for (i = 0; identities[i]; i++) yd->identities = y_list_append(yd->identities, strdup(identities[i])); y_strfreev(identities); } YAHOO_CALLBACK(ext_yahoo_got_identities) (yd->client_id, yd->identities); break; case 59: /* cookies */ if (pair->value[0] == 'Y') { FREE(yd->cookie_y); FREE(yd->login_cookie); yd->cookie_y = getcookie(pair->value); yd->login_cookie = getlcookie(yd->cookie_y); } else if (pair->value[0] == 'T') { FREE(yd->cookie_t); yd->cookie_t = getcookie(pair->value); } else if (pair->value[0] == 'C') { FREE(yd->cookie_c); yd->cookie_c = getcookie(pair->value); } break; case 3: /* my id */ case 90: /* 1 */ case 100: /* 0 */ case 101: /* NULL */ case 102: /* NULL */ case 93: /* 86400/1440 */ break; } } if (yd->cookie_y && yd->cookie_t) /* We don't get cookie_c anymore */ YAHOO_CALLBACK(ext_yahoo_got_cookies) (yd->client_id); } static void yahoo_process_verify(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; if (pkt->status != 0x01) { DEBUG_MSG(("expected status: 0x01, got: %d", pkt->status)); YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, YAHOO_LOGIN_LOCK, ""); return; } pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } static void yahoo_process_picture_checksum(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *from = NULL; char *to = NULL; int checksum = 0; YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 1: case 4: from = pair->value; case 5: to = pair->value; break; case 212: break; case 192: checksum = atoi(pair->value); break; } } YAHOO_CALLBACK(ext_yahoo_got_buddyicon_checksum) (yd->client_id, to, from, checksum); } static void yahoo_process_picture(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *url = NULL; char *from = NULL; char *to = NULL; int status = 0; int checksum = 0; YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 1: case 4: /* sender */ from = pair->value; break; case 5: /* we */ to = pair->value; break; case 13: /* request / sending */ status = atoi(pair->value); break; case 20: /* url */ url = pair->value; break; case 192: /*checksum */ checksum = atoi(pair->value); break; } } switch (status) { case 1: /* this is a request, ignore for now */ YAHOO_CALLBACK(ext_yahoo_got_buddyicon_request) (yd->client_id, to, from); break; case 2: /* this is cool - we get a picture :) */ YAHOO_CALLBACK(ext_yahoo_got_buddyicon) (yd->client_id, to, from, url, checksum); break; } } static void yahoo_process_picture_upload(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; YList *l; char *url = NULL; if (pkt->status != 1) return; /* something went wrong */ for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 5: /* we */ break; case 20: /* url */ url = pair->value; break; case 27: /* local filename */ break; case 38: /* time */ break; } } YAHOO_CALLBACK(ext_yahoo_buddyicon_uploaded) (yd->client_id, url); } void yahoo_login(int id, int initial) { struct yahoo_data *yd = find_conn_by_id(id); struct connect_callback_data *ccd; struct yahoo_server_settings *yss; int tag; char *host; struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1); yid->yd = yd; yid->type = YAHOO_CONNECTION_PAGER; inputs = y_list_prepend(inputs, yid); yd->initial_status = initial; yss = yd->server_settings; ccd = y_new0(struct connect_callback_data, 1); ccd->yd = yd; host = yss->pager_host; if (!host) host = yss->pager_host_list[0]; tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->client_id, host, yss->pager_port, yahoo_connected, ccd, 0); /* * if tag <= 0, then callback has already been called * so ccd will have been freed */ if (tag > 0) ccd->tag = tag; else if (tag < 0) YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, YAHOO_LOGIN_SOCK, NULL); } struct yahoo_https_auth_data { struct yahoo_input_data *yid; char *token; char *chal; }; static void yahoo_https_auth_token_init(struct yahoo_https_auth_data *had); static void yahoo_https_auth_token_finish(struct http_request *req); static void yahoo_https_auth_init(struct yahoo_https_auth_data *had); static void yahoo_https_auth_finish(struct http_request *req); /* Extract a value from a login.yahoo.com response. Assume CRLF-linebreaks and FAIL miserably if they're not there... */ static char *yahoo_ha_find_key(char *response, char *key) { char *s, *end; int len = strlen(key); s = response; do { if (strncmp(s, key, len) == 0 && s[len] == '=') { s += len + 1; if ((end = strchr(s, '\r'))) return g_strndup(s, end - s); else return g_strdup(s); } if ((s = strchr(s, '\n'))) s ++; } while (s && *s); return NULL; } static enum yahoo_status yahoo_https_status_parse(int code) { switch (code) { case 1212: return YAHOO_LOGIN_PASSWD; case 1213: return YAHOO_LOGIN_LOCK; case 1235: return YAHOO_LOGIN_UNAME; default: return (enum yahoo_status) code; } } static void yahoo_https_auth(struct yahoo_input_data *yid, const char *seed, const char *sn) { struct yahoo_https_auth_data *had = g_new0(struct yahoo_https_auth_data, 1); had->yid = yid; had->chal = g_strdup(seed); yahoo_https_auth_token_init(had); } static void yahoo_https_auth_token_init(struct yahoo_https_auth_data *had) { struct yahoo_input_data *yid = had->yid; struct yahoo_data *yd = yid->yd; char *login, *passwd, *chal; char *url; login = g_strndup(yd->user, 3 * strlen(yd->user)); http_encode(login); passwd = g_strndup(yd->password, 3 * strlen(yd->password)); http_encode(passwd); chal = g_strndup(had->chal, 3 * strlen(had->chal)); http_encode(chal); url = g_strdup_printf("https://login.yahoo.com/config/pwtoken_get?src=ymsgr&ts=%d&login=%s&passwd=%s&chal=%s", (int) time(NULL), login, passwd, chal); http_dorequest_url(url, yahoo_https_auth_token_finish, had); g_free(url); g_free(chal); g_free(passwd); g_free(login); } static void yahoo_https_auth_token_finish(struct http_request *req) { struct yahoo_https_auth_data *had = req->data; struct yahoo_input_data *yid; struct yahoo_data *yd; int st; if (y_list_find(inputs, had->yid) == NULL) return; yid = had->yid; yd = yid->yd; if (req->status_code != 200) { YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 2000 + req->status_code, NULL); goto fail; } if (sscanf(req->reply_body, "%d", &st) != 1 || st != 0) { YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, yahoo_https_status_parse(st), NULL); goto fail; } if ((had->token = yahoo_ha_find_key(req->reply_body, "ymsgr")) == NULL) { YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3001, NULL); goto fail; } yahoo_https_auth_init(had); return; fail: g_free(had->token); g_free(had->chal); g_free(had); } static void yahoo_https_auth_init(struct yahoo_https_auth_data *had) { char *url; url = g_strdup_printf("https://login.yahoo.com/config/pwtoken_login?src=ymsgr&ts=%d&token=%s", (int) time(NULL), had->token); http_dorequest_url(url, yahoo_https_auth_finish, had); g_free(url); } static void yahoo_https_auth_finish(struct http_request *req) { struct yahoo_https_auth_data *had = req->data; struct yahoo_input_data *yid; struct yahoo_data *yd; struct yahoo_packet *pack; char *crumb = NULL; int st; if (y_list_find(inputs, had->yid) == NULL) return; yid = had->yid; yd = yid->yd; md5_byte_t result[16]; md5_state_t ctx; unsigned char yhash[32]; if (req->status_code != 200) { YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 2000 + req->status_code, NULL); goto fail; } if (sscanf(req->reply_body, "%d", &st) != 1 || st != 0) { YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, yahoo_https_status_parse(st), NULL); goto fail; } if ((yd->cookie_y = yahoo_ha_find_key(req->reply_body, "Y")) == NULL || (yd->cookie_t = yahoo_ha_find_key(req->reply_body, "T")) == NULL || (crumb = yahoo_ha_find_key(req->reply_body, "crumb")) == NULL) { YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3002, NULL); goto fail; } md5_init(&ctx); md5_append(&ctx, (unsigned char*) crumb, 11); md5_append(&ctx, (unsigned char*) had->chal, strlen(had->chal)); md5_finish(&ctx, result); to_y64(yhash, result, 16); pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->initial_status, yd->session_id); yahoo_packet_hash(pack, 1, yd->user); yahoo_packet_hash(pack, 0, yd->user); yahoo_packet_hash(pack, 277, yd->cookie_y); yahoo_packet_hash(pack, 278, yd->cookie_t); yahoo_packet_hash(pack, 307, (char*) yhash); yahoo_packet_hash(pack, 244, "524223"); yahoo_packet_hash(pack, 2, yd->user); yahoo_packet_hash(pack, 2, "1"); yahoo_packet_hash(pack, 98, "us"); yahoo_packet_hash(pack, 135, "7.5.0.647"); yahoo_send_packet(yid, pack, 0); yahoo_packet_free(pack); fail: g_free(crumb); g_free(had->token); g_free(had->chal); g_free(had); } static void yahoo_process_auth(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { char *seed = NULL; char *sn = NULL; YList *l = pkt->hash; int m = 0; struct yahoo_data *yd = yid->yd; while (l) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 94: seed = pair->value; break; case 1: sn = pair->value; break; case 13: m = atoi(pair->value); break; } l = l->next; } if (!seed) return; if (m==2) yahoo_https_auth(yid, seed, sn); else { /* call error */ WARNING(("unknown auth type %d", m)); YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, YAHOO_LOGIN_UNKNOWN, NULL); } } static void yahoo_process_auth_resp(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *url = NULL; int login_status = -1; YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 0) ; /* login_id */ else if (pair->key == 1) ; /* handle */ else if (pair->key == 20) url = pair->value; else if (pair->key == 66) login_status = atoi(pair->value); } if (pkt->status == YPACKET_STATUS_DISCONNECTED) { YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, login_status, url); /* yahoo_logoff(yd->client_id); */ } } static void yahoo_process_mail(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *who = NULL; char *email = NULL; char *subj = NULL; int count = 0; YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 9) count = strtol(pair->value, NULL, 10); else if (pair->key == 43) who = pair->value; else if (pair->key == 42) email = pair->value; else if (pair->key == 18) subj = pair->value; else LOG(("key: %d => value: %s", pair->key, pair->value)); } if (who && email && subj) { char from[1024]; snprintf(from, sizeof(from), "%s (%s)", who, email); YAHOO_CALLBACK(ext_yahoo_mail_notify) (yd->client_id, from, subj, count); } else if (count > 0) YAHOO_CALLBACK(ext_yahoo_mail_notify) (yd->client_id, NULL, NULL, count); } static void yahoo_process_new_contact(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *me = NULL; char *who = NULL; char *msg = NULL; int online = -1; YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 4) who = pair->value; else if (pair->key == 5) me = pair->value; else if (pair->key == 14) msg = pair->value; else if (pair->key == 13) online = strtol(pair->value, NULL, 10); } if (who && online < 0) YAHOO_CALLBACK(ext_yahoo_contact_added) (yd->client_id, me, who, msg); else if (online == 2) YAHOO_CALLBACK(ext_yahoo_rejected) (yd->client_id, who, msg); } /* UNUSED? */ static void yahoo_process_contact(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *id = NULL; char *who = NULL; char *msg = NULL; char *name = NULL; int state = YAHOO_STATUS_AVAILABLE; int away = 0; int idle = 0; int mobile = 0; YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 1) id = pair->value; else if (pair->key == 3) who = pair->value; else if (pair->key == 14) msg = pair->value; else if (pair->key == 7) name = pair->value; else if (pair->key == 10) state = strtol(pair->value, NULL, 10); else if (pair->key == 15) ; /* tm */ else if (pair->key == 13) ; /* online */ else if (pair->key == 47) away = strtol(pair->value, NULL, 10); else if (pair->key == 137) idle = strtol(pair->value, NULL, 10); else if (pair->key == 60) mobile = strtol(pair->value, NULL, 10); } if (id) YAHOO_CALLBACK(ext_yahoo_contact_added) (yd->client_id, id, who, msg); else if (name) YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->client_id, name, state, msg, away, idle, mobile); else if (pkt->status == 0x07) YAHOO_CALLBACK(ext_yahoo_rejected) (yd->client_id, who, msg); } static void yahoo_process_buddyadd(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *who = NULL; char *where = NULL; int status = 0; struct yahoo_buddy *bud = NULL; YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 1) ; /* Me... don't care */ if (pair->key == 7) who = pair->value; if (pair->key == 65) where = pair->value; if (pair->key == 66) status = strtol(pair->value, NULL, 10); } if (!who) return; if (!where) where = "Unknown"; bud = y_new0(struct yahoo_buddy, 1); bud->id = strdup(who); bud->group = strdup(where); bud->real_name = NULL; yd->buddies = y_list_append(yd->buddies, bud); #if 0 /* BitlBee: This seems to be wrong in my experience. I think: status = 0: Success status = 2: Already on list status = 3: Doesn't exist status = 42: Invalid handle (possibly banned/reserved, I get it for handles like joe or jjjjjj) Haven't seen others yet. But whenever the add is successful, there will be a separate "went online" packet when the auth. request is accepted. Couldn't find any test account that doesn't require auth. unfortunately (if there is even such a thing?) */ /* A non-zero status (i've seen 2) seems to mean the buddy is already * added and is online */ if (status) { LOG(("Setting online see packet for info")); yahoo_dump_unhandled(pkt); YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->client_id, who, YAHOO_STATUS_AVAILABLE, NULL, 0, 0, 0); } #endif /* BitlBee: Need ACK of added buddy, if it was successful. */ if (status == 0) { YList *tmp = y_list_append(NULL, bud); YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id, tmp); y_list_free(tmp); } } static void yahoo_process_buddydel(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *who = NULL; char *where = NULL; struct yahoo_buddy *bud; YList *buddy; YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 1) ; /* Me... don't care */ else if (pair->key == 7) who = pair->value; else if (pair->key == 65) where = pair->value; else if (pair->key == 66) ; /* unk_66 */ else DEBUG_MSG(("unknown key: %d = %s", pair->key, pair->value)); } if (!who || !where) return; bud = y_new0(struct yahoo_buddy, 1); bud->id = strdup(who); bud->group = strdup(where); buddy = y_list_find_custom(yd->buddies, bud, is_same_bud); FREE(bud->id); FREE(bud->group); FREE(bud); if (buddy) { bud = buddy->data; yd->buddies = y_list_remove_link(yd->buddies, buddy); y_list_free_1(buddy); FREE(bud->id); FREE(bud->group); FREE(bud->real_name); FREE(bud); bud = NULL; } } static void yahoo_process_ignore(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 0) ; /* who */ if (pair->key == 1) ; /* Me... don't care */ if (pair->key == 13) /* 1 == ignore, 2 == unignore */ ; if (pair->key == 66) ; /* status */ } /* * status * 0 - ok * 2 - already in ignore list, could not add * 3 - not in ignore list, could not delete * 12 - is a buddy, could not add */ /* if(status) YAHOO_CALLBACK(ext_yahoo_error)(yd->client_id, who, 0, status); */ } static void yahoo_process_voicechat(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { char *who = NULL; char *me = NULL; char *room = NULL; YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 4) who = pair->value; if (pair->key == 5) me = pair->value; if (pair->key == 13) ; /* voice room */ if (pair->key == 57) room = pair->value; } NOTICE(("got voice chat invite from %s in %s to identity %s", who, room, me)); /* * send: s:0 1:me 5:who 57:room 13:1 * ???? s:4 5:who 10:99 19:-1615114531 * gotr: s:4 5:who 10:99 19:-1615114615 * ???? s:1 5:me 4:who 57:room 13:3room * got: s:1 5:me 4:who 57:room 13:1room * rej: s:0 1:me 5:who 57:room 13:3 * rejr: s:4 5:who 10:99 19:-1617114599 */ } static void yahoo_process_ping(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { char *errormsg = NULL; YList *l; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 16) errormsg = pair->value; } NOTICE(("got ping packet")); YAHOO_CALLBACK(ext_yahoo_got_ping) (yid->yd->client_id, errormsg); } static void yahoo_process_buddy_change_group(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { YList *l; char *me = NULL; char *who = NULL; char *old_group = NULL; char *new_group = NULL; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 1) me = pair->value; if (pair->key == 7) who = pair->value; if (pair->key == 224) old_group = pair->value; if (pair->key == 264) new_group = pair->value; } YAHOO_CALLBACK(ext_yahoo_got_buddy_change_group) (yid->yd->client_id, me, who, old_group, new_group); } static void _yahoo_webcam_get_server_connected(void *fd, int error, void *d) { struct yahoo_input_data *yid = d; char *who = yid->wcm->user; char *data = NULL; char *packet = NULL; unsigned char magic_nr[] = { 0, 1, 0 }; unsigned char header_len = 8; unsigned int len = 0; unsigned int pos = 0; if (error || !fd) { FREE(who); FREE(yid); return; } yid->fd = fd; inputs = y_list_prepend(inputs, yid); /* send initial packet */ if (who) data = strdup(""); else data = strdup(""); yahoo_add_to_send_queue(yid, data, strlen(data)); FREE(data); /* send data */ if (who) { data = strdup("g="); data = y_string_append(data, who); data = y_string_append(data, "\r\n"); } else { data = strdup("f=1\r\n"); } len = strlen(data); packet = y_new0(char, header_len + len); packet[pos++] = header_len; memcpy(packet + pos, magic_nr, sizeof(magic_nr)); pos += sizeof(magic_nr); pos += yahoo_put32(packet + pos, len); memcpy(packet + pos, data, len); pos += len; yahoo_add_to_send_queue(yid, packet, pos); FREE(packet); FREE(data); yid->read_tag = YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, YAHOO_INPUT_READ, yid); } static void yahoo_webcam_get_server(struct yahoo_input_data *y, char *who, char *key) { struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1); struct yahoo_server_settings *yss = y->yd->server_settings; yid->type = YAHOO_CONNECTION_WEBCAM_MASTER; yid->yd = y->yd; yid->wcm = y_new0(struct yahoo_webcam, 1); yid->wcm->user = who ? strdup(who) : NULL; yid->wcm->direction = who ? YAHOO_WEBCAM_DOWNLOAD : YAHOO_WEBCAM_UPLOAD; yid->wcm->key = strdup(key); YAHOO_CALLBACK(ext_yahoo_connect_async) (yid->yd->client_id, yss->webcam_host, yss->webcam_port, _yahoo_webcam_get_server_connected, yid, 0); } static YList *webcam_queue = NULL; static void yahoo_process_webcam_key(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { char *key = NULL; char *who = NULL; YList *l; yahoo_dump_unhandled(pkt); for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 5) ; /* me */ if (pair->key == 61) key = pair->value; } l = webcam_queue; if (!l) return; who = l->data; webcam_queue = y_list_remove_link(webcam_queue, webcam_queue); y_list_free_1(l); yahoo_webcam_get_server(yid, who, key); FREE(who); } static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { DEBUG_MSG(("yahoo_packet_process: 0x%02x", pkt->service)); switch (pkt->service) { case YAHOO_SERVICE_USERSTAT: case YAHOO_SERVICE_LOGON: case YAHOO_SERVICE_LOGOFF: case YAHOO_SERVICE_ISAWAY: case YAHOO_SERVICE_ISBACK: case YAHOO_SERVICE_GAMELOGON: case YAHOO_SERVICE_GAMELOGOFF: case YAHOO_SERVICE_IDACT: case YAHOO_SERVICE_IDDEACT: case YAHOO_SERVICE_Y6_STATUS_UPDATE: case YAHOO_SERVICE_Y8_STATUS: yahoo_process_status(yid, pkt); break; case YAHOO_SERVICE_NOTIFY: yahoo_process_notify(yid, pkt); break; case YAHOO_SERVICE_MESSAGE: case YAHOO_SERVICE_GAMEMSG: case YAHOO_SERVICE_SYSMESSAGE: yahoo_process_message(yid, pkt); break; case YAHOO_SERVICE_NEWMAIL: yahoo_process_mail(yid, pkt); break; case YAHOO_SERVICE_Y7_AUTHORIZATION: yahoo_process_new_contact(yid, pkt); break; case YAHOO_SERVICE_NEWCONTACT: yahoo_process_contact(yid, pkt); break; case YAHOO_SERVICE_LIST: yahoo_process_list(yid, pkt); break; case YAHOO_SERVICE_VERIFY: yahoo_process_verify(yid, pkt); break; case YAHOO_SERVICE_AUTH: yahoo_process_auth(yid, pkt); break; case YAHOO_SERVICE_AUTHRESP: yahoo_process_auth_resp(yid, pkt); break; case YAHOO_SERVICE_CONFINVITE: case YAHOO_SERVICE_CONFADDINVITE: case YAHOO_SERVICE_CONFDECLINE: case YAHOO_SERVICE_CONFLOGON: case YAHOO_SERVICE_CONFLOGOFF: case YAHOO_SERVICE_CONFMSG: yahoo_process_conference(yid, pkt); break; case YAHOO_SERVICE_CHATONLINE: case YAHOO_SERVICE_CHATGOTO: case YAHOO_SERVICE_CHATJOIN: case YAHOO_SERVICE_CHATLEAVE: case YAHOO_SERVICE_CHATEXIT: case YAHOO_SERVICE_CHATLOGOUT: case YAHOO_SERVICE_CHATPING: case YAHOO_SERVICE_COMMENT: yahoo_process_chat(yid, pkt); break; case YAHOO_SERVICE_P2PFILEXFER: case YAHOO_SERVICE_Y7_FILETRANSFER: yahoo_process_filetransfer(yid, pkt); break; case YAHOO_SERVICE_Y7_FILETRANSFERINFO: yahoo_process_filetransferinfo(yid, pkt); break; case YAHOO_SERVICE_Y7_FILETRANSFERACCEPT: yahoo_process_filetransferaccept(yid, pkt); break; case YAHOO_SERVICE_ADDBUDDY: yahoo_process_buddyadd(yid, pkt); break; case YAHOO_SERVICE_REMBUDDY: yahoo_process_buddydel(yid, pkt); break; case YAHOO_SERVICE_IGNORECONTACT: yahoo_process_ignore(yid, pkt); break; case YAHOO_SERVICE_VOICECHAT: yahoo_process_voicechat(yid, pkt); break; case YAHOO_SERVICE_WEBCAM: yahoo_process_webcam_key(yid, pkt); break; case YAHOO_SERVICE_PING: yahoo_process_ping(yid, pkt); break; case YAHOO_SERVICE_Y7_CHANGE_GROUP: yahoo_process_buddy_change_group(yid, pkt); break; case YAHOO_SERVICE_IDLE: case YAHOO_SERVICE_MAILSTAT: case YAHOO_SERVICE_CHATINVITE: case YAHOO_SERVICE_CALENDAR: case YAHOO_SERVICE_NEWPERSONALMAIL: case YAHOO_SERVICE_ADDIDENT: case YAHOO_SERVICE_ADDIGNORE: case YAHOO_SERVICE_GOTGROUPRENAME: case YAHOO_SERVICE_GROUPRENAME: case YAHOO_SERVICE_PASSTHROUGH2: case YAHOO_SERVICE_CHATLOGON: case YAHOO_SERVICE_CHATLOGOFF: case YAHOO_SERVICE_CHATMSG: case YAHOO_SERVICE_REJECTCONTACT: case YAHOO_SERVICE_PEERTOPEER: WARNING(("unhandled service 0x%02x", pkt->service)); yahoo_dump_unhandled(pkt); break; case YAHOO_SERVICE_PICTURE: yahoo_process_picture(yid, pkt); break; case YAHOO_SERVICE_PICTURE_CHECKSUM: yahoo_process_picture_checksum(yid, pkt); break; case YAHOO_SERVICE_PICTURE_UPLOAD: yahoo_process_picture_upload(yid, pkt); break; case YAHOO_SERVICE_Y8_LIST: /* Buddy List */ yahoo_process_buddy_list(yid, pkt); break; default: WARNING(("unknown service 0x%02x", pkt->service)); yahoo_dump_unhandled(pkt); break; } } static struct yahoo_packet *yahoo_getdata(struct yahoo_input_data *yid) { struct yahoo_packet *pkt; struct yahoo_data *yd = yid->yd; int pos = 0; int pktlen; if (!yd) return NULL; DEBUG_MSG(("rxlen is %d", yid->rxlen)); if (yid->rxlen < YAHOO_PACKET_HDRLEN) { DEBUG_MSG(("len < YAHOO_PACKET_HDRLEN")); return NULL; } pos += 4; /* YMSG */ pos += 2; pos += 2; pktlen = yahoo_get16(yid->rxqueue + pos); pos += 2; DEBUG_MSG(("%d bytes to read, rxlen is %d", pktlen, yid->rxlen)); if (yid->rxlen < (YAHOO_PACKET_HDRLEN + pktlen)) { DEBUG_MSG(("len < YAHOO_PACKET_HDRLEN + pktlen")); return NULL; } LOG(("reading packet")); yahoo_packet_dump(yid->rxqueue, YAHOO_PACKET_HDRLEN + pktlen); pkt = yahoo_packet_new(0, 0, 0); pkt->service = yahoo_get16(yid->rxqueue + pos); pos += 2; pkt->status = yahoo_get32(yid->rxqueue + pos); pos += 4; DEBUG_MSG(("Yahoo Service: 0x%02x Status: %d", pkt->service, pkt->status)); pkt->id = yahoo_get32(yid->rxqueue + pos); pos += 4; yd->session_id = pkt->id; yahoo_packet_read(pkt, yid->rxqueue + pos, pktlen); yid->rxlen -= YAHOO_PACKET_HDRLEN + pktlen; DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); if (yid->rxlen > 0) { unsigned char *tmp = y_memdup(yid->rxqueue + YAHOO_PACKET_HDRLEN + pktlen, yid->rxlen); FREE(yid->rxqueue); yid->rxqueue = tmp; DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); } else { DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); FREE(yid->rxqueue); } return pkt; } #if 0 static struct yab *yahoo_yab_read(unsigned char *d, int len) { char *st, *en; char *data = (char *)d; struct yab *yab = NULL; data[len] = '\0'; DEBUG_MSG(("Got yab: %s", data)); st = en = strstr(data, "e0=\""); if (st) { yab = y_new0(struct yab, 1); st += strlen("e0=\""); en = strchr(st, '"'); *en++ = '\0'; yab->email = yahoo_xmldecode(st); } if (!en) return NULL; st = strstr(en, "id=\""); if (st) { st += strlen("id=\""); en = strchr(st, '"'); *en++ = '\0'; yab->yid = atoi(yahoo_xmldecode(st)); } st = strstr(en, "fn=\""); if (st) { st += strlen("fn=\""); en = strchr(st, '"'); *en++ = '\0'; yab->fname = yahoo_xmldecode(st); } st = strstr(en, "ln=\""); if (st) { st += strlen("ln=\""); en = strchr(st, '"'); *en++ = '\0'; yab->lname = yahoo_xmldecode(st); } st = strstr(en, "nn=\""); if (st) { st += strlen("nn=\""); en = strchr(st, '"'); *en++ = '\0'; yab->nname = yahoo_xmldecode(st); } st = strstr(en, "yi=\""); if (st) { st += strlen("yi=\""); en = strchr(st, '"'); *en++ = '\0'; yab->id = yahoo_xmldecode(st); } st = strstr(en, "hphone=\""); if (st) { st += strlen("hphone=\""); en = strchr(st, '"'); *en++ = '\0'; yab->hphone = yahoo_xmldecode(st); } st = strstr(en, "wphone=\""); if (st) { st += strlen("wphone=\""); en = strchr(st, '"'); *en++ = '\0'; yab->wphone = yahoo_xmldecode(st); } st = strstr(en, "mphone=\""); if (st) { st += strlen("mphone=\""); en = strchr(st, '"'); *en++ = '\0'; yab->mphone = yahoo_xmldecode(st); } st = strstr(en, "dbid=\""); if (st) { st += strlen("dbid=\""); en = strchr(st, '"'); *en++ = '\0'; yab->dbid = atoi(st); } return yab; } static struct yab *yahoo_getyab(struct yahoo_input_data *yid) { struct yab *yab = NULL; int pos = 0, end = 0; struct yahoo_data *yd = yid->yd; if (!yd) return NULL; do { DEBUG_MSG(("rxlen is %d", yid->rxlen)); if (yid->rxlen <= strlen("rxlen - strlen("rxqueue + pos, "= yid->rxlen - 1) return NULL; end = pos + 2; /* end with > */ while (end < yid->rxlen - strlen(">") && memcmp(yid->rxqueue + end, ">", strlen(">"))) end++; if (end >= yid->rxlen - 1) return NULL; yab = yahoo_yab_read(yid->rxqueue + pos, end + 2 - pos); yid->rxlen -= end + 1; DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); if (yid->rxlen > 0) { unsigned char *tmp = y_memdup(yid->rxqueue + end + 1, yid->rxlen); FREE(yid->rxqueue); yid->rxqueue = tmp; DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); } else { DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); FREE(yid->rxqueue); } } while (!yab && end < yid->rxlen - 1); return yab; } #endif static char *yahoo_getwebcam_master(struct yahoo_input_data *yid) { unsigned int pos = 0; unsigned int len = 0; unsigned int status = 0; char *server = NULL; struct yahoo_data *yd = yid->yd; if (!yid || !yd) return NULL; DEBUG_MSG(("rxlen is %d", yid->rxlen)); len = yid->rxqueue[pos++]; if (yid->rxlen < len) return NULL; /* extract status (0 = ok, 6 = webcam not online) */ status = yid->rxqueue[pos++]; if (status == 0) { pos += 2; /* skip next 2 bytes */ server = y_memdup(yid->rxqueue + pos, 16); pos += 16; } else if (status == 6) { YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd->client_id, yid->wcm->user, 4); } /* skip rest of the data */ yid->rxlen -= len; DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); if (yid->rxlen > 0) { unsigned char *tmp = y_memdup(yid->rxqueue + pos, yid->rxlen); FREE(yid->rxqueue); yid->rxqueue = tmp; DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); } else { DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); FREE(yid->rxqueue); } return server; } static int yahoo_get_webcam_data(struct yahoo_input_data *yid) { unsigned char reason = 0; unsigned int pos = 0; unsigned int begin = 0; unsigned int end = 0; unsigned int closed = 0; unsigned char header_len = 0; char *who; int connect = 0; struct yahoo_data *yd = yid->yd; if (!yd) return -1; if (!yid->wcm || !yid->wcd || !yid->rxlen) return -1; DEBUG_MSG(("rxlen is %d", yid->rxlen)); /* if we are not reading part of image then read header */ if (!yid->wcd->to_read) { header_len = yid->rxqueue[pos++]; yid->wcd->packet_type = 0; if (yid->rxlen < header_len) return 0; if (header_len >= 8) { reason = yid->rxqueue[pos++]; /* next 2 bytes should always be 05 00 */ pos += 2; yid->wcd->data_size = yahoo_get32(yid->rxqueue + pos); pos += 4; yid->wcd->to_read = yid->wcd->data_size; } if (header_len >= 13) { yid->wcd->packet_type = yid->rxqueue[pos++]; yid->wcd->timestamp = yahoo_get32(yid->rxqueue + pos); pos += 4; } /* skip rest of header */ pos = header_len; } begin = pos; pos += yid->wcd->to_read; if (pos > yid->rxlen) pos = yid->rxlen; /* if it is not an image then make sure we have the whole packet */ if (yid->wcd->packet_type != 0x02) { if ((pos - begin) != yid->wcd->data_size) { yid->wcd->to_read = 0; return 0; } else { yahoo_packet_dump(yid->rxqueue + begin, pos - begin); } } DEBUG_MSG(("packet type %.2X, data length %d", yid->wcd->packet_type, yid->wcd->data_size)); /* find out what kind of packet we got */ switch (yid->wcd->packet_type) { case 0x00: /* user requests to view webcam (uploading) */ if (yid->wcd->data_size && yid->wcm->direction == YAHOO_WEBCAM_UPLOAD) { end = begin; while (end <= yid->rxlen && yid->rxqueue[end++] != 13) ; if (end > begin) { who = y_memdup(yid->rxqueue + begin, end - begin); who[end - begin - 1] = 0; YAHOO_CALLBACK(ext_yahoo_webcam_viewer) (yd-> client_id, who + 2, 2); FREE(who); } } if (yid->wcm->direction == YAHOO_WEBCAM_DOWNLOAD) { /* timestamp/status field */ /* 0 = declined viewing permission */ /* 1 = accepted viewing permission */ if (yid->wcd->timestamp == 0) { YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd-> client_id, yid->wcm->user, 3); } } break; case 0x01: /* status packets?? */ /* timestamp contains status info */ /* 00 00 00 01 = we have data?? */ break; case 0x02: /* image data */ YAHOO_CALLBACK(ext_yahoo_got_webcam_image) (yd->client_id, yid->wcm->user, yid->rxqueue + begin, yid->wcd->data_size, pos - begin, yid->wcd->timestamp); break; case 0x05: /* response packets when uploading */ if (!yid->wcd->data_size) { YAHOO_CALLBACK(ext_yahoo_webcam_data_request) (yd-> client_id, yid->wcd->timestamp); } break; case 0x07: /* connection is closing */ switch (reason) { case 0x01: /* user closed connection */ closed = 1; break; case 0x0F: /* user cancelled permission */ closed = 2; break; } YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd->client_id, yid->wcm->user, closed); break; case 0x0C: /* user connected */ case 0x0D: /* user disconnected */ if (yid->wcd->data_size) { who = y_memdup(yid->rxqueue + begin, pos - begin + 1); who[pos - begin] = 0; if (yid->wcd->packet_type == 0x0C) connect = 1; else connect = 0; YAHOO_CALLBACK(ext_yahoo_webcam_viewer) (yd->client_id, who, connect); FREE(who); } break; case 0x13: /* user data */ /* i=user_ip (ip of the user we are viewing) */ /* j=user_ext_ip (external ip of the user we */ /* are viewing) */ break; case 0x17: /* ?? */ break; } yid->wcd->to_read -= pos - begin; yid->rxlen -= pos; DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); if (yid->rxlen > 0) { unsigned char *tmp = y_memdup(yid->rxqueue + pos, yid->rxlen); FREE(yid->rxqueue); yid->rxqueue = tmp; DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); } else { DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); FREE(yid->rxqueue); } /* If we read a complete packet return success */ if (!yid->wcd->to_read) return 1; return 0; } int yahoo_write_ready(int id, void *fd, void *data) { struct yahoo_input_data *yid = data; int len; struct data_queue *tx; LOG(("write callback: id=%d fd=%p data=%p", id, fd, data)); if (!yid || !yid->txqueues) return -2; tx = yid->txqueues->data; LOG(("writing %d bytes", tx->len)); len = yahoo_send_data(fd, tx->queue, MIN(1024, tx->len)); if (len == -1 && errno == EAGAIN) return 1; if (len <= 0) { int e = errno; DEBUG_MSG(("len == %d (<= 0)", len)); while (yid->txqueues) { YList *l = yid->txqueues; tx = l->data; free(tx->queue); free(tx); yid->txqueues = y_list_remove_link(yid->txqueues, yid->txqueues); y_list_free_1(l); } LOG(("yahoo_write_ready(%d, %p) len < 0", id, fd)); YAHOO_CALLBACK(ext_yahoo_remove_handler) (id, yid->write_tag); yid->write_tag = 0; errno = e; return 0; } tx->len -= len; if (tx->len > 0) { unsigned char *tmp = y_memdup(tx->queue + len, tx->len); FREE(tx->queue); tx->queue = tmp; } else { YList *l = yid->txqueues; free(tx->queue); free(tx); yid->txqueues = y_list_remove_link(yid->txqueues, yid->txqueues); y_list_free_1(l); /* if(!yid->txqueues) LOG(("yahoo_write_ready(%d, %d) !yxqueues", id, fd)); */ if (!yid->txqueues) { LOG(("yahoo_write_ready(%d, %p) !txqueues", id, fd)); YAHOO_CALLBACK(ext_yahoo_remove_handler) (id, yid->write_tag); yid->write_tag = 0; } } return 1; } static void yahoo_process_pager_connection(struct yahoo_input_data *yid, int over) { struct yahoo_packet *pkt; struct yahoo_data *yd = yid->yd; int id = yd->client_id; if (over) return; while (find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER) && (pkt = yahoo_getdata(yid)) != NULL) { yahoo_packet_process(yid, pkt); yahoo_packet_free(pkt); } } static void yahoo_process_chatcat_connection(struct yahoo_input_data *yid, int over) { if (over) return; if (strstr((char *)yid->rxqueue + (yid->rxlen - 20), "")) { YAHOO_CALLBACK(ext_yahoo_chat_cat_xml) (yid->yd->client_id, (char *)yid->rxqueue); } } #if 0 static void yahoo_process_yab_connection(struct yahoo_input_data *yid, int over) { struct yahoo_data *yd = yid->yd; struct yab *yab; YList *buds; int changed = 0; int id = yd->client_id; int yab_used = 0; LOG(("Got data for YAB")); if (over) return; while (find_input_by_id_and_type(id, YAHOO_CONNECTION_YAB) && (yab = yahoo_getyab(yid)) != NULL) { if (!yab->id) continue; changed = 1; yab_used = 0; for (buds = yd->buddies; buds; buds = buds->next) { struct yahoo_buddy *bud = buds->data; if (!strcmp(bud->id, yab->id)) { yab_used = 1; bud->yab_entry = yab; if (yab->nname) { bud->real_name = strdup(yab->nname); } else if (yab->fname && yab->lname) { bud->real_name = y_new0(char, strlen(yab->fname) + strlen(yab->lname) + 2); sprintf(bud->real_name, "%s %s", yab->fname, yab->lname); } else if (yab->fname) { bud->real_name = strdup(yab->fname); } break; /* for */ } } if (!yab_used) { FREE(yab->fname); FREE(yab->lname); FREE(yab->nname); FREE(yab->id); FREE(yab->email); FREE(yab->hphone); FREE(yab->wphone); FREE(yab->mphone); FREE(yab); } } if (changed) YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id, yd->buddies); } #endif static void yahoo_process_search_connection(struct yahoo_input_data *yid, int over) { struct yahoo_found_contact *yct = NULL; char *p = (char *)yid->rxqueue, *np, *cp; int k, n; int start = 0, found = 0, total = 0; YList *contacts = NULL; struct yahoo_input_data *pyid = find_input_by_id_and_type(yid->yd->client_id, YAHOO_CONNECTION_PAGER); if (!over || !pyid) return; if (p && (p = strstr(p, "\r\n\r\n"))) { p += 4; for (k = 0; (p = strchr(p, 4)) && (k < 4); k++) { p++; n = atoi(p); switch (k) { case 0: found = pyid->ys->lsearch_nfound = n; break; case 2: start = pyid->ys->lsearch_nstart = n; break; case 3: total = pyid->ys->lsearch_ntotal = n; break; } } if (p) p++; k = 0; while (p && *p) { cp = p; np = strchr(p, 4); if (!np) break; *np = 0; p = np + 1; switch (k++) { case 1: if (strlen(cp) > 2 && y_list_length(contacts) < total) { yct = y_new0(struct yahoo_found_contact, 1); contacts = y_list_append(contacts, yct); yct->id = cp + 2; } else { *p = 0; } break; case 2: yct->online = !strcmp(cp, "2") ? 1 : 0; break; case 3: yct->gender = cp; break; case 4: yct->age = atoi(cp); break; case 5: /* not worth the context switch for strcmp */ if (cp[0] != '\005' || cp[1] != '\000') yct->location = cp; k = 0; break; } } } YAHOO_CALLBACK(ext_yahoo_got_search_result) (yid->yd->client_id, found, start, total, contacts); while (contacts) { YList *node = contacts; contacts = y_list_remove_link(contacts, node); free(node->data); y_list_free_1(node); } } static void _yahoo_webcam_connected(void *fd, int error, void *d) { struct yahoo_input_data *yid = d; struct yahoo_webcam *wcm = yid->wcm; struct yahoo_data *yd = yid->yd; char conn_type[100]; char *data = NULL; char *packet = NULL; unsigned char magic_nr[] = { 1, 0, 0, 0, 1 }; unsigned header_len = 0; unsigned int len = 0; unsigned int pos = 0; if (error || !fd) { FREE(yid); return; } yid->fd = fd; inputs = y_list_prepend(inputs, yid); LOG(("Connected")); /* send initial packet */ switch (wcm->direction) { case YAHOO_WEBCAM_DOWNLOAD: data = strdup(""); break; case YAHOO_WEBCAM_UPLOAD: data = strdup(""); break; default: return; } yahoo_add_to_send_queue(yid, data, strlen(data)); FREE(data); /* send data */ switch (wcm->direction) { case YAHOO_WEBCAM_DOWNLOAD: header_len = 8; data = strdup("a=2\r\nc=us\r\ne=21\r\nu="); data = y_string_append(data, yd->user); data = y_string_append(data, "\r\nt="); data = y_string_append(data, wcm->key); data = y_string_append(data, "\r\ni="); data = y_string_append(data, wcm->my_ip); data = y_string_append(data, "\r\ng="); data = y_string_append(data, wcm->user); data = y_string_append(data, "\r\no=w-2-5-1\r\np="); snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type); data = y_string_append(data, conn_type); data = y_string_append(data, "\r\n"); break; case YAHOO_WEBCAM_UPLOAD: header_len = 13; data = strdup("a=2\r\nc=us\r\nu="); data = y_string_append(data, yd->user); data = y_string_append(data, "\r\nt="); data = y_string_append(data, wcm->key); data = y_string_append(data, "\r\ni="); data = y_string_append(data, wcm->my_ip); data = y_string_append(data, "\r\no=w-2-5-1\r\np="); snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type); data = y_string_append(data, conn_type); data = y_string_append(data, "\r\nb="); data = y_string_append(data, wcm->description); data = y_string_append(data, "\r\n"); break; } len = strlen(data); packet = y_new0(char, header_len + len); packet[pos++] = header_len; packet[pos++] = 0; switch (wcm->direction) { case YAHOO_WEBCAM_DOWNLOAD: packet[pos++] = 1; packet[pos++] = 0; break; case YAHOO_WEBCAM_UPLOAD: packet[pos++] = 5; packet[pos++] = 0; break; } pos += yahoo_put32(packet + pos, len); if (wcm->direction == YAHOO_WEBCAM_UPLOAD) { memcpy(packet + pos, magic_nr, sizeof(magic_nr)); pos += sizeof(magic_nr); } memcpy(packet + pos, data, len); yahoo_add_to_send_queue(yid, packet, header_len + len); FREE(packet); FREE(data); yid->read_tag = YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, yid->fd, YAHOO_INPUT_READ, yid); } static void yahoo_webcam_connect(struct yahoo_input_data *y) { struct yahoo_webcam *wcm = y->wcm; struct yahoo_input_data *yid; if (!wcm || !wcm->server || !wcm->key) return; yid = y_new0(struct yahoo_input_data, 1); yid->type = YAHOO_CONNECTION_WEBCAM; yid->yd = y->yd; /* copy webcam data to new connection */ yid->wcm = y->wcm; y->wcm = NULL; yid->wcd = y_new0(struct yahoo_webcam_data, 1); LOG(("Connecting to: %s:%d", wcm->server, wcm->port)); YAHOO_CALLBACK(ext_yahoo_connect_async) (y->yd->client_id, wcm->server, wcm->port, _yahoo_webcam_connected, yid, 0); } static void yahoo_process_webcam_master_connection(struct yahoo_input_data *yid, int over) { char *server; struct yahoo_server_settings *yss; if (over) return; server = yahoo_getwebcam_master(yid); if (server) { yss = yid->yd->server_settings; yid->wcm->server = strdup(server); yid->wcm->port = yss->webcam_port; yid->wcm->conn_type = yss->conn_type; yid->wcm->my_ip = strdup(yss->local_host); if (yid->wcm->direction == YAHOO_WEBCAM_UPLOAD) yid->wcm->description = strdup(yss->webcam_description); yahoo_webcam_connect(yid); FREE(server); } } static void yahoo_process_webcam_connection(struct yahoo_input_data *yid, int over) { int id = yid->yd->client_id; void *fd = yid->fd; if (over) return; /* as long as we still have packets available keep processing them */ while (find_input_by_id_and_fd(id, fd) && yahoo_get_webcam_data(yid) == 1) ; } static void (*yahoo_process_connection[]) (struct yahoo_input_data *, int over) = { yahoo_process_pager_connection, yahoo_process_ft_connection, NULL, /*yahoo_process_yab_connection, */ yahoo_process_webcam_master_connection, yahoo_process_webcam_connection, yahoo_process_chatcat_connection, yahoo_process_search_connection}; int yahoo_read_ready(int id, void *fd, void *data) { struct yahoo_input_data *yid = data; char buf[1024]; int len; LOG(("read callback: id=%d fd=%p data=%p", id, fd, data)); if (!yid) return -2; do { len = YAHOO_CALLBACK(ext_yahoo_read) (fd, buf, sizeof(buf)); } while (len == -1 && errno == EINTR); if (len == -1 && (errno == EAGAIN || errno == EINTR)) /* we'll try again later */ return 1; if (len <= 0) { int e = errno; DEBUG_MSG(("len == %d (<= 0)", len)); if (yid->type == YAHOO_CONNECTION_PAGER) { YAHOO_CALLBACK(ext_yahoo_login_response) (yid->yd-> client_id, YAHOO_LOGIN_SOCK, NULL); } yahoo_process_connection[yid->type] (yid, 1); yahoo_input_close(yid); /* no need to return an error, because we've already fixed it */ if (len == 0) return 1; errno = e; LOG(("read error: %s", strerror(errno))); return -1; } yid->rxqueue = y_renew(unsigned char, yid->rxqueue, len + yid->rxlen + 1); memcpy(yid->rxqueue + yid->rxlen, buf, len); yid->rxlen += len; yid->rxqueue[yid->rxlen] = 0; yahoo_process_connection[yid->type] (yid, 0); return len; } int yahoo_init_with_attributes(const char *username, const char *password, ...) { va_list ap; struct yahoo_data *yd; yd = y_new0(struct yahoo_data, 1); if (!yd) return 0; yd->user = strdup(username); yd->password = strdup(password); yd->initial_status = -1; yd->current_status = -1; yd->client_id = ++last_id; add_to_list(yd); va_start(ap, password); yd->server_settings = _yahoo_assign_server_settings(ap); va_end(ap); return yd->client_id; } int yahoo_init(const char *username, const char *password) { return yahoo_init_with_attributes(username, password, NULL); } static void yahoo_connected(void *fd, int error, void *data) { struct connect_callback_data *ccd = data; struct yahoo_data *yd = ccd->yd; struct yahoo_packet *pkt; struct yahoo_input_data *yid; struct yahoo_server_settings *yss = yd->server_settings; if (error) { int tag; if (fallback_ports[ccd->i]) { char *host = yss->pager_host; if (!host) host = yss->pager_host_list[ccd->server_i]; yss->pager_port = fallback_ports[ccd->i++]; tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd-> client_id, host, yss->pager_port, yahoo_connected, ccd, 0); if (tag > 0) ccd->tag = tag; } else if (yss->pager_host_list && yss->pager_host_list[ccd->server_i]) { /* Get back to the default port */ yss->pager_port = pager_port; ccd->server_i++; LOG(("Fallback: Connecting to %s:%d", yss->pager_host_list[ccd->server_i], yss->pager_port)); ccd->i = 0; tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->client_id, yss->pager_host_list[ccd->server_i], yss->pager_port, yahoo_connected, ccd, 0); } else { FREE(ccd); YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, YAHOO_LOGIN_SOCK, NULL); } return; } FREE(ccd); /* fd == NULL && error == 0 means connect was cancelled */ if (!fd) return; pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YPACKET_STATUS_DEFAULT, yd->session_id); NOTICE(("Sending initial packet")); yahoo_packet_hash(pkt, 1, yd->user); yid = find_input_by_id_and_type(yd->client_id, YAHOO_CONNECTION_PAGER); yid->fd = fd; yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); yid->read_tag = YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, yid->fd, YAHOO_INPUT_READ, yid); } void *yahoo_get_fd(int id) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); if (!yid) return 0; else return yid->fd; } #if 0 void yahoo_send_buzz(int id, const char *from, const char *who) { yahoo_send_im(id, from, who, "", 1, 0); } #endif void yahoo_send_im(int id, const char *from, const char *who, const char *what, int utf8, int picture) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_packet *pkt = NULL; struct yahoo_data *yd; char pic_str[10]; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE, yd->session_id); snprintf(pic_str, sizeof(pic_str), "%d", picture); if (from && strcmp(from, yd->user)) yahoo_packet_hash(pkt, 0, yd->user); yahoo_packet_hash(pkt, 1, from ? from : yd->user); yahoo_packet_hash(pkt, 5, who); yahoo_packet_hash(pkt, 14, what); if (utf8) yahoo_packet_hash(pkt, 97, "1"); yahoo_packet_hash(pkt, 63, ";0"); /* imvironment name; or ;0 */ yahoo_packet_hash(pkt, 64, "0"); yahoo_packet_hash(pkt, 206, pic_str); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_send_typing(int id, const char *from, const char *who, int typ) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YPACKET_STATUS_NOTIFY, yd->session_id); yahoo_packet_hash(pkt, 5, who); yahoo_packet_hash(pkt, 1, from ? from : yd->user); yahoo_packet_hash(pkt, 14, " "); yahoo_packet_hash(pkt, 13, typ ? "1" : "0"); yahoo_packet_hash(pkt, 49, "TYPING"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; int old_status; char s[4]; if (!yid) return; yd = yid->yd; old_status = yd->current_status; yd->current_status = state; /* Thank you libpurple :) */ if (yd->current_status == YAHOO_STATUS_INVISIBLE) { pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE, YAHOO_STATUS_AVAILABLE, 0); yahoo_packet_hash(pkt, 13, "2"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); return; } pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE, yd->current_status, yd->session_id); snprintf(s, sizeof(s), "%d", yd->current_status); yahoo_packet_hash(pkt, 10, s); yahoo_packet_hash(pkt, 19, msg && state == YAHOO_STATUS_CUSTOM ? msg : ""); yahoo_packet_hash(pkt, 47, (away == 2)? "2": (away) ?"1":"0"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); if (old_status == YAHOO_STATUS_INVISIBLE) { pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE, YAHOO_STATUS_AVAILABLE, 0); yahoo_packet_hash(pkt, 13, "1"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } } void yahoo_logoff(int id) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; if (!yid) return; yd = yid->yd; LOG(("yahoo_logoff: current status: %d", yd->current_status)); if (yd->current_status != -1 && 0) { /* Meh. Don't send this. The event handlers are not going to get to do this so it'll just leak memory. And the TCP connection reset will hopefully be clear enough. */ pkt = yahoo_packet_new(YAHOO_SERVICE_LOGOFF, YPACKET_STATUS_DEFAULT, yd->session_id); yd->current_status = -1; if (pkt) { yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } } /* do { yahoo_input_close(yid); } while((yid = find_input_by_id(id)));*/ } #if 0 void yahoo_get_list(int id) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); if (pkt) { yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } } #endif static void _yahoo_http_connected(int id, void *fd, int error, void *data) { struct yahoo_input_data *yid = data; if (fd == NULL || error) { inputs = y_list_remove(inputs, yid); FREE(yid); return; } yid->fd = fd; yid->read_tag = YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, YAHOO_INPUT_READ, yid); } #if 0 /* FIXME Get address book from address.yahoo.com instead */ void yahoo_get_yab(int id) { struct yahoo_data *yd = find_conn_by_id(id); struct yahoo_input_data *yid; char url[1024]; char buff[2048]; if (!yd) return; yid = y_new0(struct yahoo_input_data, 1); yid->yd = yd; yid->type = YAHOO_CONNECTION_YAB; LOG(("Sending request for Address Book")); snprintf(url, 1024, "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us" "&diffs=1&t=0&tags=short&rt=0&prog-ver=8.1.0.249&useutf8=1&legenc=codepage-1252"); snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); inputs = y_list_prepend(inputs, yid); yahoo_http_get(yid->yd->client_id, url, buff, 0, 0, _yahoo_http_connected, yid); } struct yahoo_post_data { struct yahoo_input_data *yid; char *data; }; static void _yahoo_http_post_connected(int id, void *fd, int error, void *data) { struct yahoo_post_data *yad = data; struct yahoo_input_data *yid = yad->yid; char *buff = yad->data; if (!fd) { inputs = y_list_remove(inputs, yid); FREE(yid); return; } YAHOO_CALLBACK(ext_yahoo_write) (fd, buff, strlen(buff)); yid->fd = fd; yid->read_tag = YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, YAHOO_INPUT_READ, yid); FREE(buff); FREE(yad); } /* FIXME This is also likely affected */ void yahoo_set_yab(int id, struct yab *yab) { struct yahoo_post_data *yad = y_new0(struct yahoo_post_data, 1); struct yahoo_data *yd = find_conn_by_id(id); struct yahoo_input_data *yid; char url[1024]; char buff[1024]; char post[1024]; int size = 0; if (!yd) return; yid = y_new0(struct yahoo_input_data, 1); yid->type = YAHOO_CONNECTION_YAB; yid->yd = yd; if(yab->yid) size = snprintf(post, sizeof(post), "" "" "" "", yd->user, 9, yab->yid, /* Don't know why */ yab->id, yab->nname?yab->nname:""); else size = snprintf(post, sizeof(post), "" "" "" "", yd->user, 1, /* Don't know why */ yab->id, yab->nname?yab->nname:""); yad->yid = yid; yad->data = strdup(post); strcpy(url, "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us" "&sync=1&tags=short&noclear=1&useutf8=1&legenc=codepage-1252"); snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); inputs = y_list_prepend(inputs, yid); yahoo_http_post(yid->yd->client_id, url, buff, size, _yahoo_http_post_connected, yad); } void yahoo_set_identity_status(int id, const char *identity, int active) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(active ? YAHOO_SERVICE_IDACT : YAHOO_SERVICE_IDDEACT, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 3, identity); if (pkt) { yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } } void yahoo_refresh(int id) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_USERSTAT, YPACKET_STATUS_DEFAULT, yd->session_id); if (pkt) { yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } } #endif void yahoo_keepalive(int id) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } #if 0 void yahoo_chat_keepalive(int id) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_CHATPING, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } #endif void yahoo_add_buddy(int id, const char *who, const char *group, const char *msg) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; yd = yid->yd; if (!yd->logged_in) return; pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YPACKET_STATUS_DEFAULT, yd->session_id); if (msg != NULL) /* add message/request "it's me add me" */ yahoo_packet_hash(pkt, 14, msg); else yahoo_packet_hash(pkt, 14, ""); yahoo_packet_hash(pkt, 65, group); yahoo_packet_hash(pkt, 97, "1"); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 302, "319"); yahoo_packet_hash(pkt, 300, "319"); yahoo_packet_hash(pkt, 7, who); yahoo_packet_hash(pkt, 334, "0"); yahoo_packet_hash(pkt, 301, "319"); yahoo_packet_hash(pkt, 303, "319"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_remove_buddy(int id, const char *who, const char *group) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 7, who); yahoo_packet_hash(pkt, 65, group); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_confirm_buddy(int id, const char *who, int reject, const char *msg) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; yd = yid->yd; if (!yd->logged_in) return; pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_AUTHORIZATION, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 5, who); if (reject) yahoo_packet_hash(pkt, 13, "2"); else { yahoo_packet_hash(pkt, 241, "0"); yahoo_packet_hash(pkt, 13, "1"); } yahoo_packet_hash(pkt, 334, "0"); if (reject) { yahoo_packet_hash(pkt, 14, msg ? msg : ""); yahoo_packet_hash(pkt, 97, "1"); } yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } #if 0 void yahoo_ignore_buddy(int id, const char *who, int unignore) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; yd = yid->yd; if (!yd->logged_in) return; pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 7, who); yahoo_packet_hash(pkt, 13, unignore ? "2" : "1"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_stealth_buddy(int id, const char *who, int unstealth) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; yd = yid->yd; if (!yd->logged_in) return; pkt = yahoo_packet_new(YAHOO_SERVICE_STEALTH_PERM, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 7, who); yahoo_packet_hash(pkt, 31, unstealth ? "2" : "1"); yahoo_packet_hash(pkt, 13, "2"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } #endif void yahoo_change_buddy_group(int id, const char *who, const char *old_group, const char *new_group) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_CHANGE_GROUP, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 302, "240"); yahoo_packet_hash(pkt, 300, "240"); yahoo_packet_hash(pkt, 7, who); yahoo_packet_hash(pkt, 224, old_group); yahoo_packet_hash(pkt, 264, new_group); yahoo_packet_hash(pkt, 301, "240"); yahoo_packet_hash(pkt, 303, "240"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } #if 0 void yahoo_group_rename(int id, const char *old_group, const char *new_group) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_GROUPRENAME, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 65, old_group); yahoo_packet_hash(pkt, 67, new_group); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_conference_addinvite(int id, const char *from, const char *who, const char *room, const YList *members, const char *msg) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_CONFADDINVITE, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 51, who); yahoo_packet_hash(pkt, 57, room); yahoo_packet_hash(pkt, 58, msg); yahoo_packet_hash(pkt, 13, "0"); for (; members; members = members->next) { yahoo_packet_hash(pkt, 52, (char *)members->data); yahoo_packet_hash(pkt, 53, (char *)members->data); } /* 52, 53 -> other members? */ yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } #endif void yahoo_conference_invite(int id, const char *from, YList *who, const char *room, const char *msg) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_CONFINVITE, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 50, yd->user); for (; who; who = who->next) { yahoo_packet_hash(pkt, 52, (char *)who->data); } yahoo_packet_hash(pkt, 57, room); yahoo_packet_hash(pkt, 58, msg); yahoo_packet_hash(pkt, 13, "0"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_conference_logon(int id, const char *from, YList *who, const char *room) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGON, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 3, (from ? from : yd->user)); yahoo_packet_hash(pkt, 57, room); for (; who; who = who->next) yahoo_packet_hash(pkt, 3, (char *)who->data); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_conference_decline(int id, const char *from, YList *who, const char *room, const char *msg) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_CONFDECLINE, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 3, (from ? from : yd->user)); for (; who; who = who->next) yahoo_packet_hash(pkt, 3, (char *)who->data); yahoo_packet_hash(pkt, 57, room); yahoo_packet_hash(pkt, 14, msg); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_conference_logoff(int id, const char *from, YList *who, const char *room) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGOFF, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 3, (from ? from : yd->user)); for (; who; who = who->next) yahoo_packet_hash(pkt, 3, (char *)who->data); yahoo_packet_hash(pkt, 57, room); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_conference_message(int id, const char *from, YList *who, const char *room, const char *msg, int utf8) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_CONFMSG, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 53, (from ? from : yd->user)); for (; who; who = who->next) yahoo_packet_hash(pkt, 53, (char *)who->data); yahoo_packet_hash(pkt, 57, room); yahoo_packet_hash(pkt, 14, msg); if (utf8) yahoo_packet_hash(pkt, 97, "1"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } #if 0 void yahoo_get_chatrooms(int id, int chatroomid) { struct yahoo_data *yd = find_conn_by_id(id); struct yahoo_input_data *yid; char url[1024]; char buff[1024]; if (!yd) return; yid = y_new0(struct yahoo_input_data, 1); yid->yd = yd; yid->type = YAHOO_CONNECTION_CHATCAT; if (chatroomid == 0) { snprintf(url, 1024, "http://insider.msg.yahoo.com/ycontent/?chatcat=0"); } else { snprintf(url, 1024, "http://insider.msg.yahoo.com/ycontent/?chatroom_%d=0", chatroomid); } snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); inputs = y_list_prepend(inputs, yid); yahoo_http_get(yid->yd->client_id, url, buff, 0, 0, _yahoo_http_connected, yid); } void yahoo_chat_logon(int id, const char *from, const char *room, const char *roomid) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 109, yd->user); yahoo_packet_hash(pkt, 6, "abcde"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 104, room); yahoo_packet_hash(pkt, 129, roomid); yahoo_packet_hash(pkt, 62, "2"); /* ??? */ yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_chat_message(int id, const char *from, const char *room, const char *msg, const int msgtype, const int utf8) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; char buf[2]; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_COMMENT, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 104, room); yahoo_packet_hash(pkt, 117, msg); snprintf(buf, sizeof(buf), "%d", msgtype); yahoo_packet_hash(pkt, 124, buf); if (utf8) yahoo_packet_hash(pkt, 97, "1"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_chat_logoff(int id, const char *from) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_CHATLOGOUT, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_buddyicon_request(int id, const char *who) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YPACKET_STATUS_DEFAULT, 0); yahoo_packet_hash(pkt, 4, yd->user); yahoo_packet_hash(pkt, 5, who); yahoo_packet_hash(pkt, 13, "1"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_send_picture_info(int id, const char *who, const char *url, int checksum) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; char checksum_str[10]; if (!yid) return; yd = yid->yd; snprintf(checksum_str, sizeof(checksum_str), "%d", checksum); pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YPACKET_STATUS_DEFAULT, 0); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 4, yd->user); yahoo_packet_hash(pkt, 5, who); yahoo_packet_hash(pkt, 13, "2"); yahoo_packet_hash(pkt, 20, url); yahoo_packet_hash(pkt, 192, checksum_str); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_send_picture_update(int id, const char *who, int type) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; char type_str[10]; if (!yid) return; yd = yid->yd; snprintf(type_str, sizeof(type_str), "%d", type); pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_UPDATE, YPACKET_STATUS_DEFAULT, 0); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 5, who); yahoo_packet_hash(pkt, 206, type_str); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_send_picture_checksum(int id, const char *who, int checksum) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; char checksum_str[10]; if (!yid) return; yd = yid->yd; snprintf(checksum_str, sizeof(checksum_str), "%d", checksum); pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_CHECKSUM, YPACKET_STATUS_DEFAULT, 0); yahoo_packet_hash(pkt, 1, yd->user); if (who != 0) yahoo_packet_hash(pkt, 5, who); yahoo_packet_hash(pkt, 192, checksum_str); yahoo_packet_hash(pkt, 212, "1"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_webcam_close_feed(int id, const char *who) { struct yahoo_input_data *yid = find_input_by_id_and_webcam_user(id, who); if (yid) yahoo_input_close(yid); } void yahoo_webcam_get_feed(int id, const char *who) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; if (!yid) return; /* * add the user to the queue. this is a dirty hack, since * the yahoo server doesn't tell us who's key it's returning, * we have to just hope that it sends back keys in the same * order that we request them. * The queue is popped in yahoo_process_webcam_key */ webcam_queue = y_list_append(webcam_queue, who ? strdup(who) : NULL); yd = yid->yd; pkt = yahoo_packet_new(YAHOO_SERVICE_WEBCAM, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); if (who != NULL) yahoo_packet_hash(pkt, 5, who); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length, unsigned int timestamp) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM); unsigned char *packet; unsigned char header_len = 13; unsigned int pos = 0; if (!yid) return; packet = y_new0(unsigned char, header_len); packet[pos++] = header_len; packet[pos++] = 0; packet[pos++] = 5; /* version byte?? */ packet[pos++] = 0; pos += yahoo_put32(packet + pos, length); packet[pos++] = 2; /* packet type, image */ pos += yahoo_put32(packet + pos, timestamp); yahoo_add_to_send_queue(yid, packet, header_len); FREE(packet); if (length) yahoo_add_to_send_queue(yid, image, length); } void yahoo_webcam_accept_viewer(int id, const char *who, int accept) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM); char *packet = NULL; char *data = NULL; unsigned char header_len = 13; unsigned int pos = 0; unsigned int len = 0; if (!yid) return; data = strdup("u="); data = y_string_append(data, (char *)who); data = y_string_append(data, "\r\n"); len = strlen(data); packet = y_new0(char, header_len + len); packet[pos++] = header_len; packet[pos++] = 0; packet[pos++] = 5; /* version byte?? */ packet[pos++] = 0; pos += yahoo_put32(packet + pos, len); packet[pos++] = 0; /* packet type */ pos += yahoo_put32(packet + pos, accept); memcpy(packet + pos, data, len); FREE(data); yahoo_add_to_send_queue(yid, packet, header_len + len); FREE(packet); } void yahoo_webcam_invite(int id, const char *who) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_packet *pkt; if (!yid) return; pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YPACKET_STATUS_NOTIFY, yid->yd->session_id); yahoo_packet_hash(pkt, 49, "WEBCAMINVITE"); yahoo_packet_hash(pkt, 14, " "); yahoo_packet_hash(pkt, 13, "0"); yahoo_packet_hash(pkt, 1, yid->yd->user); yahoo_packet_hash(pkt, 5, who); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } static void yahoo_search_internal(int id, int t, const char *text, int g, int ar, int photo, int yahoo_only, int startpos, int total) { struct yahoo_data *yd = find_conn_by_id(id); struct yahoo_input_data *yid; char url[1024]; char buff[1024]; char *ctext, *p; if (!yd) return; yid = y_new0(struct yahoo_input_data, 1); yid->yd = yd; yid->type = YAHOO_CONNECTION_SEARCH; /* age range .ar=1 - 13-18, 2 - 18-25, 3 - 25-35, 4 - 35-50, 5 - 50-70, 6 - 70+ */ snprintf(buff, sizeof(buff), "&.sq=%%20&.tt=%d&.ss=%d", total, startpos); ctext = strdup(text); while ((p = strchr(ctext, ' '))) *p = '+'; snprintf(url, 1024, "http://members.yahoo.com/interests?.oc=m&.kw=%s&.sb=%d&.g=%d&.ar=0%s%s%s", ctext, t, g, photo ? "&.p=y" : "", yahoo_only ? "&.pg=y" : "", startpos ? buff : ""); FREE(ctext); snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); inputs = y_list_prepend(inputs, yid); yahoo_http_get(yid->yd->client_id, url, buff, 0, 0, _yahoo_http_connected, yid); } void yahoo_search(int id, enum yahoo_search_type t, const char *text, enum yahoo_search_gender g, enum yahoo_search_agerange ar, int photo, int yahoo_only) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_search_state *yss; if (!yid) return; if (!yid->ys) yid->ys = y_new0(struct yahoo_search_state, 1); yss = yid->ys; FREE(yss->lsearch_text); yss->lsearch_type = t; yss->lsearch_text = strdup(text); yss->lsearch_gender = g; yss->lsearch_agerange = ar; yss->lsearch_photo = photo; yss->lsearch_yahoo_only = yahoo_only; yahoo_search_internal(id, t, text, g, ar, photo, yahoo_only, 0, 0); } void yahoo_search_again(int id, int start) { struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_search_state *yss; if (!yid || !yid->ys) return; yss = yid->ys; if (start == -1) start = yss->lsearch_nstart + yss->lsearch_nfound; yahoo_search_internal(id, yss->lsearch_type, yss->lsearch_text, yss->lsearch_gender, yss->lsearch_agerange, yss->lsearch_photo, yss->lsearch_yahoo_only, start, yss->lsearch_ntotal); } void yahoo_send_picture(int id, const char *name, unsigned long size, yahoo_get_fd_callback callback, void *data) { /* Not Implemented */ } #endif /* File Transfer */ static YList *active_file_transfers = NULL; enum { FT_STATE_HEAD = 1, FT_STATE_RECV, FT_STATE_RECV_START, FT_STATE_SEND }; struct send_file_data { int client_id; char *id; char *who; char *filename; char *ip_addr; char *token; int size; struct yahoo_input_data *yid; int state; yahoo_get_fd_callback callback; void *data; }; #if 0 static char *yahoo_get_random(void) { int i = 0; int r = 0; int c = 0; char out[25]; out[24] = '\0'; out[23] = '$'; out[22] = '$'; for (i = 0; i < 22; i++) { if(r == 0) r = rand(); c = r%61; if(c<26) out[i] = c + 'a'; else if (c<52) out[i] = c - 26 + 'A'; else out[i] = c - 52 + '0'; r /= 61; } return strdup(out); } #endif static int _are_same_id(const void *sfd1, const void *id) { return strcmp(((struct send_file_data *)sfd1)->id, (char *)id); } static int _are_same_yid(const void *sfd1, const void *yid) { if(((struct send_file_data *)sfd1)->yid == yid) return 0; else return 1; } static struct send_file_data *yahoo_get_active_transfer(char *id) { YList *l = y_list_find_custom(active_file_transfers, id, _are_same_id); if(l) return (struct send_file_data *)l->data; return NULL; } static struct send_file_data *yahoo_get_active_transfer_with_yid(void *yid) { YList *l = y_list_find_custom(active_file_transfers, yid, _are_same_yid); if(l) return (struct send_file_data *)l->data; return NULL; } static void yahoo_add_active_transfer(struct send_file_data *sfd) { active_file_transfers = y_list_prepend(active_file_transfers, sfd); } static void yahoo_remove_active_transfer(struct send_file_data *sfd) { if (sfd == NULL) return; active_file_transfers = y_list_remove(active_file_transfers, sfd); free(sfd->id); free(sfd->who); free(sfd->filename); free(sfd->ip_addr); FREE(sfd); } static void _yahoo_ft_upload_connected(int id, void *fd, int error, void *data) { struct send_file_data *sfd = data; struct yahoo_input_data *yid = sfd->yid; if (!fd) { inputs = y_list_remove(inputs, yid); FREE(yid); return; } sfd->callback(id, fd, error, sfd->data); yid->fd = fd; yid->read_tag = YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, YAHOO_INPUT_READ, yid); } static void yahoo_file_transfer_upload(struct yahoo_data *yd, struct send_file_data *sfd) { char url[256]; char buff[4096]; char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL; struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1); yid->yd = yd; yid->type = YAHOO_CONNECTION_FT; inputs = y_list_prepend(inputs, yid); sfd->yid = yid; sfd->state = FT_STATE_SEND; token_enc = yahoo_urlencode(sfd->token); sender_enc = yahoo_urlencode(yd->user); recv_enc = yahoo_urlencode(sfd->who); snprintf(url, sizeof(url), "http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr, token_enc, sender_enc, recv_enc); snprintf(buff, sizeof(buff), "T=%s; Y=%s", yd->cookie_t, yd->cookie_y); yahoo_http_post(yd->client_id, url, buff, sfd->size, _yahoo_ft_upload_connected, sfd); FREE(token_enc); FREE(sender_enc); FREE(recv_enc); } static void yahoo_init_ft_recv(struct yahoo_data *yd, struct send_file_data *sfd) { char url[256]; char buff[1024]; char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL; struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1); yid->yd = yd; yid->type = YAHOO_CONNECTION_FT; inputs = y_list_prepend(inputs, yid); sfd->yid = yid; sfd->state = FT_STATE_HEAD; token_enc = yahoo_urlencode(sfd->token); sender_enc = yahoo_urlencode(sfd->who); recv_enc = yahoo_urlencode(yd->user); snprintf(url, sizeof(url), "http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr, token_enc, sender_enc, recv_enc); snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); yahoo_http_head(yid->yd->client_id, url, buff, 0, NULL, _yahoo_http_connected, yid); FREE(token_enc); FREE(sender_enc); FREE(recv_enc); } static void yahoo_file_transfer_accept(struct yahoo_input_data *yid, struct send_file_data *sfd) { struct yahoo_packet *pkt; pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFERACCEPT, YPACKET_STATUS_DEFAULT, yid->yd->session_id); yahoo_packet_hash(pkt, 1, yid->yd->user); yahoo_packet_hash(pkt, 5, sfd->who); yahoo_packet_hash(pkt, 265, sfd->id); yahoo_packet_hash(pkt, 27, sfd->filename); yahoo_packet_hash(pkt, 249, "3"); yahoo_packet_hash(pkt, 251, sfd->token); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); yahoo_init_ft_recv(yid->yd, sfd); } static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { YList *l; struct send_file_data *sfd; char *id = NULL; char *token = NULL; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 4: /* who */ break; case 5: /* Me... don't care */ break; case 249: break; case 265: id = pair->value; break; case 251: token = pair->value; break; case 27: /* filename */ break; } } sfd = yahoo_get_active_transfer(id); if (sfd) { sfd->token = strdup(token); yahoo_file_transfer_upload(yid->yd, sfd); } else { YAHOO_CALLBACK(ext_yahoo_file_transfer_done) (yid->yd->client_id, YAHOO_FILE_TRANSFER_UNKNOWN, sfd ? sfd->data : NULL); yahoo_remove_active_transfer(sfd); } } static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { YList *l; char *id = NULL; char *token = NULL; char *ip_addr = NULL; struct send_file_data *sfd; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 1: case 4: /* who */ break; case 5: /* Me... don't care */ break; case 249: break; case 265: id = pair->value; break; case 250: ip_addr = pair->value; break; case 251: token = pair->value; break; case 27: /* filename */ break; } } sfd = yahoo_get_active_transfer(id); if (sfd) { sfd->token = strdup(token); sfd->ip_addr = strdup(ip_addr); yahoo_file_transfer_accept(yid, sfd); } else { YAHOO_CALLBACK(ext_yahoo_file_transfer_done) (yid->yd->client_id, YAHOO_FILE_TRANSFER_UNKNOWN, sfd ? sfd->data : NULL); yahoo_remove_active_transfer(sfd); } } static void yahoo_send_filetransferinfo(struct yahoo_data *yd, struct send_file_data *sfd) { struct yahoo_input_data *yid; struct yahoo_packet *pkt; yid = find_input_by_id_and_type(yd->client_id, YAHOO_CONNECTION_PAGER); sfd->ip_addr = YAHOO_CALLBACK(ext_yahoo_get_ip_addr)("relay.yahoo.com"); if (!sfd->ip_addr) { YAHOO_CALLBACK(ext_yahoo_file_transfer_done) (yd->client_id, YAHOO_FILE_TRANSFER_RELAY, sfd->data); yahoo_remove_active_transfer(sfd); return; } pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFERINFO, YPACKET_STATUS_DEFAULT, yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 5, sfd->who); yahoo_packet_hash(pkt, 265, sfd->id); yahoo_packet_hash(pkt, 27, sfd->filename); yahoo_packet_hash(pkt, 249, "3"); yahoo_packet_hash(pkt, 250, sfd->ip_addr); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } static void yahoo_process_filetransfer(struct yahoo_input_data *yid, struct yahoo_packet *pkt) { YList *l; char *who = NULL; char *filename = NULL; char *msg = NULL; char *id = NULL; int action = 0; int size = 0; struct yahoo_data *yd = yid->yd; struct send_file_data *sfd; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; switch (pair->key) { case 4: who = pair->value; break; case 5: /* Me... don't care */ break; case 222: action = atoi(pair->value); break; case 265: id = pair->value; break; case 266: /* Don't know */ break; case 302: /* Start Data? */ break; case 300: break; case 27: filename = pair->value; break; case 28: size = atoi(pair->value); break; case 14: msg = pair->value; case 301: /* End Data? */ break; case 303: break; } } if (action == YAHOO_FILE_TRANSFER_INIT) { /* Received a FT request from buddy */ sfd = y_new0(struct send_file_data, 1); sfd->client_id = yd->client_id; sfd->id = strdup(id); sfd->who = strdup(who); sfd->filename = strdup(filename); sfd->size = size; yahoo_add_active_transfer(sfd); YAHOO_CALLBACK(ext_yahoo_got_file) (yd->client_id, yd->user, who, msg, filename, size, sfd->id); } else { /* Response to our request */ sfd = yahoo_get_active_transfer(id); if (sfd && action == YAHOO_FILE_TRANSFER_ACCEPT) { yahoo_send_filetransferinfo(yd, sfd); } else if (!sfd || action == YAHOO_FILE_TRANSFER_REJECT) { YAHOO_CALLBACK(ext_yahoo_file_transfer_done) (yd->client_id, YAHOO_FILE_TRANSFER_REJECT, sfd ? sfd->data : NULL); yahoo_remove_active_transfer(sfd); } } } #if 0 void yahoo_send_file(int id, const char *who, const char *msg, const char *name, unsigned long size, yahoo_get_fd_callback callback, void *data) { struct yahoo_packet *pkt = NULL; char size_str[10]; struct yahoo_input_data *yid; struct yahoo_data *yd; struct send_file_data *sfd; yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); yd = find_conn_by_id(id); sfd = y_new0(struct send_file_data, 1); sfd->client_id = id; sfd->id = yahoo_get_random(); sfd->who = strdup(who); sfd->filename = strdup(name); sfd->size = size; sfd->callback = callback; sfd->data = data; yahoo_add_active_transfer(sfd); if (!yd) return; pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER, YPACKET_STATUS_DEFAULT, yd->session_id); snprintf(size_str, sizeof(size_str), "%ld", size); yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 5, who); yahoo_packet_hash(pkt, 265, sfd->id); yahoo_packet_hash(pkt, 222, "1"); yahoo_packet_hash(pkt, 266, "1"); yahoo_packet_hash(pkt, 302, "268"); yahoo_packet_hash(pkt, 300, "268"); yahoo_packet_hash(pkt, 27, name); yahoo_packet_hash(pkt, 28, size_str); yahoo_packet_hash(pkt, 301, "268"); yahoo_packet_hash(pkt, 303, "268"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } void yahoo_send_file_transfer_response(int client_id, int response, char *id, void *data) { struct yahoo_packet *pkt = NULL; char resp[2]; struct yahoo_input_data *yid; struct send_file_data *sfd = yahoo_get_active_transfer(id); sfd->data = data; yid = find_input_by_id_and_type(client_id, YAHOO_CONNECTION_PAGER); pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER, YPACKET_STATUS_DEFAULT, yid->yd->session_id); snprintf(resp, sizeof(resp), "%d", response); yahoo_packet_hash(pkt, 1, yid->yd->user); yahoo_packet_hash(pkt, 5, sfd->who); yahoo_packet_hash(pkt, 265, sfd->id); yahoo_packet_hash(pkt, 222, resp); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); if(response == YAHOO_FILE_TRANSFER_REJECT) yahoo_remove_active_transfer(sfd); } #endif static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over) { struct send_file_data *sfd; struct yahoo_data *yd = yid->yd; sfd = yahoo_get_active_transfer_with_yid(yid); if (!sfd) { LOG(("Something funny happened. yid %p has no sfd.\n", yid)); return; } /* * We want to handle only the complete data with HEAD since we don't * want a situation where both the GET and HEAD are active. * With SEND, we really can't do much with partial response */ if ((sfd->state == FT_STATE_HEAD || sfd->state == FT_STATE_SEND) && !over) return; if (sfd->state == FT_STATE_HEAD) { /* Do a GET */ char url[256]; char buff[1024]; char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL; struct yahoo_input_data *yid_ft = y_new0(struct yahoo_input_data, 1); yid_ft->yd = yid->yd; yid_ft->type = YAHOO_CONNECTION_FT; inputs = y_list_prepend(inputs, yid_ft); sfd->yid = yid_ft; sfd->state = FT_STATE_RECV; token_enc = yahoo_urlencode(sfd->token); sender_enc = yahoo_urlencode(sfd->who); recv_enc = yahoo_urlencode(yd->user); snprintf(url, sizeof(url), "http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr, token_enc, sender_enc, recv_enc); snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); yahoo_http_get(yd->client_id, url, buff, 1, 1, _yahoo_http_connected, yid_ft); FREE(token_enc); FREE(sender_enc); FREE(recv_enc); } else if (sfd->state == FT_STATE_RECV || sfd->state == FT_STATE_RECV_START) { unsigned char *data_begin = NULL; if (yid->rxlen == 0) yahoo_remove_active_transfer(sfd); if (sfd->state != FT_STATE_RECV_START && (data_begin = (unsigned char *)strstr((char *)yid->rxqueue, "\r\n\r\n"))) { sfd->state = FT_STATE_RECV_START; yid->rxlen -= 4+(data_begin-yid->rxqueue)/sizeof(char); data_begin += 4; if (yid->rxlen > 0) YAHOO_CALLBACK(ext_yahoo_got_ft_data) (yd->client_id, data_begin, yid->rxlen, sfd->data); } else if (sfd->state == FT_STATE_RECV_START) YAHOO_CALLBACK(ext_yahoo_got_ft_data) (yd->client_id, yid->rxqueue, yid->rxlen, sfd->data); FREE(yid->rxqueue); yid->rxqueue = NULL; yid->rxlen = 0; } else if (sfd->state == FT_STATE_SEND) { /* Sent file completed */ int len = 0; char *off = strstr((char *)yid->rxqueue, "Content-Length: "); if (off) { off += 16; len = atoi(off); } if (len < sfd->size) YAHOO_CALLBACK(ext_yahoo_file_transfer_done) (yd->client_id, YAHOO_FILE_TRANSFER_FAILED, sfd->data); else YAHOO_CALLBACK(ext_yahoo_file_transfer_done) (yd->client_id, YAHOO_FILE_TRANSFER_DONE, sfd->data); yahoo_remove_active_transfer(sfd); } } /* End File Transfer */ #if 0 enum yahoo_status yahoo_current_status(int id) { struct yahoo_data *yd = find_conn_by_id(id); if (!yd) return YAHOO_STATUS_OFFLINE; return yd->current_status; } const YList *yahoo_get_buddylist(int id) { struct yahoo_data *yd = find_conn_by_id(id); if (!yd) return NULL; return yd->buddies; } const YList *yahoo_get_ignorelist(int id) { struct yahoo_data *yd = find_conn_by_id(id); if (!yd) return NULL; return yd->ignore; } const YList *yahoo_get_identities(int id) { struct yahoo_data *yd = find_conn_by_id(id); if (!yd) return NULL; return yd->identities; } const char *yahoo_get_cookie(int id, const char *which) { struct yahoo_data *yd = find_conn_by_id(id); if (!yd) return NULL; if (!strncasecmp(which, "y", 1)) return yd->cookie_y; if (!strncasecmp(which, "b", 1)) return yd->cookie_b; if (!strncasecmp(which, "t", 1)) return yd->cookie_t; if (!strncasecmp(which, "c", 1)) return yd->cookie_c; if (!strncasecmp(which, "login", 5)) return yd->login_cookie; return NULL; } #endif const char *yahoo_get_profile_url(void) { return profile_url; } bitlbee-3.2.1/protocols/yahoo/yahoo.c0000644000175000017500000006102312245474076017146 0ustar wilmerwilmer/* * libyahoo2 wrapper to BitlBee * * Mostly Copyright 2004-2012 Wilmer van der Gaast * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include #include "nogaim.h" #include "yahoo2.h" #include "yahoo2_callbacks.h" #define BYAHOO_DEFAULT_GROUP "Buddies" /* A hack to handle removal of buddies not in the group "Buddies" correctly */ struct byahoo_buddygroups { char *buddy; char *group; }; struct byahoo_data { int y2_id; int current_status; gboolean logged_in; GSList *buddygroups; }; struct byahoo_input_data { int h; void *d; }; struct byahoo_conf_invitation { char *name; struct groupchat *c; int yid; YList *members; struct im_connection *ic; }; static GSList *byahoo_inputs = NULL; static int byahoo_chat_id = 0; static char *byahoo_strip( const char *in ) { int len; /* This should get rid of the markup noise at the beginning of the string. */ while( *in ) { if( g_strncasecmp( in, "' ); if( !s ) break; in = s + 1; } else if( strncmp( in, "\e[", 2 ) == 0 ) { const char *s; for( s = in + 2; *s && *s != 'm'; s ++ ); if( *s != 'm' ) break; in = s + 1; } else { break; } } /* This is supposed to get rid of the noise at the end of the line. */ len = strlen( in ); while( len > 0 && ( in[len-1] == '>' || in[len-1] == 'm' ) ) { int blen = len; const char *search; if( in[len-1] == '>' ) search = " 0 && strncmp( in + len, search, 2 ) != 0 ) len --; if( len <= 0 && strncmp( in, search, 2 ) != 0 ) { len = blen; break; } } return( g_strndup( in, len ) ); } static void byahoo_init( account_t *acc ) { set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc ); acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE; } static void byahoo_login( account_t *acc ) { struct im_connection *ic = imcb_new( acc ); struct byahoo_data *yd = ic->proto_data = g_new0( struct byahoo_data, 1 ); char *s; yd->logged_in = FALSE; yd->current_status = YAHOO_STATUS_AVAILABLE; if( ( s = strchr( acc->user, '@' ) ) && g_strcasecmp( s, "@yahoo.com" ) == 0 ) imcb_error( ic, "Your Yahoo! username should just be a username. " "Do not include any @domain part." ); imcb_log( ic, "Connecting" ); yd->y2_id = yahoo_init( acc->user, acc->pass ); yahoo_login( yd->y2_id, yd->current_status ); } static void byahoo_logout( struct im_connection *ic ) { struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; GSList *l; while( ic->groupchats ) imcb_chat_free( ic->groupchats->data ); for( l = yd->buddygroups; l; l = l->next ) { struct byahoo_buddygroups *bg = l->data; g_free( bg->buddy ); g_free( bg->group ); g_free( bg ); } g_slist_free( yd->buddygroups ); yahoo_logoff( yd->y2_id ); g_free( yd ); } static void byahoo_get_info(struct im_connection *ic, char *who) { /* Just make an URL and let the user fetch the info */ imcb_log(ic, "%s\n%s: %s%s", _("User Info"), _("For now, fetch yourself"), yahoo_get_profile_url(), who); } static int byahoo_buddy_msg( struct im_connection *ic, char *who, char *what, int flags ) { struct byahoo_data *yd = ic->proto_data; yahoo_send_im( yd->y2_id, NULL, who, what, 1, 0 ); return 1; } static int byahoo_send_typing( struct im_connection *ic, char *who, int typing ) { struct byahoo_data *yd = ic->proto_data; yahoo_send_typing( yd->y2_id, NULL, who, ( typing & OPT_TYPING ) != 0 ); return 1; } static void byahoo_set_away( struct im_connection *ic, char *state, char *msg ) { struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; if( state && msg == NULL ) { /* Use these states only if msg doesn't contain additional info since away messages are only supported with CUSTOM. */ if( g_strcasecmp( state, "Be Right Back" ) == 0 ) yd->current_status = YAHOO_STATUS_BRB; else if( g_strcasecmp( state, "Busy" ) == 0 ) yd->current_status = YAHOO_STATUS_BUSY; else if( g_strcasecmp( state, "Not At Home" ) == 0 ) yd->current_status = YAHOO_STATUS_NOTATHOME; else if( g_strcasecmp( state, "Not At Desk" ) == 0 ) yd->current_status = YAHOO_STATUS_NOTATDESK; else if( g_strcasecmp( state, "Not In Office" ) == 0 ) yd->current_status = YAHOO_STATUS_NOTINOFFICE; else if( g_strcasecmp( state, "On Phone" ) == 0 ) yd->current_status = YAHOO_STATUS_ONPHONE; else if( g_strcasecmp( state, "On Vacation" ) == 0 ) yd->current_status = YAHOO_STATUS_ONVACATION; else if( g_strcasecmp( state, "Out To Lunch" ) == 0 ) yd->current_status = YAHOO_STATUS_OUTTOLUNCH; else if( g_strcasecmp( state, "Stepped Out" ) == 0 ) yd->current_status = YAHOO_STATUS_STEPPEDOUT; else if( g_strcasecmp( state, "Invisible" ) == 0 ) yd->current_status = YAHOO_STATUS_INVISIBLE; else yd->current_status = YAHOO_STATUS_CUSTOM; } else if( msg ) yd->current_status = YAHOO_STATUS_CUSTOM; else yd->current_status = YAHOO_STATUS_AVAILABLE; yahoo_set_away( yd->y2_id, yd->current_status, msg, state ? 2 : 0 ); } static GList *byahoo_away_states( struct im_connection *ic ) { static GList *m = NULL; if( m == NULL ) { m = g_list_append( m, "Be Right Back" ); m = g_list_append( m, "Busy" ); m = g_list_append( m, "Not At Home" ); m = g_list_append( m, "Not At Desk" ); m = g_list_append( m, "Not In Office" ); m = g_list_append( m, "On Phone" ); m = g_list_append( m, "On Vacation" ); m = g_list_append( m, "Out To Lunch" ); m = g_list_append( m, "Stepped Out" ); m = g_list_append( m, "Invisible" ); } return m; } static void byahoo_keepalive( struct im_connection *ic ) { struct byahoo_data *yd = ic->proto_data; yahoo_keepalive( yd->y2_id ); } static void byahoo_add_buddy( struct im_connection *ic, char *who, char *group ) { struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; bee_user_t *bu; if( group && ( bu = bee_user_by_handle( ic->bee, ic, who ) ) && bu->group ) { GSList *bgl; /* If the person is in our list already, this is a group change. */ yahoo_change_buddy_group( yd->y2_id, who, bu->group->name, group ); /* No idea how often people have people in multiple groups and BitlBee doesn't currently support this anyway .. but keep this struct up-to-date for now. */ for( bgl = yd->buddygroups; bgl; bgl = bgl->next ) { struct byahoo_buddygroups *bg = bgl->data; if( g_strcasecmp( bg->buddy, who ) == 0 && g_strcasecmp( bg->group, bu->group->name ) == 0 ) { g_free( bg->group ); bg->group = g_strdup( group ); } } } else yahoo_add_buddy( yd->y2_id, who, group ? group : BYAHOO_DEFAULT_GROUP, NULL ); } static void byahoo_remove_buddy( struct im_connection *ic, char *who, char *group ) { struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; GSList *bgl; yahoo_remove_buddy( yd->y2_id, who, BYAHOO_DEFAULT_GROUP ); for( bgl = yd->buddygroups; bgl; bgl = bgl->next ) { struct byahoo_buddygroups *bg = bgl->data; if( g_strcasecmp( bg->buddy, who ) == 0 ) yahoo_remove_buddy( yd->y2_id, who, bg->group ); } } static void byahoo_chat_msg( struct groupchat *c, char *message, int flags ) { struct byahoo_data *yd = (struct byahoo_data *) c->ic->proto_data; yahoo_conference_message( yd->y2_id, NULL, c->data, c->title, message, 1 ); } static void byahoo_chat_invite( struct groupchat *c, char *who, char *msg ) { struct byahoo_data *yd = (struct byahoo_data *) c->ic->proto_data; yahoo_conference_invite( yd->y2_id, NULL, c->data, c->title, msg ? msg : "" ); } static void byahoo_chat_leave( struct groupchat *c ) { struct byahoo_data *yd = (struct byahoo_data *) c->ic->proto_data; yahoo_conference_logoff( yd->y2_id, NULL, c->data, c->title ); imcb_chat_free( c ); } static struct groupchat *byahoo_chat_with( struct im_connection *ic, char *who ) { struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; struct groupchat *c; char *roomname; YList *members; roomname = g_strdup_printf( "%s-Bee-%d", ic->acc->user, byahoo_chat_id ); c = imcb_chat_new( ic, roomname ); imcb_chat_add_buddy( c, ic->acc->user ); /* FIXME: Free this thing when the chat's destroyed. We can't *always* do this because it's not always created here. */ c->data = members = g_new0( YList, 1 ); members->data = g_strdup( who ); yahoo_conference_invite( yd->y2_id, NULL, members, roomname, "Please join my groupchat..." ); g_free( roomname ); return c; } static void byahoo_auth_allow( struct im_connection *ic, const char *who ) { struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; yahoo_confirm_buddy( yd->y2_id, who, 0, "" ); } static void byahoo_auth_deny( struct im_connection *ic, const char *who ) { struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; yahoo_confirm_buddy( yd->y2_id, who, 1, "" ); } void byahoo_initmodule( ) { struct prpl *ret = g_new0(struct prpl, 1); ret->name = "yahoo"; ret->mms = 832; /* this guess taken from libotr UPGRADING file */ ret->init = byahoo_init; ret->login = byahoo_login; ret->keepalive = byahoo_keepalive; ret->logout = byahoo_logout; ret->buddy_msg = byahoo_buddy_msg; ret->get_info = byahoo_get_info; ret->away_states = byahoo_away_states; ret->set_away = byahoo_set_away; ret->add_buddy = byahoo_add_buddy; ret->remove_buddy = byahoo_remove_buddy; ret->send_typing = byahoo_send_typing; ret->chat_msg = byahoo_chat_msg; ret->chat_invite = byahoo_chat_invite; ret->chat_leave = byahoo_chat_leave; ret->chat_with = byahoo_chat_with; ret->handle_cmp = g_strcasecmp; ret->auth_allow = byahoo_auth_allow; ret->auth_deny = byahoo_auth_deny; register_protocol(ret); } static struct im_connection *byahoo_get_ic_by_id( int id ) { GSList *l; struct im_connection *ic; struct byahoo_data *yd; for( l = get_connections(); l; l = l->next ) { ic = l->data; yd = ic->proto_data; if( strcmp( ic->acc->prpl->name, "yahoo" ) == 0 && yd->y2_id == id ) return( ic ); } return( NULL ); } /* Now it's callback time! */ struct byahoo_connect_callback_data { int fd; yahoo_connect_callback callback; gpointer data; int id; }; void byahoo_connect_callback( gpointer data, gint source, b_input_condition cond ) { struct byahoo_connect_callback_data *d = data; if( !byahoo_get_ic_by_id( d->id ) ) { g_free( d ); return; } d->callback( NULL + d->fd, 0, d->data ); g_free( d ); } struct byahoo_read_ready_data { int id; int fd; int tag; gpointer data; }; gboolean byahoo_read_ready_callback( gpointer data, gint source, b_input_condition cond ) { struct byahoo_read_ready_data *d = data; if( !byahoo_get_ic_by_id( d->id ) ) /* WTF doesn't libyahoo clean this up? */ return FALSE; yahoo_read_ready( d->id, NULL + d->fd, d->data ); return TRUE; } struct byahoo_write_ready_data { int id; int fd; int tag; gpointer data; }; gboolean byahoo_write_ready_callback( gpointer data, gint source, b_input_condition cond ) { struct byahoo_write_ready_data *d = data; return yahoo_write_ready( d->id, NULL + d->fd, d->data ); } void ext_yahoo_login_response( int id, int succ, const char *url ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); struct byahoo_data *yd = NULL; if( ic == NULL ) { /* libyahoo2 seems to call this one twice when something went wrong sometimes. Don't know why. Because we clean up the connection on the first failure, the second should be ignored. */ return; } yd = (struct byahoo_data *) ic->proto_data; if( succ == YAHOO_LOGIN_OK ) { imcb_connected( ic ); yd->logged_in = TRUE; } else { char *errstr; int allow_reconnect = FALSE; yd->logged_in = FALSE; if( succ == YAHOO_LOGIN_UNAME ) errstr = "Incorrect Yahoo! username"; else if( succ == YAHOO_LOGIN_PASSWD ) errstr = "Incorrect Yahoo! password"; else if( succ == YAHOO_LOGIN_LOCK ) errstr = "Yahoo! account locked"; else if( succ == 1236 ) errstr = "Yahoo! account locked or machine temporarily banned"; else if( succ == YAHOO_LOGIN_DUPL ) errstr = "Logged in on a different machine or device"; else if( succ == YAHOO_LOGIN_SOCK ) { errstr = "Socket problem"; allow_reconnect = TRUE; } else errstr = "Unknown error"; if( url && *url ) imcb_error( ic, "Error %d (%s). See %s for more information.", succ, errstr, url ); else imcb_error( ic, "Error %d (%s)", succ, errstr ); imc_logout( ic, allow_reconnect ); } } void ext_yahoo_got_buddies( int id, YList *buds ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); struct byahoo_data *yd = ic->proto_data; YList *bl = buds; while( bl ) { struct yahoo_buddy *b = bl->data; struct byahoo_buddygroups *bg; if( strcmp( b->group, BYAHOO_DEFAULT_GROUP ) != 0 ) { bg = g_new0( struct byahoo_buddygroups, 1 ); bg->buddy = g_strdup( b->id ); bg->group = g_strdup( b->group ); yd->buddygroups = g_slist_append( yd->buddygroups, bg ); } imcb_add_buddy( ic, b->id, b->group ); imcb_rename_buddy( ic, b->id, b->real_name ); bl = bl->next; } } void ext_yahoo_got_identities( int id, YList *ids ) { } void ext_yahoo_got_cookies( int id ) { } void ext_yahoo_status_changed( int id, const char *who, int stat, const char *msg, int away, int idle, int mobile ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); char *state_string = NULL; int flags = OPT_LOGGED_IN; if( away ) flags |= OPT_AWAY; if( mobile ) flags |= OPT_MOBILE; switch (stat) { case YAHOO_STATUS_BRB: state_string = "Be Right Back"; break; case YAHOO_STATUS_BUSY: state_string = "Busy"; break; case YAHOO_STATUS_NOTATHOME: state_string = "Not At Home"; break; case YAHOO_STATUS_NOTATDESK: state_string = "Not At Desk"; break; case YAHOO_STATUS_NOTINOFFICE: state_string = "Not In Office"; break; case YAHOO_STATUS_ONPHONE: state_string = "On Phone"; break; case YAHOO_STATUS_ONVACATION: state_string = "On Vacation"; break; case YAHOO_STATUS_OUTTOLUNCH: state_string = "Out To Lunch"; break; case YAHOO_STATUS_STEPPEDOUT: state_string = "Stepped Out"; break; case YAHOO_STATUS_INVISIBLE: state_string = "Invisible"; break; case YAHOO_STATUS_CUSTOM: state_string = "Away"; break; case YAHOO_STATUS_IDLE: state_string = "Idle"; break; case YAHOO_STATUS_OFFLINE: state_string = "Offline"; flags = 0; break; } imcb_buddy_status( ic, who, flags, state_string, msg ); if( stat == YAHOO_STATUS_IDLE ) imcb_buddy_times( ic, who, 0, idle ); } void ext_yahoo_got_buzz( int id, const char *me, const char *who, long tm ) { } void ext_yahoo_got_im( int id, const char *me, const char *who, const char *msg, long tm, int stat, int utf8 ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); char *m; if( msg ) { m = byahoo_strip( msg ); imcb_buddy_msg( ic, (char*) who, (char*) m, 0, 0 ); g_free( m ); } } void ext_yahoo_got_file( int id, const char *ignored, const char *who, const char *msg, const char *fname, unsigned long fesize, char *trid ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); imcb_log( ic, "Got a file transfer (file = %s) from %s. Ignoring for now due to lack of support.", fname, who ); } void ext_yahoo_got_ft_data( int id, const unsigned char *in, int len, void *data ) { } void ext_yahoo_file_transfer_done( int id, int result, void *data ) { } void ext_yahoo_typing_notify( int id, const char *ignored, const char *who, int stat ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); if( stat == 1 ) imcb_buddy_typing( ic, (char*) who, OPT_TYPING ); else imcb_buddy_typing( ic, (char*) who, 0 ); } void ext_yahoo_system_message( int id, const char *me, const char *who, const char *msg ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); imcb_log( ic, "Yahoo! system message: %s", msg ); } void ext_yahoo_webcam_invite( int id, const char *ignored, const char *from ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); imcb_log( ic, "Got a webcam invitation from %s. IRC+webcams is a no-no though...", from ); } void ext_yahoo_error( int id, const char *err, int fatal, int num ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); imcb_error( ic, "%s", err ); } /* TODO: Clear up the mess of inp and d structures */ int ext_yahoo_add_handler( int id, void *fd_, yahoo_input_condition cond, void *data ) { struct byahoo_input_data *inp = g_new0( struct byahoo_input_data, 1 ); int fd = (long) fd_; if( cond == YAHOO_INPUT_READ ) { struct byahoo_read_ready_data *d = g_new0( struct byahoo_read_ready_data, 1 ); d->id = id; d->fd = fd; d->data = data; inp->d = d; d->tag = inp->h = b_input_add( fd, B_EV_IO_READ, (b_event_handler) byahoo_read_ready_callback, (gpointer) d ); } else if( cond == YAHOO_INPUT_WRITE ) { struct byahoo_write_ready_data *d = g_new0( struct byahoo_write_ready_data, 1 ); d->id = id; d->fd = fd; d->data = data; inp->d = d; d->tag = inp->h = b_input_add( fd, B_EV_IO_WRITE, (b_event_handler) byahoo_write_ready_callback, (gpointer) d ); } else { g_free( inp ); return -1; /* Panic... */ } byahoo_inputs = g_slist_append( byahoo_inputs, inp ); return inp->h; } void ext_yahoo_remove_handler( int id, int tag ) { struct byahoo_input_data *inp; GSList *l = byahoo_inputs; while( l ) { inp = l->data; if( inp->h == tag ) { g_free( inp->d ); g_free( inp ); byahoo_inputs = g_slist_remove( byahoo_inputs, inp ); break; } l = l->next; } b_event_remove( tag ); } int ext_yahoo_connect_async( int id, const char *host, int port, yahoo_connect_callback callback, void *data, int use_ssl ) { struct byahoo_connect_callback_data *d; int fd; d = g_new0( struct byahoo_connect_callback_data, 1 ); if( ( fd = proxy_connect( host, port, (b_event_handler) byahoo_connect_callback, (gpointer) d ) ) < 0 ) { g_free( d ); return( fd ); } d->fd = fd; d->callback = callback; d->data = data; d->id = id; return fd; } char *ext_yahoo_get_ip_addr( const char *domain ) { return NULL; } int ext_yahoo_write( void *fd, char *buf, int len ) { return write( (long) fd, buf, len ); } int ext_yahoo_read( void *fd, char *buf, int len ) { return read( (long) fd, buf, len ); } void ext_yahoo_close( void *fd ) { close( (long) fd ); } void ext_yahoo_got_buddy_change_group( int id, const char *me, const char *who, const char *old_group, const char *new_group ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); imcb_add_buddy( ic, who, new_group ); } static void byahoo_accept_conf( void *data ) { struct byahoo_conf_invitation *inv = data; struct groupchat *b = NULL; GSList *l; for( l = inv->ic->groupchats; l; l = l->next ) { b = l->data; if( b == inv->c ) break; } if( b != NULL ) { yahoo_conference_logon( inv->yid, NULL, inv->members, inv->name ); imcb_chat_add_buddy( inv->c, inv->ic->acc->user ); } else { imcb_log( inv->ic, "Duplicate/corrupted invitation to `%s'.", inv->name ); } g_free( inv->name ); g_free( inv ); } static void byahoo_reject_conf( void *data ) { struct byahoo_conf_invitation *inv = data; yahoo_conference_decline( inv->yid, NULL, inv->members, inv->name, "User rejected groupchat" ); imcb_chat_free( inv->c ); g_free( inv->name ); g_free( inv ); } void ext_yahoo_got_conf_invite( int id, const char *ignored, const char *who, const char *room, const char *msg, YList *members ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); struct byahoo_conf_invitation *inv; char txt[1024]; YList *m; if( g_strcasecmp( who, ic->acc->user ) == 0 ) /* WTF, Yahoo! seems to echo these now? */ return; inv = g_malloc( sizeof( struct byahoo_conf_invitation ) ); memset( inv, 0, sizeof( struct byahoo_conf_invitation ) ); inv->name = g_strdup( room ); inv->c = imcb_chat_new( ic, (char*) room ); inv->c->data = members; inv->yid = id; inv->members = members; inv->ic = ic; for( m = members; m; m = m->next ) if( g_strcasecmp( m->data, ic->acc->user ) != 0 ) imcb_chat_add_buddy( inv->c, m->data ); g_snprintf( txt, 1024, "Got an invitation to chatroom %s from %s: %s", room, who, msg ); imcb_ask( ic, txt, inv, byahoo_accept_conf, byahoo_reject_conf ); } void ext_yahoo_conf_userdecline( int id, const char *ignored, const char *who, const char *room, const char *msg ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); imcb_log( ic, "Invite to chatroom %s rejected by %s: %s", room, who, msg ); } void ext_yahoo_conf_userjoin( int id, const char *ignored, const char *who, const char *room ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); struct groupchat *c = bee_chat_by_title( ic->bee, ic, room ); if( c ) imcb_chat_add_buddy( c, (char*) who ); } void ext_yahoo_conf_userleave( int id, const char *ignored, const char *who, const char *room ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); struct groupchat *c = bee_chat_by_title( ic->bee, ic, room ); if( c ) imcb_chat_remove_buddy( c, (char*) who, "" ); } void ext_yahoo_conf_message( int id, const char *ignored, const char *who, const char *room, const char *msg, int utf8 ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); char *m = byahoo_strip( msg ); struct groupchat *c = bee_chat_by_title( ic->bee, ic, room ); if( c ) imcb_chat_msg( c, (char*) who, (char*) m, 0, 0 ); g_free( m ); } void ext_yahoo_chat_cat_xml( int id, const char *xml ) { } void ext_yahoo_chat_join( int id, const char *who, const char *room, const char *topic, YList *members, void *fd ) { } void ext_yahoo_chat_userjoin( int id, const char *me, const char *room, struct yahoo_chat_member *who ) { free(who->id); free(who->alias); free(who->location); free(who); } void ext_yahoo_chat_userleave( int id, const char *me, const char *room, const char *who ) { } void ext_yahoo_chat_message( int id, const char *me, const char *who, const char *room, const char *msg, int msgtype, int utf8 ) { } void ext_yahoo_chat_yahoologout( int id, const char *me ) { } void ext_yahoo_chat_yahooerror( int id, const char *me ) { } void ext_yahoo_contact_added( int id, const char *myid, const char *who, const char *msg ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); imcb_ask_auth( ic, who, msg ); } void ext_yahoo_rejected( int id, const char *who, const char *msg ) { } void ext_yahoo_game_notify( int id, const char *me, const char *who, int stat, const char *msg ) { } void ext_yahoo_mail_notify( int id, const char *from, const char *subj, int cnt ) { struct im_connection *ic = byahoo_get_ic_by_id( id ); if( !set_getbool( &ic->acc->set, "mail_notifications" ) ) ; /* The user doesn't care. */ else if( from && subj ) imcb_log( ic, "Received e-mail message from %s with subject `%s'", from, subj ); else if( cnt > 0 ) imcb_log( ic, "Received %d new e-mails", cnt ); } void ext_yahoo_webcam_invite_reply( int id, const char *me, const char *from, int accept ) { } void ext_yahoo_webcam_closed( int id, const char *who, int reason ) { } void ext_yahoo_got_search_result( int id, int found, int start, int total, YList *contacts ) { } void ext_yahoo_webcam_viewer( int id, const char *who, int connect ) { } void ext_yahoo_webcam_data_request( int id, int send ) { } int ext_yahoo_log( const char *fmt, ... ) { return( 0 ); } void ext_yahoo_got_webcam_image( int id, const char * who, const unsigned char *image, unsigned int image_size, unsigned int real_size, unsigned int timestamp ) { } void ext_yahoo_got_ping( int id, const char *msg ) { } void ext_yahoo_got_buddyicon (int id, const char *me, const char *who, const char *url, int checksum) {} void ext_yahoo_got_buddyicon_checksum (int id, const char *me,const char *who, int checksum) {} void ext_yahoo_got_buddyicon_request(int id, const char *me, const char *who){} void ext_yahoo_buddyicon_uploaded(int id, const char *url){} bitlbee-3.2.1/protocols/yahoo/yahoo2.h0000644000175000017500000002233112245474076017234 0ustar wilmerwilmer/* * libyahoo2: yahoo2.h * * Copyright (C) 2002-2004, Philip S Tellis * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #ifndef YAHOO2_H #define YAHOO2_H #ifdef __cplusplus extern "C" { #endif /* *** BitlBee: *** */ #include "bitlbee.h" #undef free #define free( x ) g_free( x ) #undef malloc #define malloc( x ) g_malloc( x ) #undef calloc #define calloc( x, y ) g_calloc( x, y ) #undef realloc #define realloc( x, y ) g_realloc( x, y ) #undef strdup #define strdup( x ) g_strdup( x ) #undef strndup #define strndup( x,y ) g_strndup( x,y ) #undef snprintf // #define snprintf( x... ) g_snprintf( x ) #undef strcasecmp #define strcasecmp( x,y ) g_strcasecmp( x,y ) #undef strncasecmp #define strncasecmp( x,y,z ) g_strncasecmp( x,y,z ) #include "yahoo2_types.h" /* returns the socket descriptor object for a given pager connection. shouldn't be needed */ void *yahoo_get_fd(int id); /* says how much logging to do */ /* see yahoo2_types.h for the different values */ int yahoo_set_log_level(enum yahoo_log_level level); enum yahoo_log_level yahoo_get_log_level(void); /* these functions should be self explanatory */ /* who always means the buddy you're acting on */ /* id is the successful value returned by yahoo_init */ /* init returns a connection id used to identify the connection hereon */ /* or 0 on failure */ /* you must call init before calling any other function */ /* * The optional parameters to init are key/value pairs that specify * server settings to use. This list must be NULL terminated - even * if the list is empty. If a parameter isn't set, a default value * will be used. Parameter keys are strings, parameter values are * either strings or ints, depending on the key. Values passed in * are copied, so you can use const/auto/static/pointers/whatever * you want. Parameters are: * NAME TYPE DEFAULT * pager_host char * scs.msg.yahoo.com * pager_port int 5050 * filetransfer_host char * filetransfer.msg.yahoo.com * filetransfer_port int 80 * webcam_host char * webcam.yahoo.com * webcam_port int 5100 * webcam_description char * "" * local_host char * "" * conn_type int Y_WCM_DSL * * You should set at least local_host if you intend to use webcams */ int yahoo_init_with_attributes(const char *username, const char *password, ...); /* yahoo_init does the same as yahoo_init_with_attributes, assuming defaults * for all attributes */ int yahoo_init(const char *username, const char *password); /* release all resources held by this session */ /* you need to call yahoo_close for a session only if * yahoo_logoff is never called for it (ie, it was never logged in) */ void yahoo_close(int id); /* login logs in to the server */ /* initial is of type enum yahoo_status. see yahoo2_types.h */ void yahoo_login(int id, int initial); void yahoo_logoff(int id); /* reloads status of all buddies */ void yahoo_refresh(int id); /* activates/deactivates an identity */ void yahoo_set_identity_status(int id, const char *identity, int active); /* regets the entire buddy list from the server */ void yahoo_get_list(int id); /* download buddy contact information from your yahoo addressbook */ void yahoo_get_yab(int id); /* add/modify an address book entry. if yab->dbid is set, it will */ /* modify that entry else it creates a new entry */ void yahoo_set_yab(int id, struct yab *yab); void yahoo_keepalive(int id); void yahoo_chat_keepalive(int id); /* from is the identity you're sending from. if NULL, the default is used */ /* utf8 is whether msg is a utf8 string or not. */ void yahoo_send_im(int id, const char *from, const char *who, const char *msg, int utf8, int picture); // void yahoo_send_buzz(int id, const char *from, const char *who); /* if type is true, send typing notice, else send stopped typing notice */ void yahoo_send_typing(int id, const char *from, const char *who, int typ); /* used to set away/back status. */ /* away says whether the custom message is an away message or a sig */ void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away); void yahoo_add_buddy(int id, const char *who, const char *group, const char *msg); void yahoo_remove_buddy(int id, const char *who, const char *group); void yahoo_confirm_buddy(int id, const char *who, int reject, const char *msg); void yahoo_stealth_buddy(int id, const char *who, int unstealth); /* if unignore is true, unignore, else ignore */ void yahoo_ignore_buddy(int id, const char *who, int unignore); void yahoo_change_buddy_group(int id, const char *who, const char *old_group, const char *new_group); void yahoo_group_rename(int id, const char *old_group, const char *new_group); void yahoo_conference_invite(int id, const char *from, YList *who, const char *room, const char *msg); void yahoo_conference_addinvite(int id, const char *from, const char *who, const char *room, const YList *members, const char *msg); void yahoo_conference_decline(int id, const char *from, YList *who, const char *room, const char *msg); void yahoo_conference_message(int id, const char *from, YList *who, const char *room, const char *msg, int utf8); void yahoo_conference_logon(int id, const char *from, YList *who, const char *room); void yahoo_conference_logoff(int id, const char *from, YList *who, const char *room); /* Get a list of chatrooms */ void yahoo_get_chatrooms(int id, int chatroomid); /* join room with specified roomname and roomid */ void yahoo_chat_logon(int id, const char *from, const char *room, const char *roomid); /* Send message "msg" to room with specified roomname, msgtype is 1-normal message or 2-/me mesage */ void yahoo_chat_message(int id, const char *from, const char *room, const char *msg, const int msgtype, const int utf8); /* Log off chat */ void yahoo_chat_logoff(int id, const char *from); /* requests a webcam feed */ /* who is the person who's webcam you would like to view */ /* if who is null, then you're the broadcaster */ void yahoo_webcam_get_feed(int id, const char *who); void yahoo_webcam_close_feed(int id, const char *who); /* sends an image when uploading */ /* image points to a JPEG-2000 image, length is the length of the image */ /* in bytes. The timestamp is the time in milliseconds since we started the */ /* webcam. */ void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length, unsigned int timestamp); /* this function should be called if we want to allow a user to watch the */ /* webcam. Who is the user we want to accept. */ /* Accept user (accept = 1), decline user (accept = 0) */ void yahoo_webcam_accept_viewer(int id, const char *who, int accept); /* send an invitation to a user to view your webcam */ void yahoo_webcam_invite(int id, const char *who); /* will set up a connection and initiate file transfer. * callback will be called with the fd that you should write * the file data to */ void yahoo_send_file(int id, const char *who, const char *msg, const char *name, unsigned long size, yahoo_get_fd_callback callback, void *data); /* * Respond to a file transfer request. Be sure to provide the callback data * since that is your only chance to recognize future callbacks */ void yahoo_send_file_transfer_response(int client_id, int response, char *id, void *data); /* send a search request */ void yahoo_search(int id, enum yahoo_search_type t, const char *text, enum yahoo_search_gender g, enum yahoo_search_agerange ar, int photo, int yahoo_only); /* continue last search * should be called if only (start+found >= total) * * where the above three are passed to ext_yahoo_got_search_result */ void yahoo_search_again(int id, int start); /* these should be called when input is available on a fd */ /* registered by ext_yahoo_add_handler */ /* if these return negative values, errno may be set */ int yahoo_read_ready(int id, void *fd, void *data); int yahoo_write_ready(int id, void *fd, void *data); /* utility functions. these do not hit the server */ enum yahoo_status yahoo_current_status(int id); const YList *yahoo_get_buddylist(int id); const YList *yahoo_get_ignorelist(int id); const YList *yahoo_get_identities(int id); /* 'which' could be y, t, c or login. This may change in later versions. */ const char *yahoo_get_cookie(int id, const char *which); /* returns the url used to get user profiles - you must append the user id */ /* as of now this is http://profiles.yahoo.com/ */ /* You'll have to do urlencoding yourself, but see yahoo_httplib.h first */ const char *yahoo_get_profile_url(void); void yahoo_buddyicon_request(int id, const char *who); #include "yahoo_httplib.h" #ifdef __cplusplus } #endif #endif bitlbee-3.2.1/protocols/yahoo/yahoo_list.h0000644000175000017500000000317712245474076020214 0ustar wilmerwilmer/* * yahoo_list.h: linked list routines * * Some code copyright (C) 2002-2004, Philip S Tellis * Other code copyright Meredydd Luff * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #ifndef __YLIST_H__ #define __YLIST_H__ /* BitlBee already uses GLib so use it. */ typedef GList YList; #define y_list_append g_list_append #define y_list_concat g_list_concat #define y_list_copy g_list_copy #define y_list_empty g_list_empty #define y_list_find g_list_find #define y_list_find_custom g_list_find_custom #define y_list_foreach g_list_foreach #define y_list_free g_list_free #define y_list_free_1 g_list_free_1 #define y_list_insert_sorted g_list_insert_sorted #define y_list_length g_list_length #define y_list_next g_list_next #define y_list_nth g_list_nth #define y_list_prepend g_list_prepend #define y_list_remove g_list_remove #define y_list_remove_link g_list_remove_link #define y_list_singleton g_list_singleton #endif bitlbee-3.2.1/protocols/yahoo/yahoo2_callbacks.h0000644000175000017500000006124512245474076021242 0ustar wilmerwilmer/* * libyahoo2: yahoo2_callbacks.h * * Copyright (C) 2002-2004, Philip S Tellis * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* * The functions in this file *must* be defined in your client program * If you want to use a callback structure instead of direct functions, * then you must define USE_STRUCT_CALLBACKS in all files that #include * this one. * * Register the callback structure by calling yahoo_register_callbacks - * declared in this file and defined in libyahoo2.c */ #ifndef YAHOO2_CALLBACKS_H #define YAHOO2_CALLBACKS_H #ifdef __cplusplus extern "C" { #endif #include "yahoo2_types.h" /* * yahoo2_callbacks.h * * Callback interface for libyahoo2 */ typedef enum { YAHOO_INPUT_READ = 1 << 0, YAHOO_INPUT_WRITE = 1 << 1, YAHOO_INPUT_EXCEPTION = 1 << 2 } yahoo_input_condition; /* * A callback function called when an asynchronous connect completes. * * Params: * fd - The file descriptor object that has been connected, or NULL on * error * error - The value of errno set by the call to connect or 0 if no error * Set both fd and error to 0 if the connect was cancelled by the * user * callback_data - the callback_data passed to the ext_yahoo_connect_async * function */ typedef void (*yahoo_connect_callback) (void *fd, int error, void *callback_data); /* * The following functions need to be implemented in the client * interface. They will be called by the library when each * event occurs. */ /* * should we use a callback structure or directly call functions * if you want the structure, you *must* define USE_STRUCT_CALLBACKS * both when you compile the library, and when you compile your code * that uses the library */ #ifdef USE_STRUCT_CALLBACKS #define YAHOO_CALLBACK_TYPE(x) (*x) struct yahoo_callbacks { #else #define YAHOO_CALLBACK_TYPE(x) x #endif /* * Name: ext_yahoo_login_response * Called when the login process is complete * Params: * id - the id that identifies the server connection * succ - enum yahoo_login_status * url - url to reactivate account if locked */ void YAHOO_CALLBACK_TYPE(ext_yahoo_login_response) (int id, int succ, const char *url); /* * Name: ext_yahoo_got_buddies * Called when the contact list is got from the server * Params: * id - the id that identifies the server connection * buds - the buddy list */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddies) (int id, YList *buds); /* * Name: ext_yahoo_got_ignore * Called when the ignore list is got from the server * Params: * id - the id that identifies the server connection * igns - the ignore list */ // void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ignore) (int id, YList *igns); /* * Name: ext_yahoo_got_identities * Called when the contact list is got from the server * Params: * id - the id that identifies the server connection * ids - the identity list */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_identities) (int id, YList *ids); /* * Name: ext_yahoo_got_cookies * Called when the cookie list is got from the server * Params: * id - the id that identifies the server connection */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_cookies) (int id); /* * Name: ext_yahoo_got_ping * Called when the ping packet is received from the server * Params: * id - the id that identifies the server connection * errormsg - optional error message */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ping) (int id, const char *errormsg); /* * Name: ext_yahoo_status_changed * Called when remote user's status changes. * Params: * id - the id that identifies the server connection * who - the handle of the remote user * stat - status code (enum yahoo_status) * msg - the message if stat == YAHOO_STATUS_CUSTOM * away - whether the contact is away or not (YAHOO_STATUS_CUSTOM) * idle - this is the number of seconds he is idle [if he is idle] * mobile - this is set for mobile users/buddies * TODO: add support for pager, chat, and game states */ void YAHOO_CALLBACK_TYPE(ext_yahoo_status_changed) (int id, const char *who, int stat, const char *msg, int away, int idle, int mobile); /* * Name: ext_yahoo_got_buzz * Called when remote user sends you a buzz. * Params: * id - the id that identifies the server connection * me - the identity the message was sent to * who - the handle of the remote user * tm - timestamp of message if offline */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buzz) (int id, const char *me, const char *who, long tm); /* * Name: ext_yahoo_got_im * Called when remote user sends you a message. * Params: * id - the id that identifies the server connection * me - the identity the message was sent to * who - the handle of the remote user * msg - the message - NULL if stat == 2 * tm - timestamp of message if offline * stat - message status - 0 * 1 * 2 == error sending message * 5 * utf8 - whether the message is encoded as utf8 or not */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_im) (int id, const char *me, const char *who, const char *msg, long tm, int stat, int utf8); /* * Name: ext_yahoo_got_conf_invite * Called when remote user sends you a conference invitation. * Params: * id - the id that identifies the server connection * me - the identity the invitation was sent to * who - the user inviting you * room - the room to join * msg - the message * members - the initial members of the conference (null terminated list) */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_conf_invite) (int id, const char *me, const char *who, const char *room, const char *msg, YList *members); /* * Name: ext_yahoo_conf_userdecline * Called when someone declines to join the conference. * Params: * id - the id that identifies the server connection * me - the identity in the conference * who - the user who has declined * room - the room * msg - the declining message */ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userdecline) (int id, const char *me, const char *who, const char *room, const char *msg); /* * Name: ext_yahoo_conf_userjoin * Called when someone joins the conference. * Params: * id - the id that identifies the server connection * me - the identity in the conference * who - the user who has joined * room - the room joined */ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userjoin) (int id, const char *me, const char *who, const char *room); /* * Name: ext_yahoo_conf_userleave * Called when someone leaves the conference. * Params: * id - the id that identifies the server connection * me - the identity in the conference * who - the user who has left * room - the room left */ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userleave) (int id, const char *me, const char *who, const char *room); /* * Name: ext_yahoo_chat_cat_xml * Called when ? * Params: * id - the id that identifies the server connection * xml - ? */ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_cat_xml) (int id, const char *xml); /* * Name: ext_yahoo_chat_join * Called when joining the chatroom. * Params: * id - the id that identifies the server connection * me - the identity in the chatroom * room - the room joined, used in all other chat calls, freed by * library after call * topic - the topic of the room, freed by library after call * members - the initial members of the chatroom (null terminated YList * of yahoo_chat_member's) Must be freed by the client * fd - the object where the connection is coming from (for tracking) */ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_join) (int id, const char *me, const char *room, const char *topic, YList *members, void *fd); /* * Name: ext_yahoo_chat_userjoin * Called when someone joins the chatroom. * Params: * id - the id that identifies the server connection * me - the identity in the chatroom * room - the room joined * who - the user who has joined, Must be freed by the client */ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userjoin) (int id, const char *me, const char *room, struct yahoo_chat_member *who); /* * Name: ext_yahoo_chat_userleave * Called when someone leaves the chatroom. * Params: * id - the id that identifies the server connection * me - the identity in the chatroom * room - the room left * who - the user who has left (Just the User ID) */ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userleave) (int id, const char *me, const char *room, const char *who); /* * Name: ext_yahoo_chat_message * Called when someone messages in the chatroom. * Params: * id - the id that identifies the server connection * me - the identity in the chatroom * room - the room * who - the user who messaged (Just the user id) * msg - the message * msgtype - 1 = Normal message * 2 = /me type message * utf8 - whether the message is utf8 encoded or not */ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_message) (int id, const char *me, const char *who, const char *room, const char *msg, int msgtype, int utf8); /* * * Name: ext_yahoo_chat_yahoologout * called when yahoo disconnects your chat session * Note this is called whenver a disconnect happens, client or server * requested. Care should be taken to make sure you know the origin * of the disconnect request before doing anything here (auto-join's etc) * Params: * id - the id that identifies this connection * me - the identity in the chatroom * Returns: * nothing. */ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahoologout) (int id, const char *me); /* * * Name: ext_yahoo_chat_yahooerror * called when yahoo sends back an error to you * Note this is called whenver chat message is sent into a room * in error (fd not connected, room doesn't exists etc) * Care should be taken to make sure you know the origin * of the error before doing anything about it. * Params: * id - the id that identifies this connection * me - the identity in the chatroom * Returns: * nothing. */ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahooerror) (int id, const char *me); /* * Name: ext_yahoo_conf_message * Called when someone messages in the conference. * Params: * id - the id that identifies the server connection * me - the identity the conf message was sent to * who - the user who messaged * room - the room * msg - the message * utf8 - whether the message is utf8 encoded or not */ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_message) (int id, const char *me, const char *who, const char *room, const char *msg, int utf8); /* * Name: ext_yahoo_got_file * Called when someone sends you a file * Params: * id - the id that identifies the server connection * me - the identity the file was sent to * who - the user who sent the file * msg - the message * fname- the file name if direct transfer * fsize- the file size if direct transfer * trid - transfer id. Unique for this transfer * * NOTE: Subsequent callbacks for file transfer do not send all of this * information again since it is wasteful. Implementations are expected to * save this information and supply it as callback data when the file or * confirmation is sent */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_file) (int id, const char *me, const char *who, const char *msg, const char *fname, unsigned long fesize, char *trid); /* * Name: ext_yahoo_got_ft_data * Called multiple times when parts of the file are received * Params: * id - the id that identifies the server connection * in - The data * len - Length of the data * data - callback data */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ft_data) (int id, const unsigned char *in, int len, void *data); /* * Name: ext_yahoo_file_transfer_done * File transfer is done * Params: * id - the id that identifies the server connection * result - To notify if it finished successfully or with a failure * data - callback data */ void YAHOO_CALLBACK_TYPE(ext_yahoo_file_transfer_done) (int id, int result, void *data); /* * Name: ext_yahoo_contact_added * Called when a contact is added to your list * Params: * id - the id that identifies the server connection * myid - the identity he was added to * who - who was added * msg - any message sent */ void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_added) (int id, const char *myid, const char *who, const char *msg); /* * Name: ext_yahoo_rejected * Called when a contact rejects your add * Params: * id - the id that identifies the server connection * who - who rejected you * msg - any message sent */ void YAHOO_CALLBACK_TYPE(ext_yahoo_rejected) (int id, const char *who, const char *msg); /* * Name: ext_yahoo_typing_notify * Called when remote user starts or stops typing. * Params: * id - the id that identifies the server connection * me - the handle of the identity the notification is sent to * who - the handle of the remote user * stat - 1 if typing, 0 if stopped typing */ void YAHOO_CALLBACK_TYPE(ext_yahoo_typing_notify) (int id, const char *me, const char *who, int stat); /* * Name: ext_yahoo_game_notify * Called when remote user starts or stops a game. * Params: * id - the id that identifies the server connection * me - the handle of the identity the notification is sent to * who - the handle of the remote user * stat - 1 if game, 0 if stopped gaming * msg - game description and/or other text */ void YAHOO_CALLBACK_TYPE(ext_yahoo_game_notify) (int id, const char *me, const char *who, int stat, const char *msg); /* * Name: ext_yahoo_mail_notify * Called when you receive mail, or with number of messages * Params: * id - the id that identifies the server connection * from - who the mail is from - NULL if only mail count * subj - the subject of the mail - NULL if only mail count * cnt - mail count - 0 if new mail notification */ void YAHOO_CALLBACK_TYPE(ext_yahoo_mail_notify) (int id, const char *from, const char *subj, int cnt); /* * Name: ext_yahoo_system_message * System message * Params: * id - the id that identifies the server connection * me - the handle of the identity the notification is sent to * who - the source of the system message (there are different types) * msg - the message */ void YAHOO_CALLBACK_TYPE(ext_yahoo_system_message) (int id, const char *me, const char *who, const char *msg); /* * Name: ext_yahoo_got_buddyicon * Buddy icon received * Params: * id - the id that identifies the server connection * me - the handle of the identity the notification is sent to * who - the person the buddy icon is for * url - the url to use to load the icon * checksum - the checksum of the icon content */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon) (int id, const char *me, const char *who, const char *url, int checksum); /* * Name: ext_yahoo_got_buddyicon_checksum * Buddy icon checksum received * Params: * id - the id that identifies the server connection * me - the handle of the identity the notification is sent to * who - the yahoo id of the buddy icon checksum is for * checksum - the checksum of the icon content */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_checksum) (int id, const char *me, const char *who, int checksum); /* * Name: ext_yahoo_got_buddyicon_request * Buddy icon request received * Params: * id - the id that identifies the server connection * me - the handle of the identity the notification is sent to * who - the yahoo id of the buddy that requested the buddy icon */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_request) (int id, const char *me, const char *who); /* * Name: ext_yahoo_got_buddyicon_request * Buddy icon request received * Params: * id - the id that identifies the server connection * url - remote url, the uploaded buddy icon can be fetched from */ void YAHOO_CALLBACK_TYPE(ext_yahoo_buddyicon_uploaded) (int id, const char *url); /* * Name: ext_yahoo_got_webcam_image * Called when you get a webcam update * An update can either be receiving an image, a part of an image or * just an update with a timestamp * Params: * id - the id that identifies the server connection * who - the user who's webcam we're viewing * image - image data * image_size - length of the image in bytes * real_size - actual length of image data * timestamp - milliseconds since the webcam started * * If the real_size is smaller then the image_size then only part of * the image has been read. This function will keep being called till * the total amount of bytes in image_size has been read. The image * received is in JPEG-2000 Code Stream Syntax (ISO/IEC 15444-1). * The size of the image will be either 160x120 or 320x240. * Each webcam image contains a timestamp. This timestamp should be * used to keep the image in sync since some images can take longer * to transport then others. When image_size is 0 we can still receive * a timestamp to stay in sync */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_webcam_image) (int id, const char *who, const unsigned char *image, unsigned int image_size, unsigned int real_size, unsigned int timestamp); /* * Name: ext_yahoo_webcam_invite * Called when you get a webcam invitation * Params: * id - the id that identifies the server connection * me - identity the invitation is to * from - who the invitation is from */ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite) (int id, const char *me, const char *from); /* * Name: ext_yahoo_webcam_invite_reply * Called when you get a response to a webcam invitation * Params: * id - the id that identifies the server connection * me - identity the invitation response is to * from - who the invitation response is from * accept - 0 (decline), 1 (accept) */ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite_reply) (int id, const char *me, const char *from, int accept); /* * Name: ext_yahoo_webcam_closed * Called when the webcam connection closed * Params: * id - the id that identifies the server connection * who - the user who we where connected to * reason - reason why the connection closed * 1 = user stopped broadcasting * 2 = user cancelled viewing permission * 3 = user declines permission * 4 = user does not have webcam online */ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_closed) (int id, const char *who, int reason); /* * Name: ext_yahoo_got_search_result * Called when the search result received from server * Params: * id - the id that identifies the server connection * found - total number of results returned in the current result set * start - offset from where the current result set starts * total - total number of results available (start + found <= total) * contacts - the list of results as a YList of yahoo_found_contact * these will be freed after this function returns, so * if you need to use the information, make a copy */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_search_result) (int id, int found, int start, int total, YList *contacts); /* * Name: ext_yahoo_error * Called on error. * Params: * id - the id that identifies the server connection * err - the error message * fatal- whether this error is fatal to the connection or not * num - Which error is this */ void YAHOO_CALLBACK_TYPE(ext_yahoo_error) (int id, const char *err, int fatal, int num); /* * Name: ext_yahoo_webcam_viewer * Called when a viewer disconnects/connects/requests to connect * Params: * id - the id that identifies the server connection * who - the viewer * connect - 0=disconnect 1=connect 2=request */ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_viewer) (int id, const char *who, int connect); /* * Name: ext_yahoo_webcam_data_request * Called when you get a request for webcam images * Params: * id - the id that identifies the server connection * send - whether to send images or not */ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_data_request) (int id, int send); /* * Name: ext_yahoo_log * Called to log a message. * Params: * fmt - the printf formatted message * Returns: * 0 */ int YAHOO_CALLBACK_TYPE(ext_yahoo_log) (const char *fmt, ...); /* * Name: ext_yahoo_add_handler * Add a listener for the fd. Must call yahoo_read_ready * when a YAHOO_INPUT_READ fd is ready and yahoo_write_ready * when a YAHOO_INPUT_WRITE fd is ready. * Params: * id - the id that identifies the server connection * fd - the fd object on which to listen * cond - the condition on which to call the callback * data - callback data to pass to yahoo_*_ready * * Returns: a tag to be used when removing the handler */ int YAHOO_CALLBACK_TYPE(ext_yahoo_add_handler) (int id, void *fd, yahoo_input_condition cond, void *data); /* * Name: ext_yahoo_remove_handler * Remove the listener for the fd. * Params: * id - the id that identifies the connection * tag - the handler tag to remove */ void YAHOO_CALLBACK_TYPE(ext_yahoo_remove_handler) (int id, int tag); /* * Name: ext_yahoo_connect * Connect to a host:port * Params: * host - the host to connect to * port - the port to connect on * Returns: * a unix file descriptor to the socket */ // int YAHOO_CALLBACK_TYPE(ext_yahoo_connect) (const char *host, int port); /* * Name: ext_yahoo_connect_async * Connect to a host:port asynchronously. This function should return * immediately returing a tag used to identify the connection handler, * or a pre-connect error (eg: host name lookup failure). * Once the connect completes (successfully or unsuccessfully), callback * should be called (see the signature for yahoo_connect_callback). * The callback may safely be called before this function returns, but * it should not be called twice. * Params: * id - the id that identifies this connection * host - the host to connect to * port - the port to connect on * callback - function to call when connect completes * callback_data - data to pass to the callback function * use_ssl - Whether we need an SSL connection * Returns: * a tag signifying the connection attempt */ int YAHOO_CALLBACK_TYPE(ext_yahoo_connect_async) (int id, const char *host, int port, yahoo_connect_callback callback, void *callback_data, int use_ssl); /* * Name: ext_yahoo_get_ip_addr * get IP Address for a domain name * Params: * domain - Domain name * Returns: * Newly allocated string containing the IP Address in IPv4 notation */ char *YAHOO_CALLBACK_TYPE(ext_yahoo_get_ip_addr) (const char *domain); /* * Name: ext_yahoo_write * Write data from the buffer into the socket for the specified connection * Params: * fd - the file descriptor object that identifies this connection * buf - Buffer to write the data from * len - Length of the data * Returns: * Number of bytes written or -1 for error */ int YAHOO_CALLBACK_TYPE(ext_yahoo_write) (void *fd, char *buf, int len); /* * Name: ext_yahoo_read * Read data into a buffer from socket for the specified connection * Params: * fd - the file descriptor object that identifies this connection * buf - Buffer to read the data into * len - Max length to read * Returns: * Number of bytes read or -1 for error */ int YAHOO_CALLBACK_TYPE(ext_yahoo_read) (void *fd, char *buf, int len); /* * Name: ext_yahoo_close * Close the file descriptor object and free its resources. Libyahoo2 will not * use this object again. * Params: * fd - the file descriptor object that identifies this connection * Returns: * Nothing */ void YAHOO_CALLBACK_TYPE(ext_yahoo_close) (void *fd); /* * Name: ext_yahoo_got_buddy_change_group * Acknowledgement of buddy changing group * Params: * id: client id * me: The user * who: Buddy name * old_group: Old group name * new_group: New group name * Returns: * Nothing */ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddy_change_group) (int id, const char *me, const char *who, const char *old_group, const char *new_group); #ifdef USE_STRUCT_CALLBACKS }; /* * if using a callback structure, call yahoo_register_callbacks * before doing anything else */ void yahoo_register_callbacks(struct yahoo_callbacks *tyc); #undef YAHOO_CALLBACK_TYPE #endif #ifdef __cplusplus } #endif #endif bitlbee-3.2.1/protocols/yahoo/yahoo_util.h0000644000175000017500000000467312245474076020220 0ustar wilmerwilmer/* * libyahoo2: yahoo_util.h * * Copyright (C) 2002-2004, Philip S Tellis * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #ifndef __YAHOO_UTIL_H__ #define __YAHOO_UTIL_H__ #if HAVE_CONFIG_H # include #endif #if HAVE_GLIB # include # define FREE(x) if(x) {g_free(x); x=NULL;} # define y_new g_new # define y_new0 g_new0 # define y_renew g_renew # define y_memdup g_memdup # define y_strsplit g_strsplit # define y_strfreev g_strfreev # ifndef strdup # define strdup g_strdup # endif # ifndef strncasecmp # define strncasecmp g_strncasecmp # define strcasecmp g_strcasecmp # endif # define snprintf g_snprintf # define vsnprintf g_vsnprintf #else # include # include # define FREE(x) if(x) {free(x); x=NULL;} # define y_new(type, n) (type *)malloc(sizeof(type) * (n)) # define y_new0(type, n) (type *)calloc((n), sizeof(type)) # define y_renew(type, mem, n) (type *)realloc(mem, n) void *y_memdup(const void *addr, int n); char **y_strsplit(char *str, char *sep, int nelem); void y_strfreev(char **vector); #ifndef _WIN32 int strncasecmp(const char *s1, const char *s2, size_t n); int strcasecmp(const char *s1, const char *s2); char *strdup(const char *s); int snprintf(char *str, size_t size, const char *format, ...); int vsnprintf(char *str, size_t size, const char *format, va_list ap); #endif #endif #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #ifndef MIN #define MIN(x,y) ((x)<(y)?(x):(y)) #endif #ifndef MAX #define MAX(x,y) ((x)>(y)?(x):(y)) #endif /* * The following three functions return newly allocated memory. * You must free it yourself */ char *y_string_append(char *str, char *append); char *y_str_to_utf8(const char *in); char *y_utf8_to_str(const char *in); #endif bitlbee-3.2.1/protocols/yahoo/yahoo2_types.h0000644000175000017500000002454512245474076020471 0ustar wilmerwilmer/* * libyahoo2: yahoo2_types.h * * Copyright (C) 2002-2004, Philip S Tellis * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #ifndef YAHOO2_TYPES_H #define YAHOO2_TYPES_H #include "yahoo_list.h" #ifdef __cplusplus extern "C" { #endif enum yahoo_service { /* these are easier to see in hex */ YAHOO_SERVICE_LOGON = 1, YAHOO_SERVICE_LOGOFF, YAHOO_SERVICE_ISAWAY, YAHOO_SERVICE_ISBACK, YAHOO_SERVICE_IDLE, /* 5 (placemarker) */ YAHOO_SERVICE_MESSAGE, YAHOO_SERVICE_IDACT, YAHOO_SERVICE_IDDEACT, YAHOO_SERVICE_MAILSTAT, YAHOO_SERVICE_USERSTAT, /* 0xa */ YAHOO_SERVICE_NEWMAIL, YAHOO_SERVICE_CHATINVITE, YAHOO_SERVICE_CALENDAR, YAHOO_SERVICE_NEWPERSONALMAIL, YAHOO_SERVICE_NEWCONTACT, YAHOO_SERVICE_ADDIDENT, /* 0x10 */ YAHOO_SERVICE_ADDIGNORE, YAHOO_SERVICE_PING, YAHOO_SERVICE_GOTGROUPRENAME, /* < 1, 36(old), 37(new) */ YAHOO_SERVICE_SYSMESSAGE = 0x14, YAHOO_SERVICE_SKINNAME = 0x15, YAHOO_SERVICE_PASSTHROUGH2 = 0x16, YAHOO_SERVICE_CONFINVITE = 0x18, YAHOO_SERVICE_CONFLOGON, YAHOO_SERVICE_CONFDECLINE, YAHOO_SERVICE_CONFLOGOFF, YAHOO_SERVICE_CONFADDINVITE, YAHOO_SERVICE_CONFMSG, YAHOO_SERVICE_CHATLOGON, YAHOO_SERVICE_CHATLOGOFF, YAHOO_SERVICE_CHATMSG = 0x20, YAHOO_SERVICE_GAMELOGON = 0x28, YAHOO_SERVICE_GAMELOGOFF, YAHOO_SERVICE_GAMEMSG = 0x2a, YAHOO_SERVICE_FILETRANSFER = 0x46, YAHOO_SERVICE_VOICECHAT = 0x4A, YAHOO_SERVICE_NOTIFY, YAHOO_SERVICE_VERIFY, YAHOO_SERVICE_P2PFILEXFER, YAHOO_SERVICE_PEERTOPEER = 0x4F, /* Checks if P2P possible */ YAHOO_SERVICE_WEBCAM, YAHOO_SERVICE_AUTHRESP = 0x54, YAHOO_SERVICE_LIST, YAHOO_SERVICE_AUTH = 0x57, YAHOO_SERVICE_AUTHBUDDY = 0x6d, YAHOO_SERVICE_ADDBUDDY = 0x83, YAHOO_SERVICE_REMBUDDY, YAHOO_SERVICE_IGNORECONTACT, /* > 1, 7, 13 < 1, 66, 13, 0 */ YAHOO_SERVICE_REJECTCONTACT, YAHOO_SERVICE_GROUPRENAME = 0x89, /* > 1, 65(new), 66(0), 67(old) */ YAHOO_SERVICE_Y7_PING = 0x8A, YAHOO_SERVICE_CHATONLINE = 0x96, /* > 109(id), 1, 6(abcde) < 0,1 */ YAHOO_SERVICE_CHATGOTO, YAHOO_SERVICE_CHATJOIN, /* > 1 104-room 129-1600326591 62-2 */ YAHOO_SERVICE_CHATLEAVE, YAHOO_SERVICE_CHATEXIT = 0x9b, YAHOO_SERVICE_CHATADDINVITE = 0x9d, YAHOO_SERVICE_CHATLOGOUT = 0xa0, YAHOO_SERVICE_CHATPING, YAHOO_SERVICE_COMMENT = 0xa8, YAHOO_SERVICE_GAME_INVITE = 0xb7, YAHOO_SERVICE_STEALTH_PERM = 0xb9, YAHOO_SERVICE_STEALTH_SESSION = 0xba, YAHOO_SERVICE_AVATAR = 0xbc, YAHOO_SERVICE_PICTURE_CHECKSUM = 0xbd, YAHOO_SERVICE_PICTURE = 0xbe, YAHOO_SERVICE_PICTURE_UPDATE = 0xc1, YAHOO_SERVICE_PICTURE_UPLOAD = 0xc2, YAHOO_SERVICE_YAB_UPDATE = 0xc4, YAHOO_SERVICE_Y6_VISIBLE_TOGGLE = 0xc5, /* YMSG13, key 13: 2 = invisible, 1 = visible */ YAHOO_SERVICE_Y6_STATUS_UPDATE = 0xc6, /* YMSG13 */ YAHOO_SERVICE_PICTURE_STATUS = 0xc7, /* YMSG13, key 213: 0 = none, 1 = avatar, 2 = picture */ YAHOO_SERVICE_VERIFY_ID_EXISTS = 0xc8, YAHOO_SERVICE_AUDIBLE = 0xd0, YAHOO_SERVICE_Y7_PHOTO_SHARING = 0xd2, YAHOO_SERVICE_Y7_CONTACT_DETAILS = 0xd3, /* YMSG13 */ YAHOO_SERVICE_Y7_CHAT_SESSION = 0xd4, YAHOO_SERVICE_Y7_AUTHORIZATION = 0xd6, /* YMSG13 */ YAHOO_SERVICE_Y7_FILETRANSFER = 0xdc, /* YMSG13 */ YAHOO_SERVICE_Y7_FILETRANSFERINFO, /* YMSG13 */ YAHOO_SERVICE_Y7_FILETRANSFERACCEPT, /* YMSG13 */ YAHOO_SERVICE_Y7_MINGLE = 0xe1, /* YMSG13 */ YAHOO_SERVICE_Y7_CHANGE_GROUP = 0xe7, /* YMSG13 */ YAHOO_SERVICE_MYSTERY = 0xef, /* Don't know what this is for */ YAHOO_SERVICE_Y8_STATUS = 0xf0, /* YMSG15 */ YAHOO_SERVICE_Y8_LIST = 0Xf1, /* YMSG15 */ YAHOO_SERVICE_MESSAGE_CONFIRM = 0xfb, YAHOO_SERVICE_WEBLOGIN = 0x0226, YAHOO_SERVICE_SMS_MSG = 0x02ea }; enum yahoo_status { YAHOO_STATUS_AVAILABLE = 0, YAHOO_STATUS_BRB, YAHOO_STATUS_BUSY, YAHOO_STATUS_NOTATHOME, YAHOO_STATUS_NOTATDESK, YAHOO_STATUS_NOTINOFFICE, YAHOO_STATUS_ONPHONE, YAHOO_STATUS_ONVACATION, YAHOO_STATUS_OUTTOLUNCH, YAHOO_STATUS_STEPPEDOUT, YAHOO_STATUS_INVISIBLE = 12, YAHOO_STATUS_CUSTOM = 99, YAHOO_STATUS_IDLE = 999, YAHOO_STATUS_OFFLINE = 0x5a55aa56 /* don't ask */ }; enum ypacket_status { YPACKET_STATUS_DISCONNECTED = -1, YPACKET_STATUS_DEFAULT = 0, YPACKET_STATUS_SERVERACK = 1, YPACKET_STATUS_GAME = 0x2, YPACKET_STATUS_AWAY = 0x4, YPACKET_STATUS_CONTINUED = 0x5, YPACKET_STATUS_INVISIBLE = 12, YPACKET_STATUS_NOTIFY = 0x16, /* TYPING */ YPACKET_STATUS_WEBLOGIN = 0x5a55aa55, YPACKET_STATUS_OFFLINE = 0x5a55aa56 }; #define YAHOO_STATUS_GAME 0x2 /* Games don't fit into the regular status model */ enum yahoo_login_status { YAHOO_LOGIN_OK = 0, YAHOO_LOGIN_LOGOFF = 1, YAHOO_LOGIN_UNAME = 3, YAHOO_LOGIN_PASSWD = 13, YAHOO_LOGIN_LOCK = 14, YAHOO_LOGIN_DUPL = 99, YAHOO_LOGIN_SOCK = -1, YAHOO_LOGIN_UNKNOWN = 999 }; enum yahoo_error { E_UNKNOWN = -1, E_CONNECTION = -2, E_SYSTEM = -3, E_CUSTOM = 0, /* responses from ignore buddy */ E_IGNOREDUP = 2, E_IGNORENONE = 3, E_IGNORECONF = 12, /* conference */ E_CONFNOTAVAIL = 20 }; enum yahoo_log_level { YAHOO_LOG_NONE = 0, YAHOO_LOG_FATAL, YAHOO_LOG_ERR, YAHOO_LOG_WARNING, YAHOO_LOG_NOTICE, YAHOO_LOG_INFO, YAHOO_LOG_DEBUG }; enum yahoo_file_transfer { YAHOO_FILE_TRANSFER_INIT = 1, YAHOO_FILE_TRANSFER_ACCEPT = 3, YAHOO_FILE_TRANSFER_REJECT = 4, YAHOO_FILE_TRANSFER_DONE = 5, YAHOO_FILE_TRANSFER_RELAY, YAHOO_FILE_TRANSFER_FAILED, YAHOO_FILE_TRANSFER_UNKNOWN }; #define YAHOO_PROTO_VER 0x0010 /* Yahoo style/color directives */ #define YAHOO_COLOR_BLACK "\033[30m" #define YAHOO_COLOR_BLUE "\033[31m" #define YAHOO_COLOR_LIGHTBLUE "\033[32m" #define YAHOO_COLOR_GRAY "\033[33m" #define YAHOO_COLOR_GREEN "\033[34m" #define YAHOO_COLOR_PINK "\033[35m" #define YAHOO_COLOR_PURPLE "\033[36m" #define YAHOO_COLOR_ORANGE "\033[37m" #define YAHOO_COLOR_RED "\033[38m" #define YAHOO_COLOR_OLIVE "\033[39m" #define YAHOO_COLOR_ANY "\033[#" #define YAHOO_STYLE_ITALICON "\033[2m" #define YAHOO_STYLE_ITALICOFF "\033[x2m" #define YAHOO_STYLE_BOLDON "\033[1m" #define YAHOO_STYLE_BOLDOFF "\033[x1m" #define YAHOO_STYLE_UNDERLINEON "\033[4m" #define YAHOO_STYLE_UNDERLINEOFF "\033[x4m" #define YAHOO_STYLE_URLON "\033[lm" #define YAHOO_STYLE_URLOFF "\033[xlm" enum yahoo_connection_type { YAHOO_CONNECTION_PAGER = 0, YAHOO_CONNECTION_FT, YAHOO_CONNECTION_YAB, YAHOO_CONNECTION_WEBCAM_MASTER, YAHOO_CONNECTION_WEBCAM, YAHOO_CONNECTION_CHATCAT, YAHOO_CONNECTION_SEARCH, YAHOO_CONNECTION_AUTH }; enum yahoo_webcam_direction_type { YAHOO_WEBCAM_DOWNLOAD = 0, YAHOO_WEBCAM_UPLOAD }; enum yahoo_stealth_visibility_type { YAHOO_STEALTH_DEFAULT = 0, YAHOO_STEALTH_ONLINE, YAHOO_STEALTH_PERM_OFFLINE }; /* chat member attribs */ #define YAHOO_CHAT_MALE 0x8000 #define YAHOO_CHAT_FEMALE 0x10000 #define YAHOO_CHAT_FEMALE 0x10000 #define YAHOO_CHAT_DUNNO 0x400 #define YAHOO_CHAT_WEBCAM 0x10 enum yahoo_webcam_conn_type { Y_WCM_DIALUP, Y_WCM_DSL, Y_WCM_T1 }; struct yahoo_webcam { int direction; /* Uploading or downloading */ int conn_type; /* 0=Dialup, 1=DSL/Cable, 2=T1/Lan */ char *user; /* user we are viewing */ char *server; /* webcam server to connect to */ int port; /* webcam port to connect on */ char *key; /* key to connect to the server with */ char *description; /* webcam description */ char *my_ip; /* own ip number */ }; struct yahoo_webcam_data { unsigned int data_size; unsigned int to_read; unsigned int timestamp; unsigned char packet_type; }; struct yahoo_data { char *user; char *password; char *cookie_y; char *cookie_t; char *cookie_c; char *cookie_b; char *login_cookie; char *crumb; char *seed; YList *buddies; YList *ignore; YList *identities; char *login_id; int current_status; int initial_status; int logged_in; int session_id; int client_id; char *rawbuddylist; char *ignorelist; void *server_settings; struct yahoo_process_status_entry *half_user; }; struct yab { int yid; char *id; char *fname; char *lname; char *nname; char *email; char *hphone; char *wphone; char *mphone; int dbid; }; struct yahoo_buddy { char *group; char *id; char *real_name; struct yab *yab_entry; }; enum yahoo_search_type { YAHOO_SEARCH_KEYWORD = 0, YAHOO_SEARCH_YID, YAHOO_SEARCH_NAME }; enum yahoo_search_gender { YAHOO_GENDER_NONE = 0, YAHOO_GENDER_MALE, YAHOO_GENDER_FEMALE }; enum yahoo_search_agerange { YAHOO_AGERANGE_NONE = 0 }; struct yahoo_found_contact { char *id; char *gender; char *location; int age; int online; }; /* * Function pointer to be passed to http get/post and send file */ typedef void (*yahoo_get_fd_callback) (int id, void *fd, int error, void *data); /* * Function pointer to be passed to yahoo_get_url_handle */ typedef void (*yahoo_get_url_handle_callback) (int id, void *fd, int error, const char *filename, unsigned long size, void *data); struct yahoo_chat_member { char *id; int age; int attribs; char *alias; char *location; }; struct yahoo_process_status_entry { char *name; /* 7 name */ int state; /* 10 state */ int flags; /* 13 flags, bit 0 = pager, bit 1 = chat, bit 2 = game */ int mobile; /* 60 mobile */ char *msg; /* 19 custom status message */ int away; /* 47 away (or invisible) */ int buddy_session; /* 11 state */ int f17; /* 17 in chat? then what about flags? */ int idle; /* 137 seconds idle */ int f138; /* 138 state */ char *f184; /* 184 state */ int f192; /* 192 state */ int f10001; /* 10001 state */ int f10002; /* 10002 state */ int f198; /* 198 state */ char *f197; /* 197 state */ char *f205; /* 205 state */ int f213; /* 213 state */ }; #ifdef __cplusplus } #endif #endif bitlbee-3.2.1/protocols/yahoo/yahoo_httplib.h0000644000175000017500000000305612245474076020703 0ustar wilmerwilmer/* * libyahoo2: yahoo_httplib.h * * Copyright (C) 2002-2004, Philip S Tellis * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #ifndef YAHOO_HTTPLIB_H #define YAHOO_HTTPLIB_H #ifdef __cplusplus extern "C" { #endif #include "yahoo2_types.h" char *yahoo_urlencode(const char *instr); char *yahoo_urldecode(const char *instr); char *yahoo_xmldecode(const char *instr); int yahoo_tcp_readline(char *ptr, int maxlen, void *fd); void yahoo_http_post(int id, const char *url, const char *cookies, long size, yahoo_get_fd_callback callback, void *data); void yahoo_http_get(int id, const char *url, const char *cookies, int http11, int keepalive, yahoo_get_fd_callback callback, void *data); void yahoo_http_head(int id, const char *url, const char *cookies, int size, char *payload, yahoo_get_fd_callback callback, void *data); #ifdef __cplusplus } #endif #endif bitlbee-3.2.1/protocols/yahoo/yahoo_httplib.c0000644000175000017500000002122312245474076020672 0ustar wilmerwilmer/* * libyahoo2: yahoo_httplib.c * * Copyright (C) 2002-2004, Philip S Tellis * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #if STDC_HEADERS # include #else # if !HAVE_STRCHR # define strchr index # define strrchr rindex # endif char *strchr(), *strrchr(); # if !HAVE_MEMCPY # define memcpy(d, s, n) bcopy ((s), (d), (n)) # define memmove(d, s, n) bcopy ((s), (d), (n)) # endif #endif #include #ifndef _WIN32 #include #endif #include #include "yahoo2.h" #include "yahoo2_callbacks.h" #include "yahoo_httplib.h" #include "yahoo_util.h" #include "yahoo_debug.h" #ifdef __MINGW32__ # include # define snprintf _snprintf #endif #ifdef USE_STRUCT_CALLBACKS extern struct yahoo_callbacks *yc; #define YAHOO_CALLBACK(x) yc->x #else #define YAHOO_CALLBACK(x) x #endif extern enum yahoo_log_level log_level; #if 0 int yahoo_tcp_readline(char *ptr, int maxlen, void *fd) { int n, rc; char c; for (n = 1; n < maxlen; n++) { do { rc = YAHOO_CALLBACK(ext_yahoo_read) (fd, &c, 1); } while (rc == -1 && (errno == EINTR || errno == EAGAIN)); /* this is bad - it should be done asynchronously */ if (rc == 1) { if (c == '\r') /* get rid of \r */ continue; *ptr = c; if (c == '\n') break; ptr++; } else if (rc == 0) { if (n == 1) return (0); /* EOF, no data */ else break; /* EOF, w/ data */ } else { return -1; } } *ptr = 0; return (n); } #endif static int url_to_host_port_path(const char *url, char *host, int *port, char *path, int *ssl) { char *urlcopy = NULL; char *slash = NULL; char *colon = NULL; /* * http://hostname * http://hostname/ * http://hostname/path * http://hostname/path:foo * http://hostname:port * http://hostname:port/ * http://hostname:port/path * http://hostname:port/path:foo * and https:// variants of the above */ if (strstr(url, "http://") == url) { urlcopy = strdup(url + 7); } else if (strstr(url, "https://") == url) { urlcopy = strdup(url + 8); *ssl = 1; } else { WARNING(("Weird url - unknown protocol: %s", url)); return 0; } slash = strchr(urlcopy, '/'); colon = strchr(urlcopy, ':'); if (!colon || (slash && slash < colon)) { if (*ssl) *port = 443; else *port = 80; } else { *colon = 0; *port = atoi(colon + 1); } if (!slash) { strcpy(path, "/"); } else { strcpy(path, slash); *slash = 0; } strcpy(host, urlcopy); FREE(urlcopy); return 1; } static int isurlchar(unsigned char c) { return (isalnum(c)); } char *yahoo_urlencode(const char *instr) { int ipos = 0, bpos = 0; char *str = NULL; int len = strlen(instr); if (!(str = y_new(char, 3 *len + 1))) return ""; while (instr[ipos]) { while (isurlchar(instr[ipos])) str[bpos++] = instr[ipos++]; if (!instr[ipos]) break; snprintf(&str[bpos], 4, "%%%02x", instr[ipos] & 0xff); bpos += 3; ipos++; } str[bpos] = '\0'; /* free extra alloc'ed mem. */ len = strlen(str); str = y_renew(char, str, len + 1); return (str); } #if 0 char *yahoo_urldecode(const char *instr) { int ipos = 0, bpos = 0; char *str = NULL; char entity[3] = { 0, 0, 0 }; unsigned dec; int len = strlen(instr); if (!(str = y_new(char, len + 1))) return ""; while (instr[ipos]) { while (instr[ipos] && instr[ipos] != '%') if (instr[ipos] == '+') { str[bpos++] = ' '; ipos++; } else str[bpos++] = instr[ipos++]; if (!instr[ipos]) break; if (instr[ipos + 1] && instr[ipos + 2]) { ipos++; entity[0] = instr[ipos++]; entity[1] = instr[ipos++]; sscanf(entity, "%2x", &dec); str[bpos++] = (char)dec; } else { str[bpos++] = instr[ipos++]; } } str[bpos] = '\0'; /* free extra alloc'ed mem. */ len = strlen(str); str = y_renew(char, str, len + 1); return (str); } char *yahoo_xmldecode(const char *instr) { int ipos = 0, bpos = 0, epos = 0; char *str = NULL; char entity[4] = { 0, 0, 0, 0 }; char *entitymap[5][2] = { {"amp;", "&"}, {"quot;", "\""}, {"lt;", "<"}, {"gt;", "<"}, {"nbsp;", " "} }; unsigned dec; int len = strlen(instr); if (!(str = y_new(char, len + 1))) return ""; while (instr[ipos]) { while (instr[ipos] && instr[ipos] != '&') if (instr[ipos] == '+') { str[bpos++] = ' '; ipos++; } else str[bpos++] = instr[ipos++]; if (!instr[ipos] || !instr[ipos + 1]) break; ipos++; if (instr[ipos] == '#') { ipos++; epos = 0; while (instr[ipos] != ';') entity[epos++] = instr[ipos++]; sscanf(entity, "%u", &dec); str[bpos++] = (char)dec; ipos++; } else { int i; for (i = 0; i < 5; i++) if (!strncmp(instr + ipos, entitymap[i][0], strlen(entitymap[i][0]))) { str[bpos++] = entitymap[i][1][0]; ipos += strlen(entitymap[i][0]); break; } } } str[bpos] = '\0'; /* free extra alloc'ed mem. */ len = strlen(str); str = y_renew(char, str, len + 1); return (str); } #endif typedef void (*http_connected) (int id, void *fd, int error); struct callback_data { int id; yahoo_get_fd_callback callback; char *request; void *user_data; }; static void connect_complete(void *fd, int error, void *data) { struct callback_data *ccd = data; if (error == 0) YAHOO_CALLBACK(ext_yahoo_write) (fd, ccd->request, strlen(ccd->request)); free(ccd->request); ccd->callback(ccd->id, fd, error, ccd->user_data); FREE(ccd); } static void yahoo_send_http_request(int id, char *host, int port, char *request, yahoo_get_fd_callback callback, void *data, int use_ssl) { struct callback_data *ccd = y_new0(struct callback_data, 1); ccd->callback = callback; ccd->id = id; ccd->request = strdup(request); ccd->user_data = data; YAHOO_CALLBACK(ext_yahoo_connect_async) (id, host, port, connect_complete, ccd, use_ssl); } void yahoo_http_post(int id, const char *url, const char *cookies, long content_length, yahoo_get_fd_callback callback, void *data) { char host[255]; int port = 80; char path[255]; char buff[1024]; int ssl = 0; if (!url_to_host_port_path(url, host, &port, path, &ssl)) return; /* thanks to kopete dumpcap */ snprintf(buff, sizeof(buff), "POST %s HTTP/1.1\r\n" "Cookie: %s\r\n" "User-Agent: Mozilla/5.0\r\n" "Host: %s\r\n" "Content-Length: %ld\r\n" "Cache-Control: no-cache\r\n" "\r\n", path, cookies, host, content_length); yahoo_send_http_request(id, host, port, buff, callback, data, ssl); } void yahoo_http_get(int id, const char *url, const char *cookies, int http11, int keepalive, yahoo_get_fd_callback callback, void *data) { char host[255]; int port = 80; char path[255]; char buff[2048]; char cookiebuff[1024]; int ssl = 0; if (!url_to_host_port_path(url, host, &port, path, &ssl)) return; /* Allow cases when we don't need to send a cookie */ if (cookies) snprintf(cookiebuff, sizeof(cookiebuff), "Cookie: %s\r\n", cookies); else cookiebuff[0] = '\0'; snprintf(buff, sizeof(buff), "GET %s HTTP/1.%s\r\n" "%sHost: %s\r\n" "User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n" "Accept: */*\r\n" "%s" "\r\n", path, http11?"1":"0", cookiebuff, host, keepalive? "Connection: Keep-Alive\r\n":"Connection: close\r\n"); yahoo_send_http_request(id, host, port, buff, callback, data, ssl); } void yahoo_http_head(int id, const char *url, const char *cookies, int len, char *payload, yahoo_get_fd_callback callback, void *data) { char host[255]; int port = 80; char path[255]; char buff[2048]; char cookiebuff[1024]; int ssl = 0; if (!url_to_host_port_path(url, host, &port, path, &ssl)) return; /* Allow cases when we don't need to send a cookie */ if (cookies) snprintf(cookiebuff, sizeof(cookiebuff), "Cookie: %s\r\n", cookies); else cookiebuff[0] = '\0'; snprintf(buff, sizeof(buff), "HEAD %s HTTP/1.0\r\n" "Accept: */*\r\n" "Host: %s:%d\r\n" "User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n" "%s" "Content-Length: %d\r\n" "Cache-Control: no-cache\r\n" "\r\n%s", path, host, port, cookiebuff, len, payload?payload:""); yahoo_send_http_request(id, host, port, buff, callback, data, ssl); } bitlbee-3.2.1/protocols/yahoo/yahoo_util.c0000644000175000017500000000451212245474076020203 0ustar wilmerwilmer/* * libyahoo2: yahoo_util.c * * Copyright (C) 2002-2004, Philip S Tellis * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #if STDC_HEADERS # include #else # if !HAVE_STRCHR # define strchr index # define strrchr rindex # endif char *strchr (), *strrchr (); # if !HAVE_MEMCPY # define memcpy(d, s, n) bcopy ((s), (d), (n)) # define memmove(d, s, n) bcopy ((s), (d), (n)) # endif #endif #include "yahoo_util.h" char *y_string_append(char *string, char *append) { int size = strlen(string) + strlen(append) + 1; char *new_string = y_renew(char, string, size); if (new_string == NULL) { new_string = y_new(char, size); strcpy(new_string, string); FREE(string); } strcat(new_string, append); return new_string; } #if !HAVE_GLIB void y_strfreev(char ** vector) { char **v; for(v = vector; *v; v++) { FREE(*v); } FREE(vector); } char ** y_strsplit(char * str, char * sep, int nelem) { char ** vector; char *s, *p; int i=0; int l = strlen(sep); if(nelem <= 0) { char * s; nelem=0; if (*str) { for(s=strstr(str, sep); s; s=strstr(s+l, sep),nelem++) ; if(strcmp(str+strlen(str)-l, sep)) nelem++; } } vector = y_new(char *, nelem + 1); for(p=str, s=strstr(p,sep); i * \********************************************************************/ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" #include "ft.h" file_transfer_t *imcb_file_send_start( struct im_connection *ic, char *handle, char *file_name, size_t file_size ) { bee_t *bee = ic->bee; bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); if( bee->ui->ft_in_start ) return bee->ui->ft_in_start( bee, bu, file_name, file_size ); else return NULL; } gboolean imcb_file_recv_start( struct im_connection *ic, file_transfer_t *ft ) { bee_t *bee = ic->bee; if( bee->ui->ft_out_start ) return bee->ui->ft_out_start( ic, ft ); else return FALSE; } void imcb_file_canceled( struct im_connection *ic, file_transfer_t *file, char *reason ) { bee_t *bee = ic->bee; if( file->canceled ) file->canceled( file, reason ); if( bee->ui->ft_close ) bee->ui->ft_close( ic, file ); } void imcb_file_finished( struct im_connection *ic, file_transfer_t *file ) { bee_t *bee = ic->bee; if( bee->ui->ft_finished ) bee->ui->ft_finished( ic, file ); } bitlbee-3.2.1/protocols/nogaim.h0000644000175000017500000003413212245474076016170 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* * nogaim, soon to be known as im_api. Not a separate product (unless * someone would be interested in such a thing), just a new name. * * Gaim without gaim - for BitlBee * * This file contains functions called by the Gaim IM-modules. It contains * some struct and type definitions from Gaim. * * Copyright (C) 1998-1999, Mark Spencer * (and possibly other members of the Gaim team) * Copyright 2002-2012 Wilmer van der Gaast */ /* 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 _NOGAIM_H #define _NOGAIM_H #if(__sun) #include #else #include #endif #include "bitlbee.h" #include "account.h" #include "proxy.h" #include "query.h" #include "md5.h" #include "ft.h" #define BUDDY_ALIAS_MAXLEN 388 /* because MSN names can be 387 characters */ #define WEBSITE "http://www.bitlbee.org/" /* Sharing flags between all kinds of things. I just hope I won't hit any limits before 32-bit machines become extinct. ;-) */ #define OPT_LOGGED_IN 0x00000001 #define OPT_LOGGING_OUT 0x00000002 #define OPT_AWAY 0x00000004 #define OPT_MOBILE 0x00000008 #define OPT_DOES_HTML 0x00000010 #define OPT_LOCALBUDDY 0x00000020 /* For nicks local to one groupchat */ #define OPT_SLOW_LOGIN 0x00000040 /* I.e. Twitter Oauth @ login time */ #define OPT_TYPING 0x00000100 /* Some pieces of code make assumptions */ #define OPT_THINKING 0x00000200 /* about these values... Stupid me! */ #define OPT_NOOTR 0x00001000 /* protocol not suitable for OTR */ #define OPT_PONGS 0x00010000 /* Service sends us keep-alives */ #define OPT_PONGED 0x00020000 /* Received a keep-alive during last interval */ /* ok. now the fun begins. first we create a connection structure */ struct im_connection { account_t *acc; uint32_t flags; /* each connection then can have its own protocol-specific data */ void *proto_data; /* all connections need an input watcher */ int inpa; guint keepalive; /* buddy list stuff. there is still a global groups for the buddy list, but * we need to maintain our own set of buddies, and our own permit/deny lists */ GSList *permit; GSList *deny; int permdeny; char *away; /* BitlBee */ bee_t *bee; GSList *groupchats; }; struct groupchat { struct im_connection *ic; /* stuff used just for chat */ /* The in_room variable is a list of handles (not nicks!), kind of * "nick list". This is how you can check who is in the group chat * already, for example to avoid adding somebody two times. */ GList *in_room; //GList *ignored; //struct groupchat *next; /* The title variable contains the ID you gave when you created the * chat using imcb_chat_new(). */ char *title; /* Use imcb_chat_topic() to change this variable otherwise the user * won't notice the topic change. */ char *topic; char joined; /* This is for you, you can add your own structure here to extend this * structure for your protocol's needs. */ void *data; void *ui_data; }; struct buddy { char name[80]; char show[BUDDY_ALIAS_MAXLEN]; int present; time_t signon; time_t idle; int uc; guint caps; /* woohoo! */ void *proto_data; /* what a hack */ struct im_connection *ic; /* the connection it belongs to */ }; struct buddy_action { char *name; char *description; }; struct prpl { int options; /* You should set this to the name of your protocol. * - The user sees this name ie. when imcb_log() is used. */ const char *name; void *data; /* Maximum Message Size of this protocol. * - Introduced for OTR, in order to fragment large protocol messages. * - 0 means "unlimited". */ unsigned int mms; /* Added this one to be able to add per-account settings, don't think * it should be used for anything else. You are supposed to use the * set_add() function to add new settings. */ void (* init) (account_t *); /* The typical usage of the login() function: * - Create an im_connection using imcb_new() from the account_t parameter. * - Initialize your myproto_data struct - you should store all your protocol-specific data there. * - Save your custom structure to im_connection->proto_data. * - Use proxy_connect() to connect to the server. */ void (* login) (account_t *); /* Implementing this function is optional. */ void (* keepalive) (struct im_connection *); /* In this function you should: * - Tell the server about you are logging out. * - g_free() your myproto_data struct as BitlBee does not know how to * properly do so. */ void (* logout) (struct im_connection *); /* This function is called when the user wants to send a message to a handle. * - 'to' is a handle, not a nick * - 'flags' may be ignored */ int (* buddy_msg) (struct im_connection *, char *to, char *message, int flags); /* This function is called then the user uses the /away IRC command. * - 'state' contains the away reason. * - 'message' may be ignored if your protocol does not support it. */ void (* set_away) (struct im_connection *, char *state, char *message); /* Implementing this function is optional. */ void (* get_away) (struct im_connection *, char *who); /* Implementing this function is optional. */ int (* send_typing) (struct im_connection *, char *who, int flags); /* 'name' is a handle to add/remove. For now BitlBee doesn't really * handle groups, just set it to NULL, so you can ignore that * parameter. */ void (* add_buddy) (struct im_connection *, char *name, char *group); void (* remove_buddy) (struct im_connection *, char *name, char *group); /* Block list stuff. Implementing these are optional. */ void (* add_permit) (struct im_connection *, char *who); void (* add_deny) (struct im_connection *, char *who); void (* rem_permit) (struct im_connection *, char *who); void (* rem_deny) (struct im_connection *, char *who); /* Doesn't actually have UI hooks. Not used at all, can be removed. */ void (* set_permit_deny)(struct im_connection *); /* Request profile info. Free-formatted stuff, the IM module gives back this info via imcb_log(). Implementing these are optional. */ void (* get_info) (struct im_connection *, char *who); /* set_my_name is *DEPRECATED*, not used by the UI anymore. Use the display_name setting instead. */ void (* set_my_name) (struct im_connection *, char *name); void (* set_name) (struct im_connection *, char *who, char *name); /* Group chat stuff. */ /* This is called when the user uses the /invite IRC command. * - 'who' may be ignored * - 'message' is a handle to invite */ void (* chat_invite) (struct groupchat *, char *who, char *message); /* This is called when the user uses the /part IRC command in a group * chat. You just should tell the user about it, nothing more. */ void (* chat_leave) (struct groupchat *); /* This is called when the user sends a message to the groupchat. * 'flags' may be ignored. */ void (* chat_msg) (struct groupchat *, char *message, int flags); /* This is called when the user uses the /join #nick IRC command. * - 'who' is the handle of the nick */ struct groupchat * (* chat_with) (struct im_connection *, char *who); /* This is used when the user uses the /join #channel IRC command. If * your protocol does not support publicly named group chats, then do * not implement this. */ struct groupchat * (* chat_join) (struct im_connection *, const char *room, const char *nick, const char *password, set_t **sets); /* Change the topic, if supported. Note that BitlBee expects the IM server to confirm the topic change with a regular topic change event. If it doesn't do that, you have to fake it to make it visible to the user. */ void (* chat_topic) (struct groupchat *, char *topic); /* If your protocol module needs any special info for joining chatrooms other than a roomname + nickname, add them here. */ void (* chat_add_settings) (account_t *acc, set_t **head); void (* chat_free_settings) (account_t *acc, set_t **head); /* You can tell what away states your protocol supports, so that * BitlBee will try to map the IRC away reasons to them. If your * protocol doesn't have any, just return one generic "Away". */ GList *(* away_states)(struct im_connection *ic); /* Mainly for AOL, since they think "Bung hole" == "Bu ngho le". *sigh* * - Most protocols will just want to set this to g_strcasecmp().*/ int (* handle_cmp) (const char *who1, const char *who2); /* Implement these callbacks if you want to use imcb_ask_auth() */ void (* auth_allow) (struct im_connection *, const char *who); void (* auth_deny) (struct im_connection *, const char *who); /* Incoming transfer request */ void (* transfer_request) (struct im_connection *, file_transfer_t *ft, char *handle ); void (* buddy_data_add) (struct bee_user *bu); void (* buddy_data_free) (struct bee_user *bu); GList *(* buddy_action_list) (struct bee_user *bu); void *(* buddy_action) (struct bee_user *bu, const char *action, char * const args[], void *data); /* Some placeholders so eventually older plugins may cooperate with newer BitlBees. */ void *resv1; void *resv2; void *resv3; void *resv4; void *resv5; }; /* im_api core stuff. */ void nogaim_init(); G_MODULE_EXPORT GSList *get_connections(); G_MODULE_EXPORT struct prpl *find_protocol( const char *name ); /* When registering a new protocol, you should allocate space for a new prpl * struct, initialize it (set the function pointers to point to your * functions), finally call this function. */ G_MODULE_EXPORT void register_protocol( struct prpl * ); /* Connection management. */ /* You will need this function in prpl->login() to get an im_connection from * the account_t parameter. */ G_MODULE_EXPORT struct im_connection *imcb_new( account_t *acc ); G_MODULE_EXPORT void imc_free( struct im_connection *ic ); /* Once you're connected, you should call this function, so that the user will * see the success. */ G_MODULE_EXPORT void imcb_connected( struct im_connection *ic ); /* This can be used to disconnect when something went wrong (ie. read error * from the server). You probably want to set the second parameter to TRUE. */ G_MODULE_EXPORT void imc_logout( struct im_connection *ic, int allow_reconnect ); /* Communicating with the user. */ /* A printf()-like function to tell the user anything you want. */ G_MODULE_EXPORT void imcb_log( struct im_connection *ic, char *format, ... ) G_GNUC_PRINTF( 2, 3 ); /* To tell the user an error, ie. before logging out when an error occurs. */ G_MODULE_EXPORT void imcb_error( struct im_connection *ic, char *format, ... ) G_GNUC_PRINTF( 2, 3 ); /* To ask a your about something. * - 'msg' is the question. * - 'data' can be your custom struct - it will be passed to the callbacks. * - 'doit' or 'dont' will be called depending of the answer of the user. */ G_MODULE_EXPORT void imcb_ask( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont ); G_MODULE_EXPORT void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont, query_callback myfree ); /* Two common questions you may want to ask: * - X added you to his contact list, allow? * - X is not in your contact list, want to add? */ G_MODULE_EXPORT void imcb_ask_auth( struct im_connection *ic, const char *handle, const char *realname ); G_MODULE_EXPORT void imcb_ask_add( struct im_connection *ic, const char *handle, const char *realname ); /* Buddy management */ /* This function should be called for each handle which are visible to the * user, usually after a login, or if the user added a buddy and the IM * server confirms that the add was successful. Don't forget to do this! */ G_MODULE_EXPORT void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group ); G_MODULE_EXPORT void imcb_remove_buddy( struct im_connection *ic, const char *handle, char *group ); G_MODULE_EXPORT struct buddy *imcb_find_buddy( struct im_connection *ic, char *handle ); G_MODULE_EXPORT void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *realname ); G_MODULE_EXPORT void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick ); G_MODULE_EXPORT void imcb_buddy_action_response( bee_user_t *bu, const char *action, char * const args[], void *data ); G_MODULE_EXPORT void imcb_buddy_typing( struct im_connection *ic, const char *handle, uint32_t flags ); G_MODULE_EXPORT struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle ); G_MODULE_EXPORT void imcb_clean_handle( struct im_connection *ic, char *handle ); /* Actions, or whatever. */ int imc_away_send_update( struct im_connection *ic ); int imc_chat_msg( struct groupchat *c, char *msg, int flags ); void imc_add_allow( struct im_connection *ic, char *handle ); void imc_rem_allow( struct im_connection *ic, char *handle ); void imc_add_block( struct im_connection *ic, char *handle ); void imc_rem_block( struct im_connection *ic, char *handle ); /* Misc. stuff */ char *set_eval_timezone( set_t *set, char *value ); gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond ); void cancel_auto_reconnect( struct account *a ); #endif bitlbee-3.2.1/protocols/bee_user.c0000644000175000017500000001606212245474076016504 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2010 Wilmer van der Gaast and others * \********************************************************************/ /* Stuff to handle, save and search buddies */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" bee_user_t *bee_user_new( bee_t *bee, struct im_connection *ic, const char *handle, bee_user_flags_t flags ) { bee_user_t *bu; if( bee_user_by_handle( bee, ic, handle ) != NULL ) return NULL; bu = g_new0( bee_user_t, 1 ); bu->bee = bee; bu->ic = ic; bu->flags = flags; bu->handle = g_strdup( handle ); bee->users = g_slist_prepend( bee->users, bu ); if( bee->ui->user_new ) bee->ui->user_new( bee, bu ); if( ic->acc->prpl->buddy_data_add ) ic->acc->prpl->buddy_data_add( bu ); /* Offline by default. This will set the right flags. */ imcb_buddy_status( ic, handle, 0, NULL, NULL ); return bu; } int bee_user_free( bee_t *bee, bee_user_t *bu ) { if( !bu ) return 0; if( bee->ui->user_free ) bee->ui->user_free( bee, bu ); if( bu->ic->acc->prpl->buddy_data_free ) bu->ic->acc->prpl->buddy_data_free( bu ); g_free( bu->handle ); g_free( bu->fullname ); g_free( bu->nick ); g_free( bu->status ); g_free( bu->status_msg ); g_free( bu ); bee->users = g_slist_remove( bee->users, bu ); return 1; } bee_user_t *bee_user_by_handle( bee_t *bee, struct im_connection *ic, const char *handle ) { GSList *l; for( l = bee->users; l; l = l->next ) { bee_user_t *bu = l->data; if( bu->ic == ic && ic->acc->prpl->handle_cmp( bu->handle, handle ) == 0 ) return bu; } return NULL; } int bee_user_msg( bee_t *bee, bee_user_t *bu, const char *msg, int flags ) { char *buf = NULL; int st; if( ( bu->ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "", 6 ) != 0 ) ) { buf = escape_html( msg ); msg = buf; } else buf = g_strdup( msg ); st = bu->ic->acc->prpl->buddy_msg( bu->ic, bu->handle, buf, flags ); g_free( buf ); return st; } /* Groups */ static bee_group_t *bee_group_new( bee_t *bee, const char *name ) { bee_group_t *bg = g_new0( bee_group_t, 1 ); bg->name = g_strdup( name ); bg->key = g_utf8_casefold( name, -1 ); bee->groups = g_slist_prepend( bee->groups, bg ); return bg; } bee_group_t *bee_group_by_name( bee_t *bee, const char *name, gboolean creat ) { GSList *l; char *key; if( name == NULL ) return NULL; key = g_utf8_casefold( name, -1 ); for( l = bee->groups; l; l = l->next ) { bee_group_t *bg = l->data; if( strcmp( bg->key, key ) == 0 ) break; } g_free( key ); if( !l ) return creat ? bee_group_new( bee, name ) : NULL; else return l->data; } void bee_group_free( bee_t *bee ) { while( bee->groups ) { bee_group_t *bg = bee->groups->data; g_free( bg->name ); g_free( bg->key ); g_free( bg ); bee->groups = g_slist_remove( bee->groups, bee->groups->data ); } } /* IM->UI callbacks */ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message ) { bee_t *bee = ic->bee; bee_user_t *bu, *old; if( !( bu = bee_user_by_handle( bee, ic, handle ) ) ) { if( g_strcasecmp( set_getstr( &ic->bee->set, "handle_unknown" ), "add" ) == 0 ) { bu = bee_user_new( bee, ic, handle, BEE_USER_LOCAL ); } else { if( g_strcasecmp( set_getstr( &ic->bee->set, "handle_unknown" ), "ignore" ) != 0 ) { imcb_log( ic, "imcb_buddy_status() for unknown handle %s:\n" "flags = %d, state = %s, message = %s", handle, flags, state ? state : "NULL", message ? message : "NULL" ); } return; } } /* May be nice to give the UI something to compare against. */ old = g_memdup( bu, sizeof( bee_user_t ) ); /* TODO(wilmer): OPT_AWAY, or just state == NULL ? */ bu->flags = flags; bu->status_msg = g_strdup( message ); if( state && *state ) bu->status = g_strdup( state ); else if( flags & OPT_AWAY ) bu->status = g_strdup( "Away" ); else bu->status = NULL; if( bu->status == NULL && ( flags & OPT_MOBILE ) && set_getbool( &bee->set, "mobile_is_away" ) ) { bu->flags |= BEE_USER_AWAY; bu->status = g_strdup( "Mobile" ); } if( bee->ui->user_status ) bee->ui->user_status( bee, bu, old ); g_free( old->status_msg ); g_free( old->status ); g_free( old ); } /* Same, but only change the away/status message, not any away/online state info. */ void imcb_buddy_status_msg( struct im_connection *ic, const char *handle, const char *message ) { bee_t *bee = ic->bee; bee_user_t *bu, *old; if( !( bu = bee_user_by_handle( bee, ic, handle ) ) ) { return; } old = g_memdup( bu, sizeof( bee_user_t ) ); bu->status_msg = message && *message ? g_strdup( message ) : NULL; if( bee->ui->user_status ) bee->ui->user_status( bee, bu, old ); g_free( old->status_msg ); g_free( old ); } void imcb_buddy_times( struct im_connection *ic, const char *handle, time_t login, time_t idle ) { bee_t *bee = ic->bee; bee_user_t *bu; if( !( bu = bee_user_by_handle( bee, ic, handle ) ) ) return; bu->login_time = login; bu->idle_time = idle; } void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, uint32_t flags, time_t sent_at ) { bee_t *bee = ic->bee; bee_user_t *bu; bu = bee_user_by_handle( bee, ic, handle ); if( !bu && !( ic->flags & OPT_LOGGING_OUT ) ) { char *h = set_getstr( &bee->set, "handle_unknown" ); if( g_strcasecmp( h, "ignore" ) == 0 ) { return; } else if( g_strncasecmp( h, "add", 3 ) == 0 ) { bu = bee_user_new( bee, ic, handle, BEE_USER_LOCAL ); } } if( bee->ui->user_msg && bu ) bee->ui->user_msg( bee, bu, msg, sent_at ); else imcb_log( ic, "Message from unknown handle %s:\n%s", handle, msg ); } void imcb_buddy_typing( struct im_connection *ic, const char *handle, uint32_t flags ) { bee_user_t *bu; if( ic->bee->ui->user_typing && ( bu = bee_user_by_handle( ic->bee, ic, handle ) ) ) { ic->bee->ui->user_typing( ic->bee, bu, flags ); } } void imcb_buddy_action_response( bee_user_t *bu, const char *action, char * const args[], void *data ) { if( bu->bee->ui->user_action_response ) bu->bee->ui->user_action_response( bu->bee, bu, action, args, data ); } bitlbee-3.2.1/protocols/msn/0000755000175000017500000000000012245477444015341 5ustar wilmerwilmerbitlbee-3.2.1/protocols/msn/Makefile0000644000175000017500000000143112245474076016776 0ustar wilmerwilmer########################### ## Makefile for BitlBee ## ## ## ## Copyright 2002 Lintux ## ########################### ### DEFINITIONS -include ../../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)protocols/msn/ endif # [SH] Program variables objects = msn.o msn_util.o ns.o sb.o soap.o tables.o LFLAGS += -r # [SH] Phony targets all: msn_mod.o check: all lcov: check gcov: gcov *.c .PHONY: all clean distclean clean: rm -f *.o core distclean: clean rm -rf .depend ### MAIN PROGRAM $(objects): ../../Makefile.settings Makefile $(objects): %.o: $(_SRCDIR_)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $(CFLAGS_BITLBEE) $< -o $@ msn_mod.o: $(objects) @echo '*' Linking msn_mod.o @$(LD) $(LFLAGS) $(objects) -o msn_mod.o -include .depend/*.d bitlbee-3.2.1/protocols/msn/sb.c0000644000175000017500000004713212245474076016116 0ustar wilmerwilmer /********************************************************************\ * 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 ) { 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} %s\r\n", ++sb->trId, ic->acc->user, md->uuid, sb->key ); else g_snprintf( buf, sizeof( buf ), "ANS %d %s;{%s} %s %d\r\n", ++sb->trId, ic->acc->user, md->uuid, 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]; /* For as much as I understand this MPOP stuff now, a switchboard has two (or more) roster entries per participant. One "bare JID" and one JID;UUID. Ignore the latter. */ if( !strchr( cmd[4], ';' ) ) { /* HACK: Since even 1:1 chats now have >2 participants (ourselves included) it gets hard to tell them apart from rooms. Let's hope this is enough: */ if( sb->chat == NULL && num != tot ) { 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; } if( sb->chat ) imcb_chat_add_buddy( sb->chat, cmd[4] ); } /* We have the full roster, start showing the channel to the user. */ if( num == tot && sb->chat ) { 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 ); } /* 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; } } bitlbee-3.2.1/protocols/msn/invitation.c0000644000175000017500000004707112245474076017700 0ustar wilmerwilmer/********************************************************************\ * 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; } bitlbee-3.2.1/protocols/msn/soap.h0000644000175000017500000003736412245474076016467 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* MSN module - All the SOAPy XML stuff. Some manager at Microsoft apparently thought MSNP wasn't XMLy enough so someone stepped up and changed that. This is the result. Kilobytes and more kilobytes of XML vomit to transfer tiny bits of informaiton. */ /* 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 */ /* Thanks to http://msnpiki.msnfanatic.com/ for lots of info on this! */ #ifndef __SOAP_H__ #define __SOAP_H__ #include #include #include #include #ifndef _WIN32 #include #include #include #include #endif #include "nogaim.h" int msn_soapq_flush( struct im_connection *ic, gboolean resend ); #define SOAP_HTTP_REQUEST \ "POST %s HTTP/1.0\r\n" \ "Host: %s\r\n" \ "Accept: */*\r\n" \ "User-Agent: BitlBee " BITLBEE_VERSION "\r\n" \ "Content-Type: text/xml; charset=utf-8\r\n" \ "%s" \ "Content-Length: %zd\r\n" \ "Cache-Control: no-cache\r\n" \ "\r\n" \ "%s" #define SOAP_PASSPORT_SSO_URL "https://login.live.com/RST.srf" #define SOAP_PASSPORT_SSO_URL_MSN "https://msnia.login.live.com/pp900/RST.srf" #define MAX_PASSPORT_PWLEN 16 #define SOAP_PASSPORT_SSO_PAYLOAD \ "" \ "
" \ "" \ "{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}" \ "4" \ "1" \ "" \ "AQAAAAIAAABsYwQAAAAxMDMz" \ "" \ "" \ "" \ "%s" \ "%s" \ "" \ "" \ "
" \ "" \ "" \ "" \ "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue" \ "" \ "" \ "http://Passport.NET/tb" \ "" \ "" \ "" \ "" \ "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue" \ "" \ "" \ "messengerclear.live.com" \ "" \ "" \ "" \ "" \ "" \ "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue" \ "" \ "" \ "contacts.msn.com" \ "" \ "" \ "" \ "" \ "" \ "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue" \ "" \ "" \ "messengersecure.live.com" \ "" \ "" \ "" \ "" \ "" \ "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue" \ "" \ "" \ "storage.msn.com" \ "" \ "" \ "" \ "" \ "" \ "" \ "
" int msn_soap_passport_sso_request( struct im_connection *ic, const char *nonce ); #define SOAP_ABSERVICE_PAYLOAD \ "" \ "" \ "" \ "" \ "CFE80F9D-180F-4399-82AB-413F33A1FA11" \ "false" \ "%s" \ "" \ "" \ "false" \ "%s" \ "" \ "" \ "" \ "%%s" \ "" \ "" #define SOAP_MEMLIST_URL "http://contacts.msn.com/abservice/SharingService.asmx" #define SOAP_MEMLIST_ACTION "http://www.msn.com/webservices/AddressBook/FindMembership" #define SOAP_MEMLIST_PAYLOAD \ "MessengerInvitationSocialNetworkSpaceProfile" \ "" #define SOAP_MEMLIST_ADD_ACTION "http://www.msn.com/webservices/AddressBook/AddMember" #define SOAP_MEMLIST_DEL_ACTION "http://www.msn.com/webservices/AddressBook/DeleteMember" #define SOAP_MEMLIST_EDIT_PAYLOAD \ "<%sMember xmlns=\"http://www.msn.com/webservices/AddressBook\">" \ "" \ "0" \ "Messenger" \ "" \ "" \ "" \ "" \ "%s" \ "" \ "" \ "Passport" \ "Accepted" \ "%s" \ "" \ "" \ "" \ "" \ "" int msn_soap_memlist_request( struct im_connection *ic ); int msn_soap_memlist_edit( struct im_connection *ic, const char *handle, gboolean add, int list ); #define SOAP_ADDRESSBOOK_URL "http://contacts.msn.com/abservice/abservice.asmx" #define SOAP_ADDRESSBOOK_ACTION "http://www.msn.com/webservices/AddressBook/ABFindAll" #define SOAP_ADDRESSBOOK_PAYLOAD \ "" \ "00000000-0000-0000-0000-000000000000" \ "Full" \ "false" \ "0001-01-01T00:00:00.0000000-08:00" \ "" #define SOAP_AB_NAMECHANGE_ACTION "http://www.msn.com/webservices/AddressBook/ABContactUpdate" #define SOAP_AB_NAMECHANGE_PAYLOAD \ "" \ "00000000-0000-0000-0000-000000000000" \ "" \ "" \ "" \ "Me" \ "%s" \ "" \ "DisplayName" \ "" \ "" \ "" #define SOAP_AB_CONTACT_ADD_ACTION "http://www.msn.com/webservices/AddressBook/ABContactAdd" #define SOAP_AB_CONTACT_ADD_PAYLOAD \ "" \ "00000000-0000-0000-0000-000000000000" \ "" \ "" \ "" \ "LivePending" \ "%s" \ "true" \ "" \ "%s" \ "" \ "" \ "" \ "" \ "" \ "true" \ "" \ "" #define SOAP_AB_CONTACT_DEL_ACTION "http://www.msn.com/webservices/AddressBook/ABContactDelete" #define SOAP_AB_CONTACT_DEL_PAYLOAD \ "" \ "00000000-0000-0000-0000-000000000000" \ "" \ "" \ "%s" \ "" \ "" \ "" int msn_soap_addressbook_request( struct im_connection *ic ); int msn_soap_addressbook_set_display_name( struct im_connection *ic, const char *new ); int msn_soap_ab_contact_add( struct im_connection *ic, bee_user_t *bu ); int msn_soap_ab_contact_del( struct im_connection *ic, bee_user_t *bu ); #define SOAP_STORAGE_URL "https://storage.msn.com/storageservice/SchematizedStore.asmx" #define SOAP_PROFILE_GET_ACTION "http://www.msn.com/webservices/storage/w10/GetProfile" #define SOAP_PROFILE_SET_DN_ACTION "http://www.msn.com/webservices/storage/w10/UpdateProfile" #define SOAP_PROFILE_GET_PAYLOAD \ "" \ "" \ "" \ "" \ "Messenger Client 9.0" \ "Initial" \ "" \ "" \ "0" \ "%s" \ "" \ "" \ "" \ "" \ "" \ "" \ "%s" \ "MyCidStuff" \ "" \ "MyProfile" \ "" \ "" \ "true" \ "true" \ "" \ "true" \ "true" \ "true" \ "true" \ "true" \ "true" \ "true" \ "true" \ "true" \ "" \ "" \ "" \ "" \ "" #define SOAP_PROFILE_SET_DN_PAYLOAD \ "" \ "" \ "" \ "" \ "Messenger Client 9.0" \ "Initial" \ "" \ "" \ "0" \ "%s" \ "" \ "" \ "" \ "" \ "" \ "%s" \ "" \ "Update" \ "%s" \ "0" \ "" \ "" \ "" \ "" \ "" int msn_soap_profile_get( struct im_connection *ic, const char *cid ); int msn_soap_profile_set_dn( struct im_connection *ic, const char *dn ); #endif /* __SOAP_H__ */ bitlbee-3.2.1/protocols/msn/ns.c0000644000175000017500000006331412245474076016132 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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 #include #include "nogaim.h" #include "msn.h" #include "md5.h" #include "sha1.h" #include "soap.h" #include "xmltree.h" static gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond ); static gboolean msn_ns_callback( gpointer data, gint source, b_input_condition cond ); static int msn_ns_command( struct msn_handler_data *handler, char **cmd, int num_parts ); static int msn_ns_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts ); static void msn_ns_send_adl_start( struct im_connection *ic ); static void msn_ns_send_adl( struct im_connection *ic ); int msn_ns_write( struct im_connection *ic, int fd, const char *fmt, ... ) { struct msn_data *md = ic->proto_data; va_list params; char *out; size_t len; int st; va_start( params, fmt ); out = g_strdup_vprintf( fmt, params ); va_end( params ); if( fd < 0 ) fd = md->ns->fd; if( getenv( "BITLBEE_DEBUG" ) ) fprintf( stderr, "->NS%d:%s\n", fd, out ); len = strlen( out ); st = write( fd, out, len ); g_free( out ); if( st != len ) { imcb_error( ic, "Short write() to main server" ); imc_logout( ic, TRUE ); return 0; } return 1; } gboolean msn_ns_connect( struct im_connection *ic, struct msn_handler_data *handler, const char *host, int port ) { if( handler->fd >= 0 ) closesocket( handler->fd ); handler->exec_command = msn_ns_command; handler->exec_message = msn_ns_message; handler->data = ic; handler->fd = proxy_connect( host, port, msn_ns_connected, handler ); if( handler->fd < 0 ) { imcb_error( ic, "Could not connect to server" ); imc_logout( ic, TRUE ); return FALSE; } return TRUE; } static gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond ) { struct msn_handler_data *handler = data; struct im_connection *ic = handler->data; struct msn_data *md; if( !g_slist_find( msn_connections, ic ) ) return FALSE; md = ic->proto_data; if( source == -1 ) { imcb_error( ic, "Could not connect to server" ); imc_logout( ic, TRUE ); return FALSE; } g_free( handler->rxq ); handler->rxlen = 0; handler->rxq = g_new0( char, 1 ); if( md->uuid == NULL ) { struct utsname name; sha1_state_t sha[1]; /* UUID == SHA1("BitlBee" + my hostname + MSN username) */ sha1_init( sha ); sha1_append( sha, (void*) "BitlBee", 7 ); if( uname( &name ) == 0 ) { sha1_append( sha, (void*) name.nodename, strlen( name.nodename ) ); } sha1_append( sha, (void*) ic->acc->user, strlen( ic->acc->user ) ); md->uuid = sha1_random_uuid( sha ); memcpy( md->uuid, "b171be3e", 8 ); /* :-P */ } if( msn_ns_write( ic, source, "VER %d %s CVR0\r\n", ++md->trId, MSNP_VER ) ) { handler->inpa = b_input_add( handler->fd, B_EV_IO_READ, msn_ns_callback, handler ); imcb_log( ic, "Connected to server, waiting for reply" ); } return FALSE; } void msn_ns_close( struct msn_handler_data *handler ) { if( handler->fd >= 0 ) { closesocket( handler->fd ); b_event_remove( handler->inpa ); } handler->fd = handler->inpa = -1; g_free( handler->rxq ); g_free( handler->cmd_text ); handler->rxlen = 0; handler->rxq = NULL; handler->cmd_text = NULL; } static gboolean msn_ns_callback( gpointer data, gint source, b_input_condition cond ) { struct msn_handler_data *handler = data; struct im_connection *ic = handler->data; if( msn_handler( handler ) == -1 ) /* Don't do this on ret == 0, it's already done then. */ { imcb_error( ic, "Error while reading from server" ); imc_logout( ic, TRUE ); return FALSE; } else return TRUE; } static int msn_ns_command( struct msn_handler_data *handler, char **cmd, int num_parts ) { struct im_connection *ic = handler->data; struct msn_data *md = ic->proto_data; if( num_parts == 0 ) { /* Hrrm... Empty command...? Ignore? */ return( 1 ); } if( strcmp( cmd[0], "VER" ) == 0 ) { if( cmd[2] && strncmp( cmd[2], MSNP_VER, 5 ) != 0 ) { imcb_error( ic, "Unsupported protocol" ); imc_logout( ic, FALSE ); return( 0 ); } return( msn_ns_write( ic, handler->fd, "CVR %d 0x0409 mac 10.2.0 ppc macmsgs 3.5.1 macmsgs %s\r\n", ++md->trId, ic->acc->user ) ); } else if( strcmp( cmd[0], "CVR" ) == 0 ) { /* We don't give a damn about the information we just received */ return msn_ns_write( ic, handler->fd, "USR %d SSO I %s\r\n", ++md->trId, ic->acc->user ); } else if( strcmp( cmd[0], "XFR" ) == 0 ) { char *server; int port; if( num_parts >= 6 && strcmp( cmd[2], "NS" ) == 0 ) { b_event_remove( handler->inpa ); handler->inpa = -1; server = strchr( cmd[3], ':' ); if( !server ) { imcb_error( ic, "Syntax error" ); imc_logout( ic, TRUE ); return( 0 ); } *server = 0; port = atoi( server + 1 ); server = cmd[3]; imcb_log( ic, "Transferring to other server" ); return msn_ns_connect( ic, handler, server, port ); } else if( num_parts >= 6 && strcmp( cmd[2], "SB" ) == 0 ) { struct msn_switchboard *sb; server = strchr( cmd[3], ':' ); if( !server ) { imcb_error( ic, "Syntax error" ); imc_logout( ic, TRUE ); return( 0 ); } *server = 0; port = atoi( server + 1 ); server = cmd[3]; if( strcmp( cmd[4], "CKI" ) != 0 ) { imcb_error( ic, "Unknown authentication method for switchboard" ); imc_logout( ic, TRUE ); return( 0 ); } debug( "Connecting to a new switchboard with key %s", cmd[5] ); if( ( sb = msn_sb_create( ic, server, port, cmd[5], MSN_SB_NEW ) ) == NULL ) { /* Although this isn't strictly fatal for the NS connection, it's definitely something serious (we ran out of file descriptors?). */ imcb_error( ic, "Could not create new switchboard" ); imc_logout( ic, TRUE ); return( 0 ); } 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 { imcb_error( ic, "Syntax error" ); imc_logout( ic, TRUE ); return( 0 ); } } else if( strcmp( cmd[0], "USR" ) == 0 ) { if( num_parts >= 6 && strcmp( cmd[2], "SSO" ) == 0 && strcmp( cmd[3], "S" ) == 0 ) { g_free( md->pp_policy ); md->pp_policy = g_strdup( cmd[4] ); msn_soap_passport_sso_request( ic, cmd[5] ); } else if( strcmp( cmd[2], "OK" ) == 0 ) { /* If the number after the handle is 0, the e-mail address is unverified, which means we can't change the display name. */ if( cmd[4][0] == '0' ) md->flags |= MSN_EMAIL_UNVERIFIED; imcb_log( ic, "Authenticated, getting buddy list" ); msn_soap_memlist_request( ic ); } else { imcb_error( ic, "Unknown authentication type" ); imc_logout( ic, FALSE ); return( 0 ); } } else if( strcmp( cmd[0], "MSG" ) == 0 ) { if( num_parts < 4 ) { imcb_error( ic, "Syntax error" ); imc_logout( ic, TRUE ); return( 0 ); } handler->msglen = atoi( cmd[3] ); if( handler->msglen <= 0 ) { imcb_error( ic, "Syntax error" ); imc_logout( ic, TRUE ); return( 0 ); } } else if( strcmp( cmd[0], "BLP" ) == 0 ) { msn_ns_send_adl_start( ic ); return msn_ns_finish_login( ic ); } else if( strcmp( cmd[0], "ADL" ) == 0 ) { if( num_parts >= 3 && strcmp( cmd[2], "OK" ) == 0 ) { msn_ns_send_adl( ic ); return msn_ns_finish_login( ic ); } else if( num_parts >= 3 ) { handler->msglen = atoi( cmd[2] ); } } else if( strcmp( cmd[0], "PRP" ) == 0 ) { imcb_connected( ic ); } else if( strcmp( cmd[0], "CHL" ) == 0 ) { char *resp; int st; if( num_parts < 3 ) { imcb_error( ic, "Syntax error" ); imc_logout( ic, TRUE ); return( 0 ); } resp = msn_p11_challenge( cmd[2] ); st = msn_ns_write( ic, -1, "QRY %d %s %zd\r\n%s", ++md->trId, MSNP11_PROD_ID, strlen( resp ), resp ); g_free( resp ); return st; } else if( strcmp( cmd[0], "ILN" ) == 0 || strcmp( cmd[0], "NLN" ) == 0 ) { const struct msn_away_state *st; const char *handle; int cap = 0; if( num_parts < 6 ) { imcb_error( ic, "Syntax error" ); imc_logout( ic, TRUE ); return( 0 ); } /* ILN and NLN are more or less the same, except ILN has a trId at the start, and NLN has a capability field at the end. Does ILN still exist BTW? */ if( cmd[0][1] == 'I' ) cmd ++; else cap = atoi( cmd[4] ); handle = msn_normalize_handle( cmd[2] ); if( strcmp( handle, ic->acc->user ) == 0 ) return 1; /* That's me! */ http_decode( cmd[3] ); imcb_rename_buddy( ic, handle, cmd[3] ); st = msn_away_state_by_code( cmd[1] ); if( !st ) { /* FIXME: Warn/Bomb about unknown away state? */ st = msn_away_state_list + 1; } imcb_buddy_status( ic, handle, OPT_LOGGED_IN | ( st != msn_away_state_list ? OPT_AWAY : 0 ) | ( cap & 1 ? OPT_MOBILE : 0 ), st->name, NULL ); msn_sb_stop_keepalives( msn_sb_by_handle( ic, handle ) ); } else if( strcmp( cmd[0], "FLN" ) == 0 ) { const char *handle; if( cmd[1] == NULL ) return 1; handle = msn_normalize_handle( cmd[1] ); imcb_buddy_status( ic, handle, 0, NULL, NULL ); msn_sb_start_keepalives( msn_sb_by_handle( ic, handle ), TRUE ); } else if( strcmp( cmd[0], "RNG" ) == 0 ) { struct msn_switchboard *sb; char *server; int session, port; if( num_parts < 7 ) { imcb_error( ic, "Syntax error" ); imc_logout( ic, TRUE ); return( 0 ); } session = atoi( cmd[1] ); server = strchr( cmd[2], ':' ); if( !server ) { imcb_error( ic, "Syntax error" ); imc_logout( ic, TRUE ); return( 0 ); } *server = 0; port = atoi( server + 1 ); server = cmd[2]; if( strcmp( cmd[3], "CKI" ) != 0 ) { imcb_error( ic, "Unknown authentication method for switchboard" ); imc_logout( ic, TRUE ); return( 0 ); } debug( "Got a call from %s (session %d). Key = %s", cmd[5], session, cmd[4] ); if( ( sb = msn_sb_create( ic, server, port, cmd[4], session ) ) == NULL ) { /* Although this isn't strictly fatal for the NS connection, it's definitely something serious (we ran out of file descriptors?). */ imcb_error( ic, "Could not create new switchboard" ); imc_logout( ic, TRUE ); return( 0 ); } else { sb->who = g_strdup( msn_normalize_handle( cmd[5] ) ); } } else if( strcmp( cmd[0], "OUT" ) == 0 ) { int allow_reconnect = TRUE; if( cmd[1] && strcmp( cmd[1], "OTH" ) == 0 ) { imcb_error( ic, "Someone else logged in with your account" ); allow_reconnect = FALSE; } else if( cmd[1] && strcmp( cmd[1], "SSD" ) == 0 ) { imcb_error( ic, "Terminating session because of server shutdown" ); } else { imcb_error( ic, "Session terminated by remote server (%s)", cmd[1] ? cmd[1] : "reason unknown)" ); } imc_logout( ic, allow_reconnect ); return( 0 ); } else if( strcmp( cmd[0], "IPG" ) == 0 ) { imcb_error( ic, "Received IPG command, we don't handle them yet." ); handler->msglen = atoi( cmd[1] ); if( handler->msglen <= 0 ) { imcb_error( ic, "Syntax error" ); imc_logout( ic, TRUE ); return( 0 ); } } #if 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 ); } } } #endif else if( strcmp( cmd[0], "GCF" ) == 0 ) { /* Coming up is cmd[2] bytes of stuff we're supposed to censore. Meh. */ handler->msglen = atoi( cmd[2] ); } else if( strcmp( cmd[0], "UBX" ) == 0 ) { /* Status message. */ if( num_parts >= 3 ) handler->msglen = atoi( cmd[2] ); } else if( strcmp( cmd[0], "NOT" ) == 0 ) { /* Some kind of notification, poorly documented but apparently used to announce address book changes. */ if( num_parts >= 2 ) handler->msglen = atoi( cmd[1] ); } else if( strcmp( cmd[0], "UBM" ) == 0 ) { if( num_parts >= 7 ) handler->msglen = atoi( cmd[6] ); } else if( strcmp( cmd[0], "QNG" ) == 0 ) { ic->flags |= OPT_PONGED; } else if( isdigit( cmd[0][0] ) ) { int num = atoi( cmd[0] ); const struct msn_status_code *err = msn_status_by_number( num ); imcb_error( ic, "Error reported by MSN server: %s", err->text ); if( err->flags & STATUS_FATAL ) { imc_logout( ic, TRUE ); return( 0 ); } /* Oh yes, errors can have payloads too now. Discard them for now. */ if( num_parts >= 3 ) handler->msglen = atoi( cmd[2] ); } else { /* debug( "Received unknown command from main server: %s", cmd[0] ); */ } return( 1 ); } static int msn_ns_message( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int num_parts ) { struct im_connection *ic = handler->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 = get_rfc822_header( 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 = get_rfc822_header( body, "Type:", blen ); arg1 = get_rfc822_header( body, "Arg1:", blen ); if( mtype && strcmp( mtype, "1" ) == 0 ) { if( arg1 ) imcb_log( ic, "The server is going down for maintenance in %s minutes.", arg1 ); } g_free( arg1 ); 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 ) { if( set_getbool( &ic->acc->set, "mail_notifications" ) ) { char *inbox = get_rfc822_header( body, "Inbox-Unread:", blen ); char *folders = get_rfc822_header( body, "Folders-Unread:", blen ); if( inbox && folders ) imcb_log( ic, "INBOX contains %s new messages, plus %s messages in other folders.", inbox, folders ); g_free( inbox ); g_free( folders ); } } else if( g_strncasecmp( ct, "text/x-msmsgsemailnotification", 30 ) == 0 ) { if( set_getbool( &ic->acc->set, "mail_notifications" ) ) { char *from = get_rfc822_header( body, "From-Addr:", blen ); char *fromname = get_rfc822_header( body, "From:", blen ); if( from && fromname ) imcb_log( ic, "Received an e-mail message from %s <%s>.", fromname, from ); g_free( from ); g_free( fromname ); } } else if( g_strncasecmp( ct, "text/x-msmsgsactivemailnotification", 35 ) == 0 ) { } else if( g_strncasecmp( ct, "text/x-msmsgsinitialmdatanotification", 37 ) == 0 || g_strncasecmp( ct, "text/x-msmsgsoimnotification", 28 ) == 0 ) { /* We received an offline message. Or at least notification that there is one waiting for us. Fetching the message(s) and purging them from the server is a lot of SOAPy work not worth doing IMHO. Also I thought it was possible to have the notification server send them directly, I was pretty sure I saw Pidgin do it.. At least give a notification for now, seems like a reasonable thing to do. Only problem is, they'll keep coming back at login time until you read them using a different client. :-( */ char *xml = get_rfc822_header( body, "Mail-Data:", blen ); struct xt_node *md, *m; if( !xml ) return 1; md = xt_from_string( xml, 0 ); if( !md ) return 1; for( m = md->children; ( m = xt_find_node( m, "M" ) ); m = m->next ) { struct xt_node *e = xt_find_node( m->children, "E" ); struct xt_node *rt = xt_find_node( m->children, "RT" ); struct tm tp; time_t msgtime = 0; if( !e || !e->text ) continue; memset( &tp, 0, sizeof( tp ) ); if( rt && rt->text && sscanf( rt->text, "%4d-%2d-%2dT%2d:%2d:%2d.", &tp.tm_year, &tp.tm_mon, &tp.tm_mday, &tp.tm_hour, &tp.tm_min, &tp.tm_sec ) == 6 ) { tp.tm_year -= 1900; tp.tm_mon --; msgtime = mktime_utc( &tp ); } imcb_buddy_msg( ic, e->text, "<< \002BitlBee\002 - Received offline message. BitlBee can't show these. >>", 0, msgtime ); } g_free( xml ); xt_free_node( md ); } else { debug( "Can't handle %s packet from notification server", ct ); } g_free( ct ); } } else if( strcmp( cmd[0], "UBX" ) == 0 ) { struct xt_node *ubx, *psm; char *psm_text = NULL; ubx = xt_from_string( msg, msglen ); if( ubx && strcmp( ubx->name, "Data" ) == 0 && ( psm = xt_find_node( ubx->children, "PSM" ) ) ) psm_text = psm->text; imcb_buddy_status_msg( ic, msn_normalize_handle( cmd[1] ), psm_text ); xt_free_node( ubx ); } else if( strcmp( cmd[0], "ADL" ) == 0 ) { struct xt_node *adl, *d, *c; if( !( adl = xt_from_string( msg, msglen ) ) ) return 1; for( d = adl->children; d; d = d->next ) { char *dn; if( strcmp( d->name, "d" ) != 0 || ( dn = xt_find_attr( d, "n" ) ) == NULL ) continue; for( c = d->children; c; c = c->next ) { bee_user_t *bu; struct msn_buddy_data *bd; char *cn, *handle, *f, *l; int flags; if( strcmp( c->name, "c" ) != 0 || ( l = xt_find_attr( c, "l" ) ) == NULL || ( cn = xt_find_attr( c, "n" ) ) == NULL ) continue; /* FIXME: Use "t" here, guess I should just add it as a prefix like elsewhere in the protocol. */ handle = g_strdup_printf( "%s@%s", cn, dn ); if( !( ( bu = bee_user_by_handle( ic->bee, ic, handle ) ) || ( bu = bee_user_new( ic->bee, ic, handle, 0 ) ) ) ) { g_free( handle ); continue; } g_free( handle ); bd = bu->data; if( ( f = xt_find_attr( c, "f" ) ) ) { http_decode( f ); imcb_rename_buddy( ic, bu->handle, f ); } flags = atoi( l ) & 15; if( bd->flags != flags ) { bd->flags = flags; msn_buddy_ask( bu ); } } } } else if( strcmp( cmd[0], "UBM" ) == 0 ) { /* This one will give us msgs from federated networks. Technically it should also get us offline messages, but I don't know how I can signal MSN servers to use it. */ char *ct, *handle; if( strcmp( cmd[1], ic->acc->user ) == 0 ) { /* With MPOP, you'll get copies of your own msgs from other sessions. Discard those at least for now. */ return 1; } ct = get_rfc822_header( msg, "Content-Type", msglen ); if( strncmp( ct, "text/plain", 10 ) != 0 ) { /* Typing notification or something? */ g_free( ct ); return 1; } if( strcmp( cmd[2], "1" ) != 0 ) handle = g_strdup_printf( "%s:%s", cmd[2], cmd[1] ); else handle = g_strdup( cmd[1] ); imcb_buddy_msg( ic, handle, body, 0, 0 ); g_free( handle ); } return 1; } void msn_auth_got_passport_token( struct im_connection *ic, const char *token, const char *error ) { struct msn_data *md; /* Dead connection? */ if( g_slist_find( msn_connections, ic ) == NULL ) return; md = ic->proto_data; if( token ) { msn_ns_write( ic, -1, "USR %d SSO S %s %s {%s}\r\n", ++md->trId, md->tokens[0], token, md->uuid ); } else { imcb_error( ic, "Error during Passport authentication: %s", error ); imc_logout( ic, TRUE ); } } void msn_auth_got_contact_list( struct im_connection *ic ) { struct msn_data *md; /* Dead connection? */ if( g_slist_find( msn_connections, ic ) == NULL ) return; md = ic->proto_data; msn_ns_write( ic, -1, "BLP %d %s\r\n", ++md->trId, "BL" ); } static gboolean msn_ns_send_adl_1( gpointer key, gpointer value, gpointer data ) { struct xt_node *adl = data, *d, *c; struct bee_user *bu = value; struct msn_buddy_data *bd = bu->data; struct msn_data *md = bu->ic->proto_data; char handle[strlen(bu->handle)]; char *domain; char l[4]; if( ( bd->flags & 7 ) == 0 || ( bd->flags & MSN_BUDDY_ADL_SYNCED ) ) return FALSE; strcpy( handle, bu->handle ); if( ( domain = strchr( handle, '@' ) ) == NULL ) /* WTF */ return FALSE; *domain = '\0'; domain ++; if( ( d = adl->children ) == NULL || g_strcasecmp( xt_find_attr( d, "n" ), domain ) != 0 ) { d = xt_new_node( "d", NULL, NULL ); xt_add_attr( d, "n", domain ); xt_insert_child( adl, d ); } g_snprintf( l, sizeof( l ), "%d", bd->flags & 7 ); c = xt_new_node( "c", NULL, NULL ); xt_add_attr( c, "n", handle ); xt_add_attr( c, "l", l ); xt_add_attr( c, "t", "1" ); /* FIXME: Network type, i.e. 32 for Y!MSG */ xt_insert_child( d, c ); /* Do this in batches of 100. */ bd->flags |= MSN_BUDDY_ADL_SYNCED; return (--md->adl_todo % 140) == 0; } static void msn_ns_send_adl( struct im_connection *ic ) { struct xt_node *adl; struct msn_data *md = ic->proto_data; char *adls; adl = xt_new_node( "ml", NULL, NULL ); xt_add_attr( adl, "l", "1" ); g_tree_foreach( md->domaintree, msn_ns_send_adl_1, adl ); if( adl->children == NULL ) { /* This tells the caller that we're done now. */ md->adl_todo = -1; xt_free_node( adl ); return; } adls = xt_to_string( adl ); xt_free_node( adl ); msn_ns_write( ic, -1, "ADL %d %zd\r\n%s", ++md->trId, strlen( adls ), adls ); g_free( adls ); } static void msn_ns_send_adl_start( struct im_connection *ic ) { struct msn_data *md; GSList *l; /* Dead connection? */ if( g_slist_find( msn_connections, ic ) == NULL ) return; md = ic->proto_data; md->adl_todo = 0; for( l = ic->bee->users; l; l = l->next ) { bee_user_t *bu = l->data; struct msn_buddy_data *bd = bu->data; if( bu->ic != ic || ( bd->flags & 7 ) == 0 ) continue; bd->flags &= ~MSN_BUDDY_ADL_SYNCED; md->adl_todo++; } msn_ns_send_adl( ic ); } int msn_ns_finish_login( struct im_connection *ic ) { struct msn_data *md = ic->proto_data; if( ic->flags & OPT_LOGGED_IN ) return 1; if( md->adl_todo < 0 ) md->flags |= MSN_DONE_ADL; if( ( md->flags & MSN_DONE_ADL ) && ( md->flags & MSN_GOT_PROFILE ) ) { if( md->flags & MSN_EMAIL_UNVERIFIED ) imcb_connected( ic ); else return msn_ns_set_display_name( ic, set_getstr( &ic->acc->set, "display_name" ) ); } return 1; } int msn_ns_sendmessage( struct im_connection *ic, bee_user_t *bu, const char *text ) { struct msn_data *md = ic->proto_data; int type = 0; char *buf, *handle; if( strncmp( text, "\r\r\r", 3 ) == 0 ) /* Err. Shouldn't happen but I guess it can. Don't send others any of the "SHAKE THAT THING" messages. :-D */ return 1; /* This might be a federated contact. Get its network number, prefixed to bu->handle with a colon. Default is 1. */ for( handle = bu->handle; isdigit( *handle ); handle ++ ) type = type * 10 + *handle - '0'; if( *handle == ':' ) handle ++; else type = 1; buf = g_strdup_printf( "%s%s", MSN_MESSAGE_HEADERS, text ); if( msn_ns_write( ic, -1, "UUM %d %s %d %d %zd\r\n%s", ++md->trId, handle, type, 1, /* type == IM (not nudge/typing) */ strlen( buf ), buf ) ) return 1; else return 0; } void msn_ns_oim_send_queue( struct im_connection *ic, GSList **msgq ) { GSList *l; for( l = *msgq; l; l = l->next ) { struct msn_message *m = l->data; bee_user_t *bu = bee_user_by_handle( ic->bee, ic, m->who ); if( bu ) if( !msn_ns_sendmessage( ic, bu, m->text ) ) return; } while( *msgq != NULL ) { struct msn_message *m = (*msgq)->data; g_free( m->who ); g_free( m->text ); g_free( m ); *msgq = g_slist_remove( *msgq, m ); } } bitlbee-3.2.1/protocols/msn/msn_util.c0000644000175000017500000003125412245474076017342 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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 "md5.h" #include "soap.h" #include static char *adlrml_entry( const char *handle_, msn_buddy_flags_t list ) { char *domain, handle[strlen(handle_)+1]; strcpy( handle, handle_ ); if( ( domain = strchr( handle, '@' ) ) ) *(domain++) = '\0'; else return NULL; return g_markup_printf_escaped( "", domain, handle, list ); } int msn_buddy_list_add( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *realname, const char *group ) { struct msn_data *md = ic->proto_data; char groupid[8]; bee_user_t *bu; struct msn_buddy_data *bd; char *adl; *groupid = '\0'; #if 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; } 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[strlen(group)+1]; strcpy( groupname, group ); http_encode( groupname ); g_snprintf( buf, sizeof( buf ), "ADG %d %s %d\r\n", ++md->trId, groupname, 0 ); 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; } } } #endif if( !( ( bu = bee_user_by_handle( ic->bee, ic, who ) ) || ( bu = bee_user_new( ic->bee, ic, who, 0 ) ) ) || !( bd = bu->data ) || bd->flags & list ) return 1; bd->flags |= list; if( list == MSN_BUDDY_FL ) msn_soap_ab_contact_add( ic, bu ); else msn_soap_memlist_edit( ic, who, TRUE, list ); if( ( adl = adlrml_entry( who, list ) ) ) { int st = msn_ns_write( ic, -1, "ADL %d %zd\r\n%s", ++md->trId, strlen( adl ), adl ); g_free( adl ); return st; } return 1; } int msn_buddy_list_remove( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *group ) { struct msn_data *md = ic->proto_data; char groupid[8]; bee_user_t *bu; struct msn_buddy_data *bd; char *adl; *groupid = '\0'; #if 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; } } #endif if( !( bu = bee_user_by_handle( ic->bee, ic, who ) ) || !( bd = bu->data ) || !( bd->flags & list ) ) return 1; bd->flags &= ~list; if( list == MSN_BUDDY_FL ) msn_soap_ab_contact_del( ic, bu ); else msn_soap_memlist_edit( ic, who, FALSE, list ); if( ( adl = adlrml_entry( who, list ) ) ) { int st = msn_ns_write( ic, -1, "RML %d %zd\r\n%s", ++md->trId, strlen( adl ), adl ); g_free( adl ); return st; } return 1; } struct msn_buddy_ask_data { struct im_connection *ic; char *handle; char *realname; }; static void msn_buddy_ask_yes( void *data ) { struct msn_buddy_ask_data *bla = data; msn_buddy_list_add( bla->ic, MSN_BUDDY_AL, bla->handle, bla->realname, NULL ); imcb_ask_add( bla->ic, bla->handle, NULL ); g_free( bla->handle ); g_free( bla->realname ); g_free( bla ); } static void msn_buddy_ask_no( void *data ) { struct msn_buddy_ask_data *bla = data; msn_buddy_list_add( bla->ic, MSN_BUDDY_BL, bla->handle, bla->realname, NULL ); g_free( bla->handle ); g_free( bla->realname ); g_free( bla ); } void msn_buddy_ask( bee_user_t *bu ) { struct msn_buddy_ask_data *bla; struct msn_buddy_data *bd = bu->data; char buf[1024]; if( ( bd->flags & 30 ) != 8 && ( bd->flags & 30 ) != 16 ) return; bla = g_new0( struct msn_buddy_ask_data, 1 ); bla->ic = bu->ic; bla->handle = g_strdup( bu->handle ); bla->realname = g_strdup( bu->fullname ); g_snprintf( buf, sizeof( buf ), "The user %s (%s) wants to add you to his/her buddy list.", bu->handle, bu->fullname ); imcb_ask( bu->ic, buf, bla, msn_buddy_ask_yes, msn_buddy_ask_no ); } /* *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 ); if( getenv( "BITLBEE_DEBUG" ) ) { write( 2, "->C:", 4 ); write( 2, h->rxq + h->rxlen - st, st ); } 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, 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, 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 ); } 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 ) return; m = l->data; ret = g_string_sized_new( 1024 ); g_string_printf( ret, "Warning: Cleaning up MSN (switchboard) connection with unsent " "messages to %s:", m->who ? m->who : "unknown recipient" ); while( l ) { m = l->data; 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 ); g_free( m ); l = l->next; } g_slist_free( *list ); *list = NULL; if( n > 0 ) imcb_log( ic, "%s", ret->str ); g_string_free( ret, TRUE ); } /* Copied and heavily modified from http://tmsnc.sourceforge.net/chl.c */ char *msn_p11_challenge( char *challenge ) { char *output, buf[256]; md5_state_t md5c; unsigned char md5Hash[16], *newHash; unsigned int *md5Parts, *chlStringParts, newHashParts[5]; long long nHigh = 0, nLow = 0; int i, n; /* Create the MD5 hash */ md5_init(&md5c); md5_append(&md5c, (unsigned char*) challenge, strlen(challenge)); md5_append(&md5c, (unsigned char*) MSNP11_PROD_KEY, strlen(MSNP11_PROD_KEY)); md5_finish(&md5c, md5Hash); /* Split it into four integers */ md5Parts = (unsigned int *)md5Hash; for (i = 0; i < 4; i ++) { md5Parts[i] = GUINT32_TO_LE(md5Parts[i]); /* & each integer with 0x7FFFFFFF */ /* and save one unmodified array for later */ newHashParts[i] = md5Parts[i]; md5Parts[i] &= 0x7FFFFFFF; } /* make a new string and pad with '0' */ n = g_snprintf(buf, sizeof(buf)-5, "%s%s00000000", challenge, MSNP11_PROD_ID); /* truncate at an 8-byte boundary */ buf[n&=~7] = '\0'; /* split into integers */ chlStringParts = (unsigned int *)buf; /* this is magic */ for (i = 0; i < (n / 4) - 1; i += 2) { long long temp; chlStringParts[i] = GUINT32_TO_LE(chlStringParts[i]); chlStringParts[i+1] = GUINT32_TO_LE(chlStringParts[i+1]); temp = (md5Parts[0] * (((0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF)+nHigh) + md5Parts[1])%0x7FFFFFFF; nHigh = (md5Parts[2] * (((long long)chlStringParts[i+1]+temp) % 0x7FFFFFFF) + md5Parts[3]) % 0x7FFFFFFF; nLow = nLow + nHigh + temp; } nHigh = (nHigh+md5Parts[1]) % 0x7FFFFFFF; nLow = (nLow+md5Parts[3]) % 0x7FFFFFFF; newHashParts[0] ^= nHigh; newHashParts[1] ^= nLow; newHashParts[2] ^= nHigh; newHashParts[3] ^= nLow; /* swap more bytes if big endian */ for (i = 0; i < 4; i ++) newHashParts[i] = GUINT32_TO_LE(newHashParts[i]); /* make a string of the parts */ newHash = (unsigned char *)newHashParts; /* convert to hexadecimal */ output = g_new(char, 33); for (i = 0; i < 16; i ++) sprintf(output + i * 2, "%02x", newHash[i]); return output; } gint msn_domaintree_cmp( gconstpointer a_, gconstpointer b_ ) { const char *a = a_, *b = b_; gint ret; if( !( a = strchr( a, '@' ) ) || !( b = strchr( b, '@' ) ) || ( ret = strcmp( a, b ) ) == 0 ) ret = strcmp( a_, b_ ); return ret; } struct msn_group *msn_group_by_name( struct im_connection *ic, const char *name ) { struct msn_data *md = ic->proto_data; GSList *l; for( l = md->groups; l; l = l->next ) { struct msn_group *mg = l->data; if( g_strcasecmp( mg->name, name ) == 0 ) return mg; } return NULL; } struct msn_group *msn_group_by_id( struct im_connection *ic, const char *id ) { struct msn_data *md = ic->proto_data; GSList *l; for( l = md->groups; l; l = l->next ) { struct msn_group *mg = l->data; if( g_strcasecmp( mg->id, id ) == 0 ) return mg; } return NULL; } int msn_ns_set_display_name( struct im_connection *ic, const char *value ) { struct msn_data *md = ic->proto_data; char fn[strlen(value)*3+1]; strcpy( fn, value ); http_encode( fn ); /* Note: We don't actually know if the server accepted the new name, and won't give proper feedback yet if it doesn't. */ return msn_ns_write( ic, -1, "PRP %d MFN %s\r\n", ++md->trId, fn ); } const char *msn_normalize_handle( const char *handle ) { if( strncmp( handle, "1:", 2 ) == 0 ) return handle + 2; else return handle; } bitlbee-3.2.1/protocols/msn/msn.c0000644000175000017500000003044312245474076016304 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 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 "soap.h" #include "msn.h" int msn_chat_id; GSList *msn_connections; GSList *msn_switchboards; static char *set_eval_display_name( set_t *set, char *value ); static void msn_init( account_t *acc ) { set_t *s; s = set_add( &acc->set, "display_name", NULL, set_eval_display_name, acc ); s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY; set_add( &acc->set, "mail_notifications", "false", set_eval_bool, acc ); set_add( &acc->set, "switchboard_keepalives", "false", set_eval_bool, acc ); acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE | ACC_FLAG_HANDLE_DOMAINS; } static void msn_login( account_t *acc ) { struct im_connection *ic = imcb_new( acc ); struct msn_data *md = g_new0( struct msn_data, 1 ); ic->proto_data = md; ic->flags |= OPT_PONGS | OPT_PONGED; if( strchr( acc->user, '@' ) == NULL ) { imcb_error( ic, "Invalid account name" ); imc_logout( ic, FALSE ); return; } md->ic = ic; md->away_state = msn_away_state_list; md->domaintree = g_tree_new( msn_domaintree_cmp ); md->ns->fd = -1; msn_connections = g_slist_prepend( msn_connections, ic ); imcb_log( ic, "Connecting" ); msn_ns_connect( ic, md->ns, MSN_NS_HOST, MSN_NS_PORT ); } static void msn_logout( struct im_connection *ic ) { struct msn_data *md = ic->proto_data; GSList *l; int i; if( md ) { /** Disabling MSN ft support for now. while( md->filetransfers ) { imcb_file_canceled( md->filetransfers->data, "Closing connection" ); } */ msn_ns_close( md->ns ); while( md->switchboards ) msn_sb_destroy( md->switchboards->data ); msn_msgq_purge( ic, &md->msgq ); msn_soapq_flush( ic, FALSE ); for( i = 0; i < sizeof( md->tokens ) / sizeof( md->tokens[0] ); i ++ ) g_free( md->tokens[i] ); g_free( md->lock_key ); g_free( md->pp_policy ); g_free( md->uuid ); while( md->groups ) { struct msn_group *mg = md->groups->data; g_free( mg->id ); g_free( mg->name ); g_free( mg ); md->groups = g_slist_remove( md->groups, mg ); } g_free( md->profile_rid ); if( md->domaintree ) g_tree_destroy( md->domaintree ); md->domaintree = NULL; 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 ); } for( l = ic->permit; l; l = l->next ) g_free( l->data ); g_slist_free( ic->permit ); for( l = ic->deny; l; l = l->next ) g_free( l->data ); g_slist_free( ic->deny ); msn_connections = g_slist_remove( msn_connections, ic ); } static int msn_buddy_msg( struct im_connection *ic, char *who, char *message, int away ) { struct bee_user *bu = bee_user_by_handle( ic->bee, ic, who ); struct msn_buddy_data *bd = bu ? bu->data : NULL; struct msn_switchboard *sb; #ifdef DEBUG if( strcmp( who, "raw" ) == 0 ) { msn_ns_write( ic, -1, "%s\r\n", message ); } else #endif if( bd && bd->flags & MSN_BUDDY_FED ) { msn_ns_sendmessage( ic, bu, message ); } else if( ( sb = msn_sb_by_handle( ic, who ) ) ) { return( msn_sb_sendmessage( sb, message ) ); } else { struct msn_message *m; /* 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 ); return msn_sb_write_msg( ic, m ); } return( 0 ); } static GList *msn_away_states( struct im_connection *ic ) { static GList *l = NULL; int i; if( l == NULL ) for( i = 0; *msn_away_state_list[i].code; i ++ ) if( *msn_away_state_list[i].name ) l = g_list_append( l, (void*) msn_away_state_list[i].name ); return l; } static void msn_set_away( struct im_connection *ic, char *state, char *message ) { char *uux; struct msn_data *md = ic->proto_data; 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; if( !msn_ns_write( ic, -1, "CHG %d %s %d:%02d\r\n", ++md->trId, md->away_state->code, MSN_CAP1, MSN_CAP2 ) ) return; uux = g_markup_printf_escaped( "%d:%02d" "", MSN_CAP1, MSN_CAP2 ); msn_ns_write( ic, -1, "UUX %d %zd\r\n%s", ++md->trId, strlen( uux ), uux ); g_free( uux ); uux = g_markup_printf_escaped( "%s" "%s%d" "%s", md->uuid, strcmp( md->away_state->code, "IDL" ) ? "false" : "true", 1, /* ? */ md->away_state->code ); msn_ns_write( ic, -1, "UUX %d %zd\r\n%s", ++md->trId, strlen( uux ), uux ); g_free( uux ); uux = g_markup_printf_escaped( "%s" "" "%s", message ? message : "", md->uuid ); msn_ns_write( ic, -1, "UUX %d %zd\r\n%s", ++md->trId, strlen( uux ), uux ); g_free( uux ); } static void msn_get_info(struct im_connection *ic, char *who) { /* Just make an URL and let the user fetch the info */ imcb_log( ic, "%s\n%s: %s%s", _("User Info"), _("For now, fetch yourself"), PROFILE_URL, who ); } static void msn_add_buddy( struct im_connection *ic, char *who, char *group ) { struct bee_user *bu = bee_user_by_handle( ic->bee, ic, who ); msn_buddy_list_add( ic, MSN_BUDDY_FL, who, who, group ); if( bu && bu->group ) msn_buddy_list_remove( ic, MSN_BUDDY_FL, who, bu->group->name ); } static void msn_remove_buddy( struct im_connection *ic, char *who, char *group ) { msn_buddy_list_remove( ic, MSN_BUDDY_FL, who, NULL ); } static void msn_chat_msg( struct groupchat *c, char *message, int flags ) { struct msn_switchboard *sb = msn_sb_by_chat( c ); if( sb ) msn_sb_sendmessage( sb, message ); /* FIXME: Error handling (although this can't happen unless something's already severely broken) disappeared here! */ } static void msn_chat_invite( struct groupchat *c, char *who, char *message ) { struct msn_switchboard *sb = msn_sb_by_chat( c ); if( sb ) msn_sb_write( sb, "CAL %d %s\r\n", ++sb->trId, who ); } static void msn_chat_leave( struct groupchat *c ) { struct msn_switchboard *sb = msn_sb_by_chat( c ); if( sb ) msn_sb_write( sb, "OUT\r\n" ); } 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 ) ) ) { debug( "Converting existing switchboard to %s to a groupchat", who ); return msn_sb_to_chat( sb ); } else { struct msn_message *m; /* 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 ); msn_sb_write_msg( ic, m ); return c; } } static void msn_keepalive( struct im_connection *ic ) { msn_ns_write( ic, -1, "PNG\r\n" ); } static void msn_add_permit( struct im_connection *ic, char *who ) { msn_buddy_list_add( ic, MSN_BUDDY_AL, who, who, NULL ); } static void msn_rem_permit( struct im_connection *ic, char *who ) { msn_buddy_list_remove( ic, MSN_BUDDY_AL, who, NULL ); } static void msn_add_deny( struct im_connection *ic, char *who ) { struct msn_switchboard *sb; msn_buddy_list_add( ic, MSN_BUDDY_BL, who, who, NULL ); /* If there's still a conversation with this person, close it. */ if( ( sb = msn_sb_by_handle( ic, who ) ) ) { msn_sb_destroy( sb ); } } static void msn_rem_deny( struct im_connection *ic, char *who ) { msn_buddy_list_remove( ic, MSN_BUDDY_BL, who, NULL ); } static int msn_send_typing( struct im_connection *ic, char *who, int 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; } static char *set_eval_display_name( set_t *set, char *value ) { account_t *acc = set->data; struct im_connection *ic = acc->ic; struct msn_data *md = ic->proto_data; if( md->flags & MSN_EMAIL_UNVERIFIED ) imcb_log( ic, "Warning: Your e-mail address is unverified. MSN doesn't allow " "changing your display name until your e-mail address is verified." ); if( md->flags & MSN_GOT_PROFILE_DN ) msn_soap_profile_set_dn( ic, value ); else msn_soap_addressbook_set_display_name( ic, value ); return msn_ns_set_display_name( ic, value ) ? value : NULL; } static void msn_buddy_data_add( bee_user_t *bu ) { struct msn_data *md = bu->ic->proto_data; struct msn_buddy_data *bd; char *handle; bd = bu->data = g_new0( struct msn_buddy_data, 1 ); g_tree_insert( md->domaintree, bu->handle, bu ); for( handle = bu->handle; isdigit( *handle ); handle ++ ); if( *handle == ':' ) { /* Pass a nick hint so hopefully the stupid numeric prefix won't show up to the user. */ char *s = strchr( ++handle, '@' ); if( s ) { handle = g_strndup( handle, s - handle ); imcb_buddy_nick_hint( bu->ic, bu->handle, handle ); g_free( handle ); } bd->flags |= MSN_BUDDY_FED; } } static void msn_buddy_data_free( bee_user_t *bu ) { struct msn_data *md = bu->ic->proto_data; struct msn_buddy_data *bd = bu->data; g_free( bd->cid ); g_free( bd ); g_tree_remove( md->domaintree, bu->handle ); } GList *msn_buddy_action_list( bee_user_t *bu ) { static GList *ret = NULL; if( ret == NULL ) { static const struct buddy_action ba[2] = { { "NUDGE", "Draw attention" }, }; ret = g_list_prepend( ret, (void*) ba + 0 ); } return ret; } void *msn_buddy_action( struct bee_user *bu, const char *action, char * const args[], void *data ) { if( g_strcasecmp( action, "NUDGE" ) == 0 ) msn_buddy_msg( bu->ic, bu->handle, NUDGE_MESSAGE, 0 ); return NULL; } void msn_initmodule() { struct prpl *ret = g_new0(struct prpl, 1); ret->name = "msn"; ret->mms = 1409; /* this guess taken from libotr UPGRADING file */ ret->login = msn_login; ret->init = msn_init; ret->logout = msn_logout; ret->buddy_msg = msn_buddy_msg; ret->away_states = msn_away_states; ret->set_away = msn_set_away; ret->get_info = msn_get_info; ret->add_buddy = msn_add_buddy; ret->remove_buddy = msn_remove_buddy; ret->chat_msg = msn_chat_msg; ret->chat_invite = msn_chat_invite; ret->chat_leave = msn_chat_leave; ret->chat_with = msn_chat_with; 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; ret->handle_cmp = g_strcasecmp; ret->buddy_data_add = msn_buddy_data_add; ret->buddy_data_free = msn_buddy_data_free; ret->buddy_action_list = msn_buddy_action_list; ret->buddy_action = msn_buddy_action; //ret->transfer_request = msn_ftp_transfer_request; register_protocol(ret); } bitlbee-3.2.1/protocols/msn/tables.c0000644000175000017500000001644712245474076016771 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2010 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" const struct msn_away_state msn_away_state_list[] = { { "NLN", "" }, { "AWY", "Away" }, { "BSY", "Busy" }, { "IDL", "Idle" }, { "BRB", "Be Right Back" }, { "PHN", "On the Phone" }, { "LUN", "Out to Lunch" }, { "HDN", "Hidden" }, { "", "" } }; const struct msn_away_state *msn_away_state_by_code( char *code ) { int i; for( i = 0; *msn_away_state_list[i].code; i ++ ) if( g_strcasecmp( msn_away_state_list[i].code, code ) == 0 ) return( msn_away_state_list + i ); return NULL; } const struct msn_away_state *msn_away_state_by_name( char *name ) { int i; for( i = 0; *msn_away_state_list[i].code; i ++ ) if( g_strcasecmp( msn_away_state_list[i].name, name ) == 0 ) return( msn_away_state_list + i ); return NULL; } const 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", STATUS_SB_IM_SPARE }, { 209, "Forbidden nickname", 0 }, { 210, "Buddy list too long", 0 }, { 215, "Handle is already in list", 0 }, { 216, "Handle is not in list", STATUS_SB_IM_SPARE }, { 217, "Person is off-line or non-existent", STATUS_SB_IM_SPARE }, { 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 }, { 240, "ADL/RML command with corrupted payload", STATUS_FATAL }, { 241, "ADL/RML command with invalid modification", 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", STATUS_SB_IM_SPARE }, { 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_SB_FATAL | 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 } }; const 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 ); } bitlbee-3.2.1/protocols/msn/msn.h0000644000175000017500000002107612245474076016313 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 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 */ #ifndef _MSN_H #define _MSN_H /* Some hackish magicstrings to make special-purpose messages/switchboards. */ #define TYPING_NOTIFICATION_MESSAGE "\r\r\rBEWARE, ME R TYPINK MESSAGE!!!!\r\r\r" #define NUDGE_MESSAGE "\r\r\rSHAKE THAT THING\r\r\r" #define GROUPCHAT_SWITCHBOARD_MESSAGE "\r\r\rME WANT TALK TO MANY PEOPLE\r\r\r" #define SB_KEEPALIVE_MESSAGE "\r\r\rDONT HANG UP ON ME!\r\r\r" #ifdef DEBUG_MSN #define debug( text... ) imcb_log( ic, text ); #else #define debug( text... ) #endif /* This should be MSN Messenger 7.0.0813 #define MSNP11_PROD_KEY "CFHUR$52U_{VIX5T" #define MSNP11_PROD_ID "PROD0101{0RM?UBW" */ #define MSN_NS_HOST "messenger.hotmail.com" #define MSN_NS_PORT 1863 /* Some other version. #define MSNP11_PROD_KEY "O4BG@C7BWLYQX?5G" #define MSNP11_PROD_ID "PROD01065C%ZFN6F" */ /* <= BitlBee 3.0.5 #define MSNP11_PROD_KEY "ILTXC!4IXB5FB*PX" #define MSNP11_PROD_ID "PROD0119GSJUC$18" */ #define MSNP11_PROD_KEY "C1BX{V4W}Q3*10SM" #define MSNP11_PROD_ID "PROD0120PW!CCV9@" #define MSNP_VER "MSNP18" #define MSNP_BUILD "14.0.8117.416" #define MSN_SB_NEW -24062002 #define MSN_CAP1 0xC000 #define MSN_CAP2 0x0000 #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 MSN_NUDGE_HEADERS "MIME-Version: 1.0\r\n" \ "Content-Type: text/x-msnmsgr-datacast\r\n" \ "\r\n" \ "ID: 1\r\n" \ "\r\n" #define MSN_SB_KEEPALIVE_HEADERS "MIME-Version: 1.0\r\n" \ "Content-Type: text/x-ping\r\n" \ "\r\n\r\n" #define PROFILE_URL "http://members.msn.com/" typedef enum { MSN_GOT_PROFILE = 1, MSN_GOT_PROFILE_DN = 2, MSN_DONE_ADL = 4, MSN_REAUTHING = 8, MSN_EMAIL_UNVERIFIED = 16, } msn_flags_t; struct msn_handler_data { int fd, inpa; int rxlen; char *rxq; int msglen; char *cmd_text; /* Either ic or sb */ gpointer data; int (*exec_command) ( struct msn_handler_data *handler, char **cmd, int count ); int (*exec_message) ( struct msn_handler_data *handler, char *msg, int msglen, char **cmd, int count ); }; struct msn_data { struct im_connection *ic; struct msn_handler_data ns[1]; msn_flags_t flags; int trId; char *tokens[4]; char *lock_key, *pp_policy; char *uuid; GSList *msgq, *grpq, *soapq; GSList *switchboards; int sb_failures; time_t first_sb_failure; const struct msn_away_state *away_state; GSList *groups; char *profile_rid; /* Mostly used for sending the ADL command; since MSNP13 the client is responsible for downloading the contact list and then sending it to the MSNP server. */ GTree *domaintree; int adl_todo; }; struct msn_switchboard { struct im_connection *ic; /* The following two are also in the handler. TODO: Clean up. */ int fd; gint inp; struct msn_handler_data *handler; gint keepalive; int trId; int ready; int session; char *key; GSList *msgq; char *who; struct groupchat *chat; }; struct msn_away_state { 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_groupadd { char *who; char *group; }; typedef enum { MSN_BUDDY_FL = 1, /* Warning: FL,AL,BL *must* be 1,2,4. */ MSN_BUDDY_AL = 2, MSN_BUDDY_BL = 4, MSN_BUDDY_RL = 8, MSN_BUDDY_PL = 16, MSN_BUDDY_ADL_SYNCED = 256, MSN_BUDDY_FED = 512, } msn_buddy_flags_t; struct msn_buddy_data { char *cid; msn_buddy_flags_t flags; }; struct msn_group { char *name; char *id; }; /* Bitfield values for msn_status_code.flags */ #define STATUS_FATAL 1 #define STATUS_SB_FATAL 2 #define STATUS_SB_IM_SPARE 4 /* Make one-to-one conversation switchboard available again, invite failed. */ #define STATUS_SB_CHAT_SPARE 8 /* Same, but also for groupchats (not used yet). */ extern int msn_chat_id; extern const struct msn_away_state msn_away_state_list[]; extern const 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. */ extern GSList *msn_connections; extern GSList *msn_switchboards; /* ns.c */ int msn_ns_write( struct im_connection *ic, int fd, const char *fmt, ... ) G_GNUC_PRINTF( 3, 4 ); gboolean msn_ns_connect( struct im_connection *ic, struct msn_handler_data *handler, const char *host, int port ); void msn_ns_close( struct msn_handler_data *handler ); void msn_auth_got_passport_token( struct im_connection *ic, const char *token, const char *error ); void msn_auth_got_contact_list( struct im_connection *ic ); int msn_ns_finish_login( struct im_connection *ic ); int msn_ns_sendmessage( struct im_connection *ic, struct bee_user *bu, const char *text ); void msn_ns_oim_send_queue( struct im_connection *ic, GSList **msgq ); /* msn_util.c */ int msn_buddy_list_add( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *realname_, const char *group ); int msn_buddy_list_remove( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *group ); void msn_buddy_ask( bee_user_t *bu ); char **msn_linesplit( char *line ); int msn_handler( struct msn_handler_data *h ); void msn_msgq_purge( struct im_connection *ic, GSList **list ); char *msn_p11_challenge( char *challenge ); gint msn_domaintree_cmp( gconstpointer a_, gconstpointer b_ ); struct msn_group *msn_group_by_name( struct im_connection *ic, const char *name ); struct msn_group *msn_group_by_id( struct im_connection *ic, const char *id ); int msn_ns_set_display_name( struct im_connection *ic, const char *value ); const char *msn_normalize_handle( const char *handle ); /* tables.c */ const struct msn_away_state *msn_away_state_by_number( int number ); const struct msn_away_state *msn_away_state_by_code( char *code ); const struct msn_away_state *msn_away_state_by_name( char *name ); const struct msn_status_code *msn_status_by_number( int number ); /* sb.c */ int msn_sb_write( struct msn_switchboard *sb, const char *fmt, ... ) G_GNUC_PRINTF( 2, 3 );; struct msn_switchboard *msn_sb_create( struct im_connection *ic, char *host, int port, char *key, int session ); struct msn_switchboard *msn_sb_by_handle( struct im_connection *ic, const char *handle ); struct msn_switchboard *msn_sb_by_chat( struct groupchat *c ); struct msn_switchboard *msn_sb_spare( struct im_connection *ic ); int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ); struct groupchat *msn_sb_to_chat( struct msn_switchboard *sb ); void msn_sb_destroy( struct msn_switchboard *sb ); gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond ); 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 ); #endif //_MSN_H bitlbee-3.2.1/protocols/msn/soap.c0000644000175000017500000007131612245474076016455 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* MSN module - All the SOAPy XML stuff. Some manager at Microsoft apparently thought MSNP wasn't XMLy enough so someone stepped up and changed that. This is the result. Kilobytes and more kilobytes of XML vomit to transfer tiny bits of informaiton. */ /* 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 "http_client.h" #include "soap.h" #include "msn.h" #include "bitlbee.h" #include "url.h" #include "misc.h" #include "sha1.h" #include "base64.h" #include "xmltree.h" #include #include /* This file tries to make SOAP stuff pretty simple to do by letting you just provide a function to build a request, a few functions to parse various parts of the response, and a function to run when the full response was received and parsed. See the various examples below. */ typedef enum { MSN_SOAP_OK, MSN_SOAP_RETRY, MSN_SOAP_REAUTH, MSN_SOAP_ABORT, } msn_soap_result_t; struct msn_soap_req_data; typedef int (*msn_soap_func) ( struct msn_soap_req_data * ); struct msn_soap_req_data { void *data; struct im_connection *ic; int ttl; char *error; char *url, *action, *payload; struct http_request *http_req; const struct xt_handler_entry *xml_parser; msn_soap_func build_request, handle_response, free_data; }; static int msn_soap_send_request( struct msn_soap_req_data *req ); static void msn_soap_free( struct msn_soap_req_data *soap_req ); static void msn_soap_debug_print( const char *headers, const char *payload ); static int msn_soap_start( struct im_connection *ic, void *data, msn_soap_func build_request, const struct xt_handler_entry *xml_parser, msn_soap_func handle_response, msn_soap_func free_data ) { struct msn_soap_req_data *req = g_new0( struct msn_soap_req_data, 1 ); req->ic = ic; req->data = data; req->xml_parser = xml_parser; req->build_request = build_request; req->handle_response = handle_response; req->free_data = free_data; req->ttl = 3; return msn_soap_send_request( req ); } static void msn_soap_handle_response( struct http_request *http_req ); static int msn_soap_send_request( struct msn_soap_req_data *soap_req ) { char *http_req; char *soap_action = NULL; url_t url; soap_req->build_request( soap_req ); if( soap_req->action ) soap_action = g_strdup_printf( "SOAPAction: \"%s\"\r\n", soap_req->action ); url_set( &url, soap_req->url ); http_req = g_strdup_printf( SOAP_HTTP_REQUEST, url.file, url.host, soap_action ? soap_action : "", strlen( soap_req->payload ), soap_req->payload ); msn_soap_debug_print( http_req, soap_req->payload ); soap_req->http_req = http_dorequest( url.host, url.port, url.proto == PROTO_HTTPS, http_req, msn_soap_handle_response, soap_req ); g_free( http_req ); g_free( soap_action ); return soap_req->http_req != NULL; } static void msn_soap_handle_response( struct http_request *http_req ) { struct msn_soap_req_data *soap_req = http_req->data; int st; if( g_slist_find( msn_connections, soap_req->ic ) == NULL ) { msn_soap_free( soap_req ); return; } msn_soap_debug_print( http_req->reply_headers, http_req->reply_body ); if( http_req->body_size > 0 ) { struct xt_parser *parser; struct xt_node *err; parser = xt_new( soap_req->xml_parser, soap_req ); xt_feed( parser, http_req->reply_body, http_req->body_size ); if( http_req->status_code == 500 && ( err = xt_find_path( parser->root, "soap:Body/soap:Fault/detail/errorcode" ) ) && err->text_len > 0 ) { if( strcmp( err->text, "PassportAuthFail" ) == 0 ) { xt_free( parser ); st = MSN_SOAP_REAUTH; goto fail; } /* TODO: Handle/report other errors. */ } xt_handle( parser, NULL, -1 ); xt_free( parser ); } if( http_req->status_code != 200 ) soap_req->error = g_strdup( http_req->status_string ); st = soap_req->handle_response( soap_req ); fail: g_free( soap_req->url ); g_free( soap_req->action ); g_free( soap_req->payload ); g_free( soap_req->error ); soap_req->url = soap_req->action = soap_req->payload = soap_req->error = NULL; if( st == MSN_SOAP_RETRY && --soap_req->ttl ) { msn_soap_send_request( soap_req ); } else if( st == MSN_SOAP_REAUTH ) { struct msn_data *md = soap_req->ic->proto_data; if( !( md->flags & MSN_REAUTHING ) ) { /* Nonce shouldn't actually be touched for re-auths. */ msn_soap_passport_sso_request( soap_req->ic, "blaataap" ); md->flags |= MSN_REAUTHING; } md->soapq = g_slist_append( md->soapq, soap_req ); } else { soap_req->free_data( soap_req ); g_free( soap_req ); } } static char *msn_soap_abservice_build( const char *body_fmt, const char *scenario, const char *ticket, ... ) { va_list params; char *ret, *format, *body; format = g_markup_printf_escaped( SOAP_ABSERVICE_PAYLOAD, scenario, ticket ); va_start( params, ticket ); body = g_strdup_vprintf( body_fmt, params ); va_end( params ); ret = g_strdup_printf( format, body ); g_free( body ); g_free( format ); return ret; } static void msn_soap_debug_print( const char *headers, const char *payload ) { char *s; if( !getenv( "BITLBEE_DEBUG" ) ) return; if( headers ) { if( ( s = strstr( headers, "\r\n\r\n" ) ) ) write( 2, headers, s - headers + 4 ); else write( 2, headers, strlen( headers ) ); } if( payload ) { struct xt_node *xt = xt_from_string( payload, 0 ); if( xt ) xt_print( xt ); xt_free_node( xt ); } } int msn_soapq_flush( struct im_connection *ic, gboolean resend ) { struct msn_data *md = ic->proto_data; while( md->soapq ) { if( resend ) msn_soap_send_request( (struct msn_soap_req_data*) md->soapq->data ); else msn_soap_free( (struct msn_soap_req_data*) md->soapq->data ); md->soapq = g_slist_remove( md->soapq, md->soapq->data ); } return MSN_SOAP_OK; } static void msn_soap_free( struct msn_soap_req_data *soap_req ) { soap_req->free_data( soap_req ); g_free( soap_req->url ); g_free( soap_req->action ); g_free( soap_req->payload ); g_free( soap_req->error ); g_free( soap_req ); } /* passport_sso: Authentication MSNP15+ */ struct msn_soap_passport_sso_data { char *nonce; char *secret; char *error; char *redirect; }; static int msn_soap_passport_sso_build_request( struct msn_soap_req_data *soap_req ) { struct msn_soap_passport_sso_data *sd = soap_req->data; struct im_connection *ic = soap_req->ic; struct msn_data *md = ic->proto_data; char pass[MAX_PASSPORT_PWLEN+1]; if( sd->redirect ) { soap_req->url = sd->redirect; sd->redirect = NULL; } /* MS changed this URL and broke the old MSN-specific one. The generic one works, forwarding us to a msn.com URL that works. Takes an extra second, but that's better than not being able to log in at all. :-/ else if( g_str_has_suffix( ic->acc->user, "@msn.com" ) ) soap_req->url = g_strdup( SOAP_PASSPORT_SSO_URL_MSN ); */ else soap_req->url = g_strdup( SOAP_PASSPORT_SSO_URL ); strncpy( pass, ic->acc->pass, MAX_PASSPORT_PWLEN ); pass[MAX_PASSPORT_PWLEN] = '\0'; soap_req->payload = g_markup_printf_escaped( SOAP_PASSPORT_SSO_PAYLOAD, ic->acc->user, pass, md->pp_policy ); return MSN_SOAP_OK; } static xt_status msn_soap_passport_sso_token( struct xt_node *node, gpointer data ) { struct msn_soap_req_data *soap_req = data; struct msn_soap_passport_sso_data *sd = soap_req->data; struct msn_data *md = soap_req->ic->proto_data; struct xt_node *p; char *id; if( ( id = xt_find_attr( node, "Id" ) ) == NULL ) return XT_HANDLED; id += strlen( id ) - 1; if( *id == '1' && ( p = xt_find_path( node, "../../wst:RequestedProofToken/wst:BinarySecret" ) ) && p->text ) sd->secret = g_strdup( p->text ); *id -= '1'; if( *id >= 0 && *id < sizeof( md->tokens ) / sizeof( md->tokens[0] ) ) { g_free( md->tokens[(int)*id] ); md->tokens[(int)*id] = g_strdup( node->text ); } return XT_HANDLED; } static xt_status msn_soap_passport_failure( struct xt_node *node, gpointer data ) { struct msn_soap_req_data *soap_req = data; struct msn_soap_passport_sso_data *sd = soap_req->data; struct xt_node *code = xt_find_node( node->children, "faultcode" ); struct xt_node *string = xt_find_node( node->children, "faultstring" ); struct xt_node *url; if( code == NULL || code->text_len == 0 ) sd->error = g_strdup( "Unknown error" ); else if( strcmp( code->text, "psf:Redirect" ) == 0 && ( url = xt_find_node( node->children, "psf:redirectUrl" ) ) && url->text_len > 0 ) sd->redirect = g_strdup( url->text ); else sd->error = g_strdup_printf( "%s (%s)", code->text, string && string->text_len ? string->text : "no description available" ); return XT_HANDLED; } static const struct xt_handler_entry msn_soap_passport_sso_parser[] = { { "wsse:BinarySecurityToken", "wst:RequestedSecurityToken", msn_soap_passport_sso_token }, { "S:Fault", "S:Envelope", msn_soap_passport_failure }, { NULL, NULL, NULL } }; static char *msn_key_fuckery( char *key, int key_len, char *type ) { unsigned char hash1[20+strlen(type)+1]; unsigned char hash2[20]; char *ret; sha1_hmac( key, key_len, type, 0, hash1 ); strcpy( (char*) hash1 + 20, type ); sha1_hmac( key, key_len, (char*) hash1, sizeof( hash1 ) - 1, hash2 ); /* This is okay as hash1 is read completely before it's overwritten. */ sha1_hmac( key, key_len, (char*) hash1, 20, hash1 ); sha1_hmac( key, key_len, (char*) hash1, sizeof( hash1 ) - 1, hash1 ); ret = g_malloc( 24 ); memcpy( ret, hash2, 20 ); memcpy( ret + 20, hash1, 4 ); return ret; } static int msn_soap_passport_sso_handle_response( struct msn_soap_req_data *soap_req ) { struct msn_soap_passport_sso_data *sd = soap_req->data; struct im_connection *ic = soap_req->ic; struct msn_data *md = ic->proto_data; char *key1, *key2, *key3, *blurb64; int key1_len; unsigned char *padnonce, *des3res; struct { unsigned int uStructHeaderSize; // 28. Does not count data unsigned int uCryptMode; // CRYPT_MODE_CBC (1) unsigned int uCipherType; // TripleDES (0x6603) unsigned int uHashType; // SHA1 (0x8004) unsigned int uIVLen; // 8 unsigned int uHashLen; // 20 unsigned int uCipherLen; // 72 unsigned char iv[8]; unsigned char hash[20]; unsigned char cipherbytes[72]; } blurb = { GUINT32_TO_LE( 28 ), GUINT32_TO_LE( 1 ), GUINT32_TO_LE( 0x6603 ), GUINT32_TO_LE( 0x8004 ), GUINT32_TO_LE( 8 ), GUINT32_TO_LE( 20 ), GUINT32_TO_LE( 72 ), }; if( sd->redirect ) return MSN_SOAP_RETRY; if( md->soapq ) { md->flags &= ~MSN_REAUTHING; return msn_soapq_flush( ic, TRUE ); } if( sd->secret == NULL ) { msn_auth_got_passport_token( ic, NULL, sd->error ? sd->error : soap_req->error ); return MSN_SOAP_OK; } key1_len = base64_decode( sd->secret, (unsigned char**) &key1 ); key2 = msn_key_fuckery( key1, key1_len, "WS-SecureConversationSESSION KEY HASH" ); key3 = msn_key_fuckery( key1, key1_len, "WS-SecureConversationSESSION KEY ENCRYPTION" ); sha1_hmac( key2, 24, sd->nonce, 0, blurb.hash ); padnonce = g_malloc( strlen( sd->nonce ) + 8 ); strcpy( (char*) padnonce, sd->nonce ); memset( padnonce + strlen( sd->nonce ), 8, 8 ); random_bytes( blurb.iv, 8 ); ssl_des3_encrypt( (unsigned char*) key3, 24, padnonce, strlen( sd->nonce ) + 8, blurb.iv, &des3res ); memcpy( blurb.cipherbytes, des3res, 72 ); blurb64 = base64_encode( (unsigned char*) &blurb, sizeof( blurb ) ); msn_auth_got_passport_token( ic, blurb64, NULL ); g_free( padnonce ); g_free( blurb64 ); g_free( des3res ); g_free( key1 ); g_free( key2 ); g_free( key3 ); return MSN_SOAP_OK; } static int msn_soap_passport_sso_free_data( struct msn_soap_req_data *soap_req ) { struct msn_soap_passport_sso_data *sd = soap_req->data; g_free( sd->nonce ); g_free( sd->secret ); g_free( sd->error ); g_free( sd->redirect ); g_free( sd ); return MSN_SOAP_OK; } int msn_soap_passport_sso_request( struct im_connection *ic, const char *nonce ) { struct msn_soap_passport_sso_data *sd = g_new0( struct msn_soap_passport_sso_data, 1 ); sd->nonce = g_strdup( nonce ); return msn_soap_start( ic, sd, msn_soap_passport_sso_build_request, msn_soap_passport_sso_parser, msn_soap_passport_sso_handle_response, msn_soap_passport_sso_free_data ); } /* memlist: Fetching the membership list (NOT address book) */ static int msn_soap_memlist_build_request( struct msn_soap_req_data *soap_req ) { struct msn_data *md = soap_req->ic->proto_data; soap_req->url = g_strdup( SOAP_MEMLIST_URL ); soap_req->action = g_strdup( SOAP_MEMLIST_ACTION ); soap_req->payload = msn_soap_abservice_build( SOAP_MEMLIST_PAYLOAD, "Initial", md->tokens[1] ); return 1; } static xt_status msn_soap_memlist_member( struct xt_node *node, gpointer data ) { bee_user_t *bu; struct msn_buddy_data *bd; struct xt_node *p; char *role = NULL, *handle = NULL; struct msn_soap_req_data *soap_req = data; struct im_connection *ic = soap_req->ic; if( ( p = xt_find_path( node, "../../MemberRole" ) ) ) role = p->text; if( ( p = xt_find_node( node->children, "PassportName" ) ) ) handle = p->text; if( !role || !handle || !( ( bu = bee_user_by_handle( ic->bee, ic, handle ) ) || ( bu = bee_user_new( ic->bee, ic, handle, 0 ) ) ) ) return XT_HANDLED; bd = bu->data; if( strcmp( role, "Allow" ) == 0 ) { bd->flags |= MSN_BUDDY_AL; ic->permit = g_slist_prepend( ic->permit, g_strdup( handle ) ); } else if( strcmp( role, "Block" ) == 0 ) { bd->flags |= MSN_BUDDY_BL; ic->deny = g_slist_prepend( ic->deny, g_strdup( handle ) ); } else if( strcmp( role, "Reverse" ) == 0 ) bd->flags |= MSN_BUDDY_RL; else if( strcmp( role, "Pending" ) == 0 ) bd->flags |= MSN_BUDDY_PL; if( getenv( "BITLBEE_DEBUG" ) ) fprintf( stderr, "%p %s %d\n", bu, handle, bd->flags ); return XT_HANDLED; } static const struct xt_handler_entry msn_soap_memlist_parser[] = { { "Member", "Members", msn_soap_memlist_member }, { NULL, NULL, NULL } }; static int msn_soap_memlist_handle_response( struct msn_soap_req_data *soap_req ) { msn_soap_addressbook_request( soap_req->ic ); return MSN_SOAP_OK; } static int msn_soap_memlist_free_data( struct msn_soap_req_data *soap_req ) { return 0; } int msn_soap_memlist_request( struct im_connection *ic ) { return msn_soap_start( ic, NULL, msn_soap_memlist_build_request, msn_soap_memlist_parser, msn_soap_memlist_handle_response, msn_soap_memlist_free_data ); } /* Variant: Adding/Removing people */ struct msn_soap_memlist_edit_data { char *handle; gboolean add; msn_buddy_flags_t list; }; static int msn_soap_memlist_edit_build_request( struct msn_soap_req_data *soap_req ) { struct msn_data *md = soap_req->ic->proto_data; struct msn_soap_memlist_edit_data *med = soap_req->data; char *add, *scenario, *list; soap_req->url = g_strdup( SOAP_MEMLIST_URL ); if( med->add ) { soap_req->action = g_strdup( SOAP_MEMLIST_ADD_ACTION ); add = "Add"; } else { soap_req->action = g_strdup( SOAP_MEMLIST_DEL_ACTION ); add = "Delete"; } switch( med->list ) { case MSN_BUDDY_AL: scenario = "BlockUnblock"; list = "Allow"; break; case MSN_BUDDY_BL: scenario = "BlockUnblock"; list = "Block"; break; case MSN_BUDDY_RL: scenario = "Timer"; list = "Reverse"; break; case MSN_BUDDY_PL: default: scenario = "Timer"; list = "Pending"; break; } soap_req->payload = msn_soap_abservice_build( SOAP_MEMLIST_EDIT_PAYLOAD, scenario, md->tokens[1], add, list, med->handle, add ); return 1; } static int msn_soap_memlist_edit_handle_response( struct msn_soap_req_data *soap_req ) { return MSN_SOAP_OK; } static int msn_soap_memlist_edit_free_data( struct msn_soap_req_data *soap_req ) { struct msn_soap_memlist_edit_data *med = soap_req->data; g_free( med->handle ); g_free( med ); return 0; } int msn_soap_memlist_edit( struct im_connection *ic, const char *handle, gboolean add, int list ) { struct msn_soap_memlist_edit_data *med; med = g_new0( struct msn_soap_memlist_edit_data, 1 ); med->handle = g_strdup( handle ); med->add = add; med->list = list; return msn_soap_start( ic, med, msn_soap_memlist_edit_build_request, NULL, msn_soap_memlist_edit_handle_response, msn_soap_memlist_edit_free_data ); } /* addressbook: Fetching the membership list (NOT address book) */ static int msn_soap_addressbook_build_request( struct msn_soap_req_data *soap_req ) { struct msn_data *md = soap_req->ic->proto_data; soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL ); soap_req->action = g_strdup( SOAP_ADDRESSBOOK_ACTION ); soap_req->payload = msn_soap_abservice_build( SOAP_ADDRESSBOOK_PAYLOAD, "Initial", md->tokens[1] ); return 1; } static xt_status msn_soap_addressbook_group( struct xt_node *node, gpointer data ) { struct xt_node *p; char *id = NULL, *name = NULL; struct msn_soap_req_data *soap_req = data; struct msn_data *md = soap_req->ic->proto_data; if( ( p = xt_find_path( node, "../groupId" ) ) ) id = p->text; if( ( p = xt_find_node( node->children, "name" ) ) ) name = p->text; if( id && name ) { struct msn_group *mg = g_new0( struct msn_group, 1 ); mg->id = g_strdup( id ); mg->name = g_strdup( name ); md->groups = g_slist_prepend( md->groups, mg ); } if( getenv( "BITLBEE_DEBUG" ) ) fprintf( stderr, "%s %s\n", id, name ); return XT_HANDLED; } static xt_status msn_soap_addressbook_contact( struct xt_node *node, gpointer data ) { bee_user_t *bu; struct msn_buddy_data *bd; struct xt_node *p; char *id = NULL, *type = NULL, *handle = NULL, *is_msgr = "false", *display_name = NULL, *group_id = NULL; struct msn_soap_req_data *soap_req = data; struct im_connection *ic = soap_req->ic; struct msn_group *group; if( ( p = xt_find_path( node, "../contactId" ) ) ) id = p->text; if( ( p = xt_find_node( node->children, "contactType" ) ) ) type = p->text; if( ( p = xt_find_node( node->children, "passportName" ) ) ) handle = p->text; if( ( p = xt_find_node( node->children, "displayName" ) ) ) display_name = p->text; if( ( p = xt_find_node( node->children, "isMessengerUser" ) ) ) is_msgr = p->text; if( ( p = xt_find_path( node, "groupIds/guid" ) ) ) group_id = p->text; if( type && g_strcasecmp( type, "me" ) == 0 ) { set_t *set = set_find( &ic->acc->set, "display_name" ); g_free( set->value ); set->value = g_strdup( display_name ); /* Try to fetch the profile; if the user has one, that's where we can find the persistent display_name. */ if( ( p = xt_find_node( node->children, "CID" ) ) && p->text ) msn_soap_profile_get( ic, p->text ); return XT_HANDLED; } if( !bool2int( is_msgr ) || handle == NULL ) return XT_HANDLED; if( !( bu = bee_user_by_handle( ic->bee, ic, handle ) ) && !( bu = bee_user_new( ic->bee, ic, handle, 0 ) ) ) return XT_HANDLED; bd = bu->data; bd->flags |= MSN_BUDDY_FL; g_free( bd->cid ); bd->cid = g_strdup( id ); imcb_rename_buddy( ic, handle, display_name ); if( group_id && ( group = msn_group_by_id( ic, group_id ) ) ) imcb_add_buddy( ic, handle, group->name ); if( getenv( "BITLBEE_DEBUG" ) ) fprintf( stderr, "%s %s %s %s\n", id, type, handle, display_name ); return XT_HANDLED; } static const struct xt_handler_entry msn_soap_addressbook_parser[] = { { "contactInfo", "Contact", msn_soap_addressbook_contact }, { "groupInfo", "Group", msn_soap_addressbook_group }, { NULL, NULL, NULL } }; static int msn_soap_addressbook_handle_response( struct msn_soap_req_data *soap_req ) { GSList *l; int wtf = 0; for( l = soap_req->ic->bee->users; l; l = l->next ) { struct bee_user *bu = l->data; struct msn_buddy_data *bd = bu->data; if( bu->ic == soap_req->ic && bd ) { msn_buddy_ask( bu ); if( ( bd->flags & ( MSN_BUDDY_AL | MSN_BUDDY_BL ) ) == ( MSN_BUDDY_AL | MSN_BUDDY_BL ) ) { bd->flags &= ~MSN_BUDDY_BL; wtf++; } } } if( wtf ) imcb_log( soap_req->ic, "Warning: %d contacts were in both your " "block and your allow list. Assuming they're all " "allowed. Use the official WLM client once to fix " "this.", wtf ); msn_auth_got_contact_list( soap_req->ic ); return MSN_SOAP_OK; } static int msn_soap_addressbook_free_data( struct msn_soap_req_data *soap_req ) { return 0; } int msn_soap_addressbook_request( struct im_connection *ic ) { return msn_soap_start( ic, NULL, msn_soap_addressbook_build_request, msn_soap_addressbook_parser, msn_soap_addressbook_handle_response, msn_soap_addressbook_free_data ); } /* Variant: Change our display name. */ static int msn_soap_ab_namechange_build_request( struct msn_soap_req_data *soap_req ) { struct msn_data *md = soap_req->ic->proto_data; soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL ); soap_req->action = g_strdup( SOAP_AB_NAMECHANGE_ACTION ); soap_req->payload = msn_soap_abservice_build( SOAP_AB_NAMECHANGE_PAYLOAD, "Timer", md->tokens[1], (char *) soap_req->data ); return 1; } static int msn_soap_ab_namechange_handle_response( struct msn_soap_req_data *soap_req ) { /* TODO: Ack the change? Not sure what the NAKs look like.. */ return MSN_SOAP_OK; } static int msn_soap_ab_namechange_free_data( struct msn_soap_req_data *soap_req ) { g_free( soap_req->data ); return 0; } int msn_soap_addressbook_set_display_name( struct im_connection *ic, const char *new ) { return msn_soap_start( ic, g_strdup( new ), msn_soap_ab_namechange_build_request, NULL, msn_soap_ab_namechange_handle_response, msn_soap_ab_namechange_free_data ); } /* Add a contact. */ static int msn_soap_ab_contact_add_build_request( struct msn_soap_req_data *soap_req ) { struct msn_data *md = soap_req->ic->proto_data; bee_user_t *bu = soap_req->data; soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL ); soap_req->action = g_strdup( SOAP_AB_CONTACT_ADD_ACTION ); soap_req->payload = msn_soap_abservice_build( SOAP_AB_CONTACT_ADD_PAYLOAD, "ContactSave", md->tokens[1], bu->handle, bu->fullname ? bu->fullname : bu->handle ); return 1; } static xt_status msn_soap_ab_contact_add_cid( struct xt_node *node, gpointer data ) { struct msn_soap_req_data *soap_req = data; bee_user_t *bu = soap_req->data; struct msn_buddy_data *bd = bu->data; g_free( bd->cid ); bd->cid = g_strdup( node->text ); return XT_HANDLED; } static const struct xt_handler_entry msn_soap_ab_contact_add_parser[] = { { "guid", "ABContactAddResult", msn_soap_ab_contact_add_cid }, { NULL, NULL, NULL } }; static int msn_soap_ab_contact_add_handle_response( struct msn_soap_req_data *soap_req ) { /* TODO: Ack the change? Not sure what the NAKs look like.. */ return MSN_SOAP_OK; } static int msn_soap_ab_contact_add_free_data( struct msn_soap_req_data *soap_req ) { return 0; } int msn_soap_ab_contact_add( struct im_connection *ic, bee_user_t *bu ) { return msn_soap_start( ic, bu, msn_soap_ab_contact_add_build_request, msn_soap_ab_contact_add_parser, msn_soap_ab_contact_add_handle_response, msn_soap_ab_contact_add_free_data ); } /* Remove a contact. */ static int msn_soap_ab_contact_del_build_request( struct msn_soap_req_data *soap_req ) { struct msn_data *md = soap_req->ic->proto_data; const char *cid = soap_req->data; soap_req->url = g_strdup( SOAP_ADDRESSBOOK_URL ); soap_req->action = g_strdup( SOAP_AB_CONTACT_DEL_ACTION ); soap_req->payload = msn_soap_abservice_build( SOAP_AB_CONTACT_DEL_PAYLOAD, "Timer", md->tokens[1], cid ); return 1; } static int msn_soap_ab_contact_del_handle_response( struct msn_soap_req_data *soap_req ) { /* TODO: Ack the change? Not sure what the NAKs look like.. */ return MSN_SOAP_OK; } static int msn_soap_ab_contact_del_free_data( struct msn_soap_req_data *soap_req ) { g_free( soap_req->data ); return 0; } int msn_soap_ab_contact_del( struct im_connection *ic, bee_user_t *bu ) { struct msn_buddy_data *bd = bu->data; return msn_soap_start( ic, g_strdup( bd->cid ), msn_soap_ab_contact_del_build_request, NULL, msn_soap_ab_contact_del_handle_response, msn_soap_ab_contact_del_free_data ); } /* Storage stuff: Fetch profile. */ static int msn_soap_profile_get_build_request( struct msn_soap_req_data *soap_req ) { struct msn_data *md = soap_req->ic->proto_data; soap_req->url = g_strdup( SOAP_STORAGE_URL ); soap_req->action = g_strdup( SOAP_PROFILE_GET_ACTION ); soap_req->payload = g_markup_printf_escaped( SOAP_PROFILE_GET_PAYLOAD, md->tokens[3], (char*) soap_req->data ); return 1; } static xt_status msn_soap_profile_get_result( struct xt_node *node, gpointer data ) { struct msn_soap_req_data *soap_req = data; struct im_connection *ic = soap_req->ic; struct msn_data *md = soap_req->ic->proto_data; struct xt_node *dn; if( ( dn = xt_find_node( node->children, "DisplayName" ) ) && dn->text ) { set_t *set = set_find( &ic->acc->set, "display_name" ); g_free( set->value ); set->value = g_strdup( dn->text ); md->flags |= MSN_GOT_PROFILE_DN; } return XT_HANDLED; } static xt_status msn_soap_profile_get_rid( struct xt_node *node, gpointer data ) { struct msn_soap_req_data *soap_req = data; struct msn_data *md = soap_req->ic->proto_data; g_free( md->profile_rid ); md->profile_rid = g_strdup( node->text ); return XT_HANDLED; } static const struct xt_handler_entry msn_soap_profile_get_parser[] = { { "ExpressionProfile", "GetProfileResult", msn_soap_profile_get_result }, { "ResourceID", "GetProfileResult", msn_soap_profile_get_rid }, { NULL, NULL, NULL } }; static int msn_soap_profile_get_handle_response( struct msn_soap_req_data *soap_req ) { struct msn_data *md = soap_req->ic->proto_data; md->flags |= MSN_GOT_PROFILE; msn_ns_finish_login( soap_req->ic ); return MSN_SOAP_OK; } static int msn_soap_profile_get_free_data( struct msn_soap_req_data *soap_req ) { g_free( soap_req->data ); return 0; } int msn_soap_profile_get( struct im_connection *ic, const char *cid ) { return msn_soap_start( ic, g_strdup( cid ), msn_soap_profile_get_build_request, msn_soap_profile_get_parser, msn_soap_profile_get_handle_response, msn_soap_profile_get_free_data ); } /* Update profile (display name). */ static int msn_soap_profile_set_dn_build_request( struct msn_soap_req_data *soap_req ) { struct msn_data *md = soap_req->ic->proto_data; soap_req->url = g_strdup( SOAP_STORAGE_URL ); soap_req->action = g_strdup( SOAP_PROFILE_SET_DN_ACTION ); soap_req->payload = g_markup_printf_escaped( SOAP_PROFILE_SET_DN_PAYLOAD, md->tokens[3], md->profile_rid, (char*) soap_req->data ); return 1; } static const struct xt_handler_entry msn_soap_profile_set_dn_parser[] = { { NULL, NULL, NULL } }; static int msn_soap_profile_set_dn_handle_response( struct msn_soap_req_data *soap_req ) { return MSN_SOAP_OK; } static int msn_soap_profile_set_dn_free_data( struct msn_soap_req_data *soap_req ) { g_free( soap_req->data ); return 0; } int msn_soap_profile_set_dn( struct im_connection *ic, const char *dn ) { return msn_soap_start( ic, g_strdup( dn ), msn_soap_profile_set_dn_build_request, msn_soap_profile_set_dn_parser, msn_soap_profile_set_dn_handle_response, msn_soap_profile_set_dn_free_data ); } bitlbee-3.2.1/protocols/msn/invitation.h0000644000175000017500000000542112245474076017676 0ustar wilmerwilmer/********************************************************************\ * 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 bitlbee-3.2.1/protocols/oscar/0000755000175000017500000000000012245477444015653 5ustar wilmerwilmerbitlbee-3.2.1/protocols/oscar/Makefile0000644000175000017500000000172012245474076017311 0ustar wilmerwilmer########################### ## Makefile for BitlBee ## ## ## ## Copyright 2002 Lintux ## ########################### ### DEFINITIONS -include ../../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)protocols/oscar/ CFLAGS += -I$(_SRCDIR_) endif # [SH] Program variables objects = admin.o auth.o bos.o buddylist.o chat.o chatnav.o conn.o icq.o im.o info.o misc.o msgcookie.o rxhandlers.o rxqueue.o search.o service.o snac.o ssi.o stats.o tlv.o txqueue.o oscar_util.o oscar.o LFLAGS += -r # [SH] Phony targets all: oscar_mod.o check: all lcov: check gcov: gcov *.c .PHONY: all clean distclean clean: rm -f *.o core distclean: clean rm -rf .depend ### MAIN PROGRAM $(objects): ../../Makefile.settings Makefile $(objects): %.o: $(_SRCDIR_)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $(CFLAGS_BITLBEE) $< -o $@ oscar_mod.o: $(objects) @echo '*' Linking oscar_mod.o @$(LD) $(LFLAGS) $(objects) -o oscar_mod.o -include .depend/*.d bitlbee-3.2.1/protocols/oscar/info.h0000644000175000017500000000262512245474076016762 0ustar wilmerwilmer#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__ */ bitlbee-3.2.1/protocols/oscar/service.c0000644000175000017500000005145712245474076017471 0ustar wilmerwilmer/* * Group 1. This is a very special group. All connections support * this group, as it does some particularly good things (like rate limiting). */ #include #include "md5.h" /* Client Online (group 1, subtype 2) */ int aim_clientready(aim_session_t *sess, aim_conn_t *conn) { aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; struct snacgroup *sg; aim_frame_t *fr; aim_snacid_t snacid; if (!ins) return -EINVAL; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0001, 0x0002, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x0001, 0x0002, 0x0000, snacid); /* * Send only the tool versions that the server cares about (that it * marked as supporting in the server ready SNAC). */ for (sg = ins->groups; sg; sg = sg->next) { aim_module_t *mod; if ((mod = aim__findmodulebygroup(sess, sg->group))) { aimbs_put16(&fr->data, mod->family); aimbs_put16(&fr->data, mod->version); aimbs_put16(&fr->data, mod->toolid); aimbs_put16(&fr->data, mod->toolversion); } } aim_tx_enqueue(sess, fr); return 0; } /* * Host Online (group 1, type 3) * * See comments in conn.c about how the group associations are supposed * to work, and how they really work. * * This info probably doesn't even need to make it to the client. * * We don't actually call the client here. This starts off the connection * initialization routine required by all AIM connections. The next time * the client is called is the CONNINITDONE callback, which should be * shortly after the rate information is acknowledged. * */ static int hostonline(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { guint16 *families; int famcount; if (!(families = g_malloc(aim_bstream_empty(bs)))) return 0; for (famcount = 0; aim_bstream_empty(bs); famcount++) { families[famcount] = aimbs_get16(bs); aim_conn_addgroup(rx->conn, families[famcount]); } g_free(families); /* * Next step is in the Host Versions handler. * * Note that we must send this before we request rates, since * the format of the rate information depends on the versions we * give it. * */ aim_setversions(sess, rx->conn); return 1; } /* Service request (group 1, type 4) */ int aim_reqservice(aim_session_t *sess, aim_conn_t *conn, guint16 serviceid) { return aim_genericreq_s(sess, conn, 0x0001, 0x0004, &serviceid); } /* Redirect (group 1, type 5) */ static int redirect(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { struct aim_redirect_data redir; aim_rxcallback_t userfunc; aim_tlvlist_t *tlvlist; aim_snac_t *origsnac = NULL; int ret = 0; memset(&redir, 0, sizeof(redir)); tlvlist = aim_readtlvchain(bs); if (!aim_gettlv(tlvlist, 0x000d, 1) || !aim_gettlv(tlvlist, 0x0005, 1) || !aim_gettlv(tlvlist, 0x0006, 1)) { aim_freetlvchain(&tlvlist); return 0; } redir.group = aim_gettlv16(tlvlist, 0x000d, 1); redir.ip = aim_gettlv_str(tlvlist, 0x0005, 1); redir.cookie = (guint8 *)aim_gettlv_str(tlvlist, 0x0006, 1); /* Fetch original SNAC so we can get csi if needed */ origsnac = aim_remsnac(sess, snac->id); if ((redir.group == AIM_CONN_TYPE_CHAT) && origsnac) { struct chatsnacinfo *csi = (struct chatsnacinfo *)origsnac->data; redir.chat.exchange = csi->exchange; redir.chat.room = csi->name; redir.chat.instance = csi->instance; } if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, &redir); g_free((void *)redir.ip); g_free((void *)redir.cookie); if (origsnac) g_free(origsnac->data); g_free(origsnac); aim_freetlvchain(&tlvlist); return ret; } /* Request Rate Information. (group 1, type 6) */ int aim_reqrates(aim_session_t *sess, aim_conn_t *conn) { return aim_genericreq_n(sess, conn, 0x0001, 0x0006); } /* * OSCAR defines several 'rate classes'. Each class has seperate * rate limiting properties (limit level, alert level, disconnect * level, etc), and a set of SNAC family/type pairs associated with * it. The rate classes, their limiting properties, and the definitions * of which SNACs are belong to which class, are defined in the * Rate Response packet at login to each host. * * Logically, all rate offenses within one class count against further * offenses for other SNACs in the same class (ie, sending messages * too fast will limit the number of user info requests you can send, * since those two SNACs are in the same rate class). * * Since the rate classes are defined dynamically at login, the values * below may change. But they seem to be fairly constant. * * Currently, BOS defines five rate classes, with the commonly used * members as follows... * * Rate class 0x0001: * - Everything thats not in any of the other classes * * Rate class 0x0002: * - Buddy list add/remove * - Permit list add/remove * - Deny list add/remove * * Rate class 0x0003: * - User information requests * - Outgoing ICBMs * * Rate class 0x0004: * - A few unknowns: 2/9, 2/b, and f/2 * * Rate class 0x0005: * - Chat room create * - Outgoing chat ICBMs * * The only other thing of note is that class 5 (chat) has slightly looser * limiting properties than class 3 (normal messages). But thats just a * small bit of trivia for you. * * The last thing that needs to be learned about the rate limiting * system is how the actual numbers relate to the passing of time. This * seems to be a big mystery. * */ static void rc_addclass(struct rateclass **head, struct rateclass *inrc) { struct rateclass *rc, *rc2; if (!(rc = g_malloc(sizeof(struct rateclass)))) return; memcpy(rc, inrc, sizeof(struct rateclass)); rc->next = NULL; for (rc2 = *head; rc2 && rc2->next; rc2 = rc2->next) ; if (!rc2) *head = rc; else rc2->next = rc; return; } static struct rateclass *rc_findclass(struct rateclass **head, guint16 id) { struct rateclass *rc; for (rc = *head; rc; rc = rc->next) { if (rc->classid == id) return rc; } return NULL; } static void rc_addpair(struct rateclass *rc, guint16 group, guint16 type) { struct snacpair *sp, *sp2; if (!(sp = g_new0(struct snacpair, 1))) return; sp->group = group; sp->subtype = type; sp->next = NULL; for (sp2 = rc->members; sp2 && sp2->next; sp2 = sp2->next) ; if (!sp2) rc->members = sp; else sp2->next = sp; return; } /* Rate Parameters (group 1, type 7) */ static int rateresp(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_conn_inside_t *ins = (aim_conn_inside_t *)rx->conn->inside; guint16 numclasses, i; aim_rxcallback_t userfunc; /* * First are the parameters for each rate class. */ numclasses = aimbs_get16(bs); for (i = 0; i < numclasses; i++) { struct rateclass rc; memset(&rc, 0, sizeof(struct rateclass)); rc.classid = aimbs_get16(bs); rc.windowsize = aimbs_get32(bs); rc.clear = aimbs_get32(bs); rc.alert = aimbs_get32(bs); rc.limit = aimbs_get32(bs); rc.disconnect = aimbs_get32(bs); rc.current = aimbs_get32(bs); rc.max = aimbs_get32(bs); /* * The server will send an extra five bytes of parameters * depending on the version we advertised in 1/17. If we * didn't send 1/17 (evil!), then this will crash and you * die, as it will default to the old version but we have * the new version hardcoded here. */ if (mod->version >= 3) aimbs_getrawbuf(bs, rc.unknown, sizeof(rc.unknown)); rc_addclass(&ins->rates, &rc); } /* * Then the members of each class. */ for (i = 0; i < numclasses; i++) { guint16 classid, count; struct rateclass *rc; int j; classid = aimbs_get16(bs); count = aimbs_get16(bs); rc = rc_findclass(&ins->rates, classid); for (j = 0; j < count; j++) { guint16 group, subtype; group = aimbs_get16(bs); subtype = aimbs_get16(bs); if (rc) rc_addpair(rc, group, subtype); } } /* * We don't pass the rate information up to the client, as it really * doesn't care. The information is stored in the connection, however * so that we can do more fun stuff later (not really). */ /* * Last step in the conn init procedure is to acknowledge that we * agree to these draconian limitations. */ aim_rates_addparam(sess, rx->conn); /* * Finally, tell the client it's ready to go... */ if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE))) userfunc(sess, rx); return 1; } /* Add Rate Parameter (group 1, type 8) */ int aim_rates_addparam(aim_session_t *sess, aim_conn_t *conn) { aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; aim_frame_t *fr; aim_snacid_t snacid; struct rateclass *rc; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0001, 0x0008, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x0001, 0x0008, 0x0000, snacid); for (rc = ins->rates; rc; rc = rc->next) aimbs_put16(&fr->data, rc->classid); aim_tx_enqueue(sess, fr); return 0; } /* Rate Change (group 1, type 0x0a) */ static int ratechange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_rxcallback_t userfunc; guint16 code, rateclass; guint32 currentavg, maxavg, windowsize, clear, alert, limit, disconnect; code = aimbs_get16(bs); rateclass = aimbs_get16(bs); windowsize = aimbs_get32(bs); clear = aimbs_get32(bs); alert = aimbs_get32(bs); limit = aimbs_get32(bs); disconnect = aimbs_get32(bs); currentavg = aimbs_get32(bs); maxavg = aimbs_get32(bs); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) return userfunc(sess, rx, code, rateclass, windowsize, clear, alert, limit, disconnect, currentavg, maxavg); return 0; } /* * How Migrations work. * * The server sends a Server Pause message, which the client should respond to * with a Server Pause Ack, which contains the families it needs on this * connection. The server will send a Migration Notice with an IP address, and * then disconnect. Next the client should open the connection and send the * cookie. Repeat the normal login process and pretend this never happened. * * The Server Pause contains no data. * */ /* Service Pause (group 1, type 0x0b) */ static int serverpause(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_rxcallback_t userfunc; if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) return userfunc(sess, rx); return 0; } /* Service Resume (group 1, type 0x0d) */ static int serverresume(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_rxcallback_t userfunc; if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) return userfunc(sess, rx); return 0; } /* Request self-info (group 1, type 0x0e) */ int aim_reqpersonalinfo(aim_session_t *sess, aim_conn_t *conn) { return aim_genericreq_n(sess, conn, 0x0001, 0x000e); } /* Self User Info (group 1, type 0x0f) */ static int selfinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_rxcallback_t userfunc; aim_userinfo_t userinfo; aim_extractuserinfo(sess, bs, &userinfo); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) return userfunc(sess, rx, &userinfo); return 0; } /* Evil Notification (group 1, type 0x10) */ static int evilnotify(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_rxcallback_t userfunc; guint16 newevil; aim_userinfo_t userinfo; memset(&userinfo, 0, sizeof(aim_userinfo_t)); newevil = aimbs_get16(bs); if (aim_bstream_empty(bs)) aim_extractuserinfo(sess, bs, &userinfo); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) return userfunc(sess, rx, newevil, &userinfo); return 0; } /* * Service Migrate (group 1, type 0x12) * * This is the final SNAC sent on the original connection during a migration. * It contains the IP and cookie used to connect to the new server, and * optionally a list of the SNAC groups being migrated. * */ static int migrate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_rxcallback_t userfunc; int ret = 0; guint16 groupcount, i; aim_tlvlist_t *tl; char *ip = NULL; aim_tlv_t *cktlv; /* * Apparently there's some fun stuff that can happen right here. The * migration can actually be quite selective about what groups it * moves to the new server. When not all the groups for a connection * are migrated, or they are all migrated but some groups are moved * to a different server than others, it is called a bifurcated * migration. * * Let's play dumb and not support that. * */ groupcount = aimbs_get16(bs); for (i = 0; i < groupcount; i++) { aimbs_get16(bs); imcb_error(sess->aux_data, "bifurcated migration unsupported"); } tl = aim_readtlvchain(bs); if (aim_gettlv(tl, 0x0005, 1)) ip = aim_gettlv_str(tl, 0x0005, 1); cktlv = aim_gettlv(tl, 0x0006, 1); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, ip, cktlv ? cktlv->value : NULL); aim_freetlvchain(&tl); g_free(ip); return ret; } /* Message of the Day (group 1, type 0x13) */ static int motd(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_rxcallback_t userfunc; char *msg = NULL; int ret = 0; aim_tlvlist_t *tlvlist; guint16 id; /* * Code. * * Valid values: * 1 Mandatory upgrade * 2 Advisory upgrade * 3 System bulletin * 4 Nothing's wrong ("top o the world" -- normal) * 5 Lets-break-something. * */ id = aimbs_get16(bs); /* * TLVs follow */ tlvlist = aim_readtlvchain(bs); msg = aim_gettlv_str(tlvlist, 0x000b, 1); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, id, msg); g_free(msg); aim_freetlvchain(&tlvlist); return ret; } /* * Set privacy flags (group 1, type 0x14) * * Normally 0x03. * * Bit 1: Allows other AIM users to see how long you've been idle. * Bit 2: Allows other AIM users to see how long you've been a member. * */ int aim_bos_setprivacyflags(aim_session_t *sess, aim_conn_t *conn, guint32 flags) { return aim_genericreq_l(sess, conn, 0x0001, 0x0014, &flags); } /* * Set client versions (group 1, subtype 0x17) * * If you've seen the clientonline/clientready SNAC you're probably * wondering what the point of this one is. And that point seems to be * that the versions in the client online SNAC are sent too late for the * server to be able to use them to change the protocol for the earlier * login packets (client versions are sent right after Host Online is * received, but client online versions aren't sent until quite a bit later). * We can see them already making use of this by changing the format of * the rate information based on what version of group 1 we advertise here. * */ int aim_setversions(aim_session_t *sess, aim_conn_t *conn) { aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; struct snacgroup *sg; aim_frame_t *fr; aim_snacid_t snacid; if (!ins) return -EINVAL; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0001, 0x0017, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x0001, 0x0017, 0x0000, snacid); /* * Send only the versions that the server cares about (that it * marked as supporting in the server ready SNAC). */ for (sg = ins->groups; sg; sg = sg->next) { aim_module_t *mod; if ((mod = aim__findmodulebygroup(sess, sg->group))) { aimbs_put16(&fr->data, mod->family); aimbs_put16(&fr->data, mod->version); } } aim_tx_enqueue(sess, fr); return 0; } /* Host versions (group 1, subtype 0x18) */ static int hostversions(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { guint8 *versions; /* This is frivolous. (Thank you SmarterChild.) */ aim_bstream_empty(bs); /* == vercount * 4 */ versions = aimbs_getraw(bs, aim_bstream_empty(bs)); g_free(versions); /* * Now request rates. */ aim_reqrates(sess, rx->conn); return 1; } /* * Subtype 0x001e - Extended Status * * Sets your ICQ status (available, away, do not disturb, etc.) * * These are the same TLVs seen in user info. You can * also set 0x0008 and 0x000c. */ int aim_setextstatus(aim_session_t *sess, aim_conn_t *conn, guint32 status) { aim_frame_t *fr; aim_snacid_t snacid; aim_tlvlist_t *tl = NULL; guint32 data; struct im_connection *ic = sess ? sess->aux_data : NULL; data = AIM_ICQ_STATE_HIDEIP | status; /* yay for error checking ;^) */ if (ic && set_getbool(&ic->acc->set, "web_aware")) data |= AIM_ICQ_STATE_WEBAWARE; aim_addtlvtochain32(&tl, 0x0006, data); /* tlvlen */ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 8))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0001, 0x001e, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x0001, 0x001e, 0x0000, snacid); aim_writetlvchain(&fr->data, &tl); aim_freetlvchain(&tl); aim_tx_enqueue(sess, fr); return 0; } /* * Starting this past week (26 Mar 2001, say), AOL has started sending * this nice little extra SNAC. AFAIK, it has never been used until now. * * The request contains eight bytes. The first four are an offset, the * second four are a length. * * The offset is an offset into aim.exe when it is mapped during execution * on Win32. So far, AOL has only been requesting bytes in static regions * of memory. (I won't put it past them to start requesting data in * less static regions -- regions that are initialized at run time, but still * before the client recieves this request.) * * When the client recieves the request, it adds it to the current ds * (0x00400000) and dereferences it, copying the data into a buffer which * it then runs directly through the MD5 hasher. The 16 byte output of * the hash is then sent back to the server. * * If the client does not send any data back, or the data does not match * the data that the specific client should have, the client will get the * following message from "AOL Instant Messenger": * "You have been disconnected from the AOL Instant Message Service (SM) * for accessing the AOL network using unauthorized software. You can * download a FREE, fully featured, and authorized client, here * http://www.aol.com/aim/download2.html" * The connection is then closed, recieving disconnect code 1, URL * http://www.aim.aol.com/errors/USER_LOGGED_OFF_NEW_LOGIN.html. * * Note, however, that numerous inconsistencies can cause the above error, * not just sending back a bad hash. Do not immediatly suspect this code * if you get disconnected. AOL and the open/free software community have * played this game for a couple years now, generating the above message * on numerous ocassions. * * Anyway, neener. We win again. * */ /* Client verification (group 1, subtype 0x1f) */ static int memrequest(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_rxcallback_t userfunc; guint32 offset, len; aim_tlvlist_t *list; char *modname; offset = aimbs_get32(bs); len = aimbs_get32(bs); list = aim_readtlvchain(bs); modname = aim_gettlv_str(list, 0x0001, 1); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) return userfunc(sess, rx, offset, len, modname); g_free(modname); aim_freetlvchain(&list); return 0; } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if (snac->subtype == 0x0003) return hostonline(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0005) return redirect(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0007) return rateresp(sess, mod, rx, snac, bs); else if (snac->subtype == 0x000a) return ratechange(sess, mod, rx, snac, bs); else if (snac->subtype == 0x000b) return serverpause(sess, mod, rx, snac, bs); else if (snac->subtype == 0x000d) return serverresume(sess, mod, rx, snac, bs); else if (snac->subtype == 0x000f) return selfinfo(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0010) return evilnotify(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0012) return migrate(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0013) return motd(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0018) return hostversions(sess, mod, rx, snac, bs); else if (snac->subtype == 0x001f) return memrequest(sess, mod, rx, snac, bs); return 0; } int general_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = 0x0001; mod->version = 0x0003; mod->toolid = 0x0110; mod->toolversion = 0x0629; mod->flags = 0; strncpy(mod->name, "general", sizeof(mod->name)); mod->snachandler = snachandler; return 0; } bitlbee-3.2.1/protocols/oscar/buddylist.h0000644000175000017500000000076012245474076020030 0ustar wilmerwilmer#ifndef __OSCAR_BUDDYLIST_H__ #define __OSCAR_BUDDYLIST_H__ #define AIM_CB_FAM_BUD 0x0003 /* * SNAC Family: Buddy List Management Services. */ #define AIM_CB_BUD_ERROR 0x0001 #define AIM_CB_BUD_REQRIGHTS 0x0002 #define AIM_CB_BUD_RIGHTSINFO 0x0003 #define AIM_CB_BUD_ADDBUDDY 0x0004 #define AIM_CB_BUD_REMBUDDY 0x0005 #define AIM_CB_BUD_REJECT 0x000a #define AIM_CB_BUD_ONCOMING 0x000b #define AIM_CB_BUD_OFFGOING 0x000c #define AIM_CB_BUD_DEFAULT 0xffff #endif /* __OSCAR_BUDDYLIST_H__ */ bitlbee-3.2.1/protocols/oscar/aim.h0000644000175000017500000006471612245474076016606 0ustar wilmerwilmer/* * Main libfaim header. Must be included in client for prototypes/macros. * * "come on, i turned a chick lesbian; i think this is the hackish equivalent" * -- Josh Meyer * */ #ifndef __AIM_H__ #define __AIM_H__ #include #include #include #include #include #include #include #include #include #include "bitlbee.h" /* XXX adjust these based on autoconf-detected platform */ typedef guint32 aim_snacid_t; typedef guint16 flap_seqnum_t; /* Portability stuff (DMP) */ #if defined(mach) && defined(__APPLE__) #define gethostbyname(x) gethostbyname2(x, AF_INET) #endif /* * Current Maximum Length for Screen Names (not including NULL) * * Currently only names up to 16 characters can be registered * however it is aparently legal for them to be larger. */ #define MAXSNLEN 32 /* * Current Maximum Length for Instant Messages * * This was found basically by experiment, but not wholly * accurate experiment. It should not be regarded * as completely correct. But its a decent approximation. * * Note that although we can send this much, its impossible * for WinAIM clients (up through the latest (4.0.1957)) to * send any more than 1kb. Amaze all your windows friends * with utterly oversized instant messages! * * XXX: the real limit is the total SNAC size at 8192. Fix this. * */ #define MAXMSGLEN 7987 /* * Maximum size of a Buddy Icon. */ #define MAXICONLEN 7168 #define AIM_ICONIDENT "AVT1picture.id" /* * Current Maximum Length for Chat Room Messages * * This is actually defined by the protocol to be * dynamic, but I have yet to see due cause to * define it dynamically here. Maybe later. * */ #define MAXCHATMSGLEN 512 /* * Standard size of an AIM authorization cookie */ #define AIM_COOKIELEN 0x100 #define AIM_MD5_STRING "AOL Instant Messenger (SM)" /* * Default Authorizer server name and TCP port for the OSCAR farm. * * You shouldn't need to change this unless you're writing * your own server. * * Note that only one server is needed to start the whole * AIM process. The later server addresses come from * the authorizer service. * * This is only here for convenience. Its still up to * the client to connect to it. * */ #define AIM_DEFAULT_LOGIN_SERVER_AIM "login.messaging.aol.com" #define AIM_DEFAULT_LOGIN_SERVER_ICQ "login.icq.com" #define AIM_LOGIN_PORT 5190 /* * Size of the SNAC caching hash. * * Default: 16 * */ #define AIM_SNAC_HASH_SIZE 16 /* * Client info. Filled in by the client and passed in to * aim_send_login(). The information ends up getting passed to OSCAR * through the initial login command. * */ struct client_info_s { const char *clientstring; guint16 clientid; int major; int minor; int point; int build; const char *country; /* two-letter abbrev */ const char *lang; /* two-letter abbrev */ }; #define AIM_CLIENTINFO_KNOWNGOOD_3_5_1670 { \ "AOL Instant Messenger (SM), version 3.5.1670/WIN32", \ 0x0004, \ 0x0003, \ 0x0005, \ 0x0000, \ 0x0686, \ "us", \ "en", \ } #define AIM_CLIENTINFO_KNOWNGOOD_4_1_2010 { \ "AOL Instant Messenger (SM), version 4.1.2010/WIN32", \ 0x0004, \ 0x0004, \ 0x0001, \ 0x0000, \ 0x07da, \ "us", \ "en", \ } #define AIM_CLIENTINFO_KNOWNGOOD_5_1_3036 { \ "AOL Instant Messenger, version 5.1.3036/WIN32", \ 0x0109, \ 0x0005, \ 0x0001, \ 0x0000, \ 0x0bdc, \ "us", \ "en", \ } /* * I would make 4.1.2010 the default, but they seem to have found * an alternate way of breaking that one. * * 3.5.1670 should work fine, however, you will be subjected to the * memory test, which may require you to have a WinAIM binary laying * around. (see login.c::memrequest()) */ #define AIM_CLIENTINFO_KNOWNGOOD AIM_CLIENTINFO_KNOWNGOOD_5_1_3036 #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif /* * These could be arbitrary, but its easier to use the actual AIM values */ #define AIM_CONN_TYPE_AUTH 0x0007 #define AIM_CONN_TYPE_ADS 0x0005 #define AIM_CONN_TYPE_BOS 0x0002 #define AIM_CONN_TYPE_CHAT 0x000e #define AIM_CONN_TYPE_CHATNAV 0x000d /* * Status values returned from aim_conn_new(). ORed together. */ #define AIM_CONN_STATUS_READY 0x0001 #define AIM_CONN_STATUS_INTERNALERR 0x0002 #define AIM_CONN_STATUS_RESOLVERR 0x0040 #define AIM_CONN_STATUS_CONNERR 0x0080 #define AIM_CONN_STATUS_INPROGRESS 0x0100 #define AIM_FRAMETYPE_FLAP 0x0000 /* * message type flags */ #define AIM_MTYPE_PLAIN 0x01 #define AIM_MTYPE_CHAT 0x02 #define AIM_MTYPE_FILEREQ 0x03 #define AIM_MTYPE_URL 0x04 #define AIM_MTYPE_AUTHREQ 0x06 #define AIM_MTYPE_AUTHDENY 0x07 #define AIM_MTYPE_AUTHOK 0x08 #define AIM_MTYPE_SERVER 0x09 #define AIM_MTYPE_ADDED 0x0C #define AIM_MTYPE_WWP 0x0D #define AIM_MTYPE_EEXPRESS 0x0E #define AIM_MTYPE_CONTACTS 0x13 #define AIM_MTYPE_PLUGIN 0x1A #define AIM_MTYPE_AUTOAWAY 0xE8 #define AIM_MTYPE_AUTOBUSY 0xE9 #define AIM_MTYPE_AUTONA 0xEA #define AIM_MTYPE_AUTODND 0xEB #define AIM_MTYPE_AUTOFFC 0xEC typedef struct aim_conn_s { int fd; guint16 type; guint16 subtype; flap_seqnum_t seqnum; guint32 status; void *priv; /* misc data the client may want to store */ void *internal; /* internal conn-specific libfaim data */ time_t lastactivity; /* time of last transmit */ int forcedlatency; void *handlerlist; void *sessv; /* pointer to parent session */ void *inside; /* only accessible from inside libfaim */ struct aim_conn_s *next; } aim_conn_t; /* * Byte Stream type. Sort of. * * Use of this type serves a couple purposes: * - Buffer/buflen pairs are passed all around everywhere. This turns * that into one value, as well as abstracting it slightly. * - Through the abstraction, it is possible to enable bounds checking * for robustness at the cost of performance. But a clean failure on * weird packets is much better than a segfault. * - I like having variables named "bs". * * Don't touch the insides of this struct. Or I'll have to kill you. * */ typedef struct aim_bstream_s { guint8 *data; guint32 len; guint32 offset; } aim_bstream_t; typedef struct aim_frame_s { guint8 hdrtype; /* defines which piece of the union to use */ union { struct { guint8 type; flap_seqnum_t seqnum; } flap; } hdr; aim_bstream_t data; /* payload stream */ guint8 handled; /* 0 = new, !0 = been handled */ guint8 nofree; /* 0 = free data on purge, 1 = only unlink */ aim_conn_t *conn; /* the connection it came in on... */ struct aim_frame_s *next; } aim_frame_t; typedef struct aim_msgcookie_s { unsigned char cookie[8]; int type; void *data; time_t addtime; struct aim_msgcookie_s *next; } aim_msgcookie_t; /* * AIM Session: The main client-data interface. * */ typedef struct aim_session_s { /* ---- Client Accessible ------------------------ */ /* Our screen name. */ char sn[MAXSNLEN+1]; /* * Pointer to anything the client wants to * explicitly associate with this session. * * This is for use in the callbacks mainly. In any * callback, you can access this with sess->aux_data. * */ void *aux_data; /* ---- Internal Use Only ------------------------ */ /* Server-stored information (ssi) */ struct { int received_data; guint16 revision; struct aim_ssi_item *items; time_t timestamp; int waiting_for_ack; aim_frame_t *holding_queue; } ssi; /* Connection information */ aim_conn_t *connlist; /* * Transmit/receive queues. * * These are only used when you don't use your own lowlevel * I/O. I don't suggest that you use libfaim's internal I/O. * Its really bad and the API/event model is quirky at best. * */ aim_frame_t *queue_outgoing; aim_frame_t *queue_incoming; /* * Tx Enqueuing function. * * This is how you override the transmit direction of libfaim's * internal I/O. This function will be called whenever it needs * to send something. * */ int (*tx_enqueue)(struct aim_session_s *, aim_frame_t *); /* * Outstanding snac handling * * XXX: Should these be per-connection? -mid */ void *snac_hash[AIM_SNAC_HASH_SIZE]; aim_snacid_t snacid_next; struct aim_icq_info *icq_info; struct aim_oft_info *oft_info; struct aim_authresp_info *authinfo; struct aim_emailinfo *emailinfo; struct { struct aim_userinfo_s *userinfo; struct userinfo_node *torequest; struct userinfo_node *requested; int waiting_for_response; } locate; guint32 flags; /* AIM_SESS_FLAGS_ */ aim_msgcookie_t *msgcookies; void *modlistv; guint8 aim_icq_state; /* ICQ representation of away state */ } aim_session_t; /* Values for sess->flags */ #define AIM_SESS_FLAGS_SNACLOGIN 0x00000001 #define AIM_SESS_FLAGS_XORLOGIN 0x00000002 #define AIM_SESS_FLAGS_NONBLOCKCONNECT 0x00000004 #define AIM_SESS_FLAGS_DONTTIMEOUTONICBM 0x00000008 /* Valid for calling aim_icq_setstatus() and for aim_userinfo_t->icqinfo.status */ #define AIM_ICQ_STATE_NORMAL 0x00000000 #define AIM_ICQ_STATE_AWAY 0x00000001 #define AIM_ICQ_STATE_DND 0x00000002 #define AIM_ICQ_STATE_OUT 0x00000004 #define AIM_ICQ_STATE_BUSY 0x00000010 #define AIM_ICQ_STATE_CHAT 0x00000020 #define AIM_ICQ_STATE_INVISIBLE 0x00000100 #define AIM_ICQ_STATE_WEBAWARE 0x00010000 #define AIM_ICQ_STATE_HIDEIP 0x00020000 #define AIM_ICQ_STATE_BIRTHDAY 0x00080000 #define AIM_ICQ_STATE_DIRECTDISABLED 0x00100000 #define AIM_ICQ_STATE_ICQHOMEPAGE 0x00200000 #define AIM_ICQ_STATE_DIRECTREQUIREAUTH 0x10000000 #define AIM_ICQ_STATE_DIRECTCONTACTLIST 0x20000000 /* * AIM User Info, Standard Form. */ typedef struct { char sn[MAXSNLEN+1]; guint16 warnlevel; guint16 idletime; guint16 flags; guint32 membersince; guint32 onlinesince; guint32 sessionlen; guint32 capabilities; struct { guint32 status; guint32 ipaddr; guint8 crap[0x25]; /* until we figure it out... */ } icqinfo; guint32 present; } aim_userinfo_t; #define AIM_USERINFO_PRESENT_FLAGS 0x00000001 #define AIM_USERINFO_PRESENT_MEMBERSINCE 0x00000002 #define AIM_USERINFO_PRESENT_ONLINESINCE 0x00000004 #define AIM_USERINFO_PRESENT_IDLE 0x00000008 #define AIM_USERINFO_PRESENT_ICQEXTSTATUS 0x00000010 #define AIM_USERINFO_PRESENT_ICQIPADDR 0x00000020 #define AIM_USERINFO_PRESENT_ICQDATA 0x00000040 #define AIM_USERINFO_PRESENT_CAPABILITIES 0x00000080 #define AIM_USERINFO_PRESENT_SESSIONLEN 0x00000100 #define AIM_FLAG_UNCONFIRMED 0x0001 /* "damned transients" */ #define AIM_FLAG_ADMINISTRATOR 0x0002 #define AIM_FLAG_AOL 0x0004 #define AIM_FLAG_OSCAR_PAY 0x0008 #define AIM_FLAG_FREE 0x0010 #define AIM_FLAG_AWAY 0x0020 #define AIM_FLAG_ICQ 0x0040 #define AIM_FLAG_WIRELESS 0x0080 #define AIM_FLAG_UNKNOWN100 0x0100 #define AIM_FLAG_UNKNOWN200 0x0200 #define AIM_FLAG_ACTIVEBUDDY 0x0400 #define AIM_FLAG_UNKNOWN800 0x0800 #define AIM_FLAG_ABINTERNAL 0x1000 #define AIM_FLAG_ALLUSERS 0x001f /* * TLV handling */ /* Generic TLV structure. */ typedef struct aim_tlv_s { guint16 type; guint16 length; guint8 *value; } aim_tlv_t; /* List of above. */ typedef struct aim_tlvlist_s { aim_tlv_t *tlv; struct aim_tlvlist_s *next; } aim_tlvlist_t; /* TLV-handling functions */ #if 0 /* Very, very raw TLV handling. */ int aim_puttlv_8(guint8 *buf, const guint16 t, const guint8 v); int aim_puttlv_16(guint8 *buf, const guint16 t, const guint16 v); int aim_puttlv_32(guint8 *buf, const guint16 t, const guint32 v); int aim_puttlv_raw(guint8 *buf, const guint16 t, const guint16 l, const guint8 *v); #endif /* TLV list handling. */ aim_tlvlist_t *aim_readtlvchain(aim_bstream_t *bs); void aim_freetlvchain(aim_tlvlist_t **list); aim_tlv_t *aim_gettlv(aim_tlvlist_t *list, guint16 t, const int n); char *aim_gettlv_str(aim_tlvlist_t *list, const guint16 t, const int n); guint8 aim_gettlv8(aim_tlvlist_t *list, const guint16 type, const int num); guint16 aim_gettlv16(aim_tlvlist_t *list, const guint16 t, const int n); guint32 aim_gettlv32(aim_tlvlist_t *list, const guint16 t, const int n); int aim_writetlvchain(aim_bstream_t *bs, aim_tlvlist_t **list); int aim_addtlvtochain8(aim_tlvlist_t **list, const guint16 t, const guint8 v); int aim_addtlvtochain16(aim_tlvlist_t **list, const guint16 t, const guint16 v); int aim_addtlvtochain32(aim_tlvlist_t **list, const guint16 type, const guint32 v); int aim_addtlvtochain_raw(aim_tlvlist_t **list, const guint16 t, const guint16 l, const guint8 *v); int aim_addtlvtochain_caps(aim_tlvlist_t **list, const guint16 t, const guint32 caps); int aim_addtlvtochain_noval(aim_tlvlist_t **list, const guint16 type); int aim_addtlvtochain_chatroom(aim_tlvlist_t **list, guint16 type, guint16 exchange, const char *roomname, guint16 instance); int aim_addtlvtochain_userinfo(aim_tlvlist_t **list, guint16 type, aim_userinfo_t *ui); int aim_addtlvtochain_frozentlvlist(aim_tlvlist_t **list, guint16 type, aim_tlvlist_t **tl); int aim_counttlvchain(aim_tlvlist_t **list); int aim_sizetlvchain(aim_tlvlist_t **list); /* * Get command from connections * * aim_get_commmand() is the libfaim lowlevel I/O in the receive direction. * XXX Make this easily overridable. * */ int aim_get_command(aim_session_t *, aim_conn_t *); /* * Dispatch commands that are in the rx queue. */ void aim_rxdispatch(aim_session_t *); int aim_debugconn_sendconnect(aim_session_t *sess, aim_conn_t *conn); typedef int (*aim_rxcallback_t)(aim_session_t *, aim_frame_t *, ...); struct aim_clientrelease { char *name; guint32 build; char *url; char *info; }; struct aim_authresp_info { char *sn; guint16 errorcode; char *errorurl; guint16 regstatus; char *email; char *bosip; guint8 *cookie; struct aim_clientrelease latestrelease; struct aim_clientrelease latestbeta; }; /* Callback data for redirect. */ struct aim_redirect_data { guint16 group; const char *ip; const guint8 *cookie; struct { /* group == AIM_CONN_TYPE_CHAT */ guint16 exchange; const char *room; guint16 instance; } chat; }; int aim_clientready(aim_session_t *sess, aim_conn_t *conn); int aim_sendflapver(aim_session_t *sess, aim_conn_t *conn); int aim_request_login(aim_session_t *sess, aim_conn_t *conn, const char *sn); int aim_send_login(aim_session_t *, aim_conn_t *, const char *, const char *, struct client_info_s *, const char *key); int aim_encode_password_md5(const char *password, const char *key, unsigned char *digest); void aim_purge_rxqueue(aim_session_t *); #define AIM_TX_QUEUED 0 /* default */ #define AIM_TX_IMMEDIATE 1 #define AIM_TX_USER 2 int aim_tx_setenqueue(aim_session_t *sess, int what, int (*func)(aim_session_t *, aim_frame_t *)); int aim_tx_flushqueue(aim_session_t *); void aim_tx_purgequeue(aim_session_t *); int aim_conn_setlatency(aim_conn_t *conn, int newval); void aim_conn_kill(aim_session_t *sess, aim_conn_t **deadconn); int aim_conn_addhandler(aim_session_t *, aim_conn_t *conn, u_short family, u_short type, aim_rxcallback_t newhandler, u_short flags); int aim_clearhandlers(aim_conn_t *conn); aim_conn_t *aim_conn_findbygroup(aim_session_t *sess, guint16 group); aim_session_t *aim_conn_getsess(aim_conn_t *conn); void aim_conn_close(aim_conn_t *deadconn); aim_conn_t *aim_newconn(aim_session_t *, int type, const char *dest); int aim_conngetmaxfd(aim_session_t *); aim_conn_t *aim_select(aim_session_t *, struct timeval *, int *); int aim_conn_isready(aim_conn_t *); int aim_conn_setstatus(aim_conn_t *, int); int aim_conn_completeconnect(aim_session_t *sess, aim_conn_t *conn); int aim_conn_isconnecting(aim_conn_t *conn); typedef void (*faim_debugging_callback_t)(aim_session_t *sess, int level, const char *format, va_list va); int aim_setdebuggingcb(aim_session_t *sess, faim_debugging_callback_t); void aim_session_init(aim_session_t *, guint32 flags, int debuglevel); void aim_session_kill(aim_session_t *); void aim_setupproxy(aim_session_t *sess, const char *server, const char *username, const char *password); aim_conn_t *aim_getconn_type(aim_session_t *, int type); aim_conn_t *aim_getconn_type_all(aim_session_t *, int type); aim_conn_t *aim_getconn_fd(aim_session_t *, int fd); /* aim_misc.c */ struct aim_chat_roominfo { unsigned short exchange; char *name; unsigned short instance; }; struct aim_chat_invitation { struct im_connection * ic; char * name; guint8 exchange; }; #define AIM_VISIBILITYCHANGE_PERMITADD 0x05 #define AIM_VISIBILITYCHANGE_PERMITREMOVE 0x06 #define AIM_VISIBILITYCHANGE_DENYADD 0x07 #define AIM_VISIBILITYCHANGE_DENYREMOVE 0x08 #define AIM_PRIVFLAGS_ALLOWIDLE 0x01 #define AIM_PRIVFLAGS_ALLOWMEMBERSINCE 0x02 #define AIM_WARN_ANON 0x01 int aim_flap_nop(aim_session_t *sess, aim_conn_t *conn); int aim_bos_setprofile(aim_session_t *sess, aim_conn_t *conn, const char *profile, const char *awaymsg, guint32 caps); int aim_bos_setgroupperm(aim_session_t *, aim_conn_t *, guint32 mask); int aim_bos_setprivacyflags(aim_session_t *, aim_conn_t *, guint32); int aim_reqpersonalinfo(aim_session_t *, aim_conn_t *); int aim_reqservice(aim_session_t *, aim_conn_t *, guint16); int aim_bos_reqrights(aim_session_t *, aim_conn_t *); int aim_bos_reqbuddyrights(aim_session_t *, aim_conn_t *); int aim_bos_reqlocaterights(aim_session_t *, aim_conn_t *); int aim_setextstatus(aim_session_t *sess, aim_conn_t *conn, guint32 status); struct aim_fileheader_t *aim_getlisting(aim_session_t *sess, FILE *); #define AIM_CLIENTTYPE_UNKNOWN 0x0000 #define AIM_CLIENTTYPE_MC 0x0001 #define AIM_CLIENTTYPE_WINAIM 0x0002 #define AIM_CLIENTTYPE_WINAIM41 0x0003 #define AIM_CLIENTTYPE_AOL_TOC 0x0004 #define AIM_RATE_CODE_CHANGE 0x0001 #define AIM_RATE_CODE_WARNING 0x0002 #define AIM_RATE_CODE_LIMIT 0x0003 #define AIM_RATE_CODE_CLEARLIMIT 0x0004 int aim_ads_requestads(aim_session_t *sess, aim_conn_t *conn); /* aim_im.c */ aim_conn_t *aim_sendfile_initiate(aim_session_t *, const char *destsn, const char *filename, guint16 numfiles, guint32 totsize); aim_conn_t *aim_getfile_initiate(aim_session_t *sess, aim_conn_t *conn, const char *destsn); int aim_oft_getfile_request(aim_session_t *sess, aim_conn_t *conn, const char *name, int size); int aim_oft_getfile_ack(aim_session_t *sess, aim_conn_t *conn); int aim_oft_getfile_end(aim_session_t *sess, aim_conn_t *conn); #define AIM_SENDMEMBLOCK_FLAG_ISREQUEST 0 #define AIM_SENDMEMBLOCK_FLAG_ISHASH 1 #define AIM_GETINFO_GENERALINFO 0x00001 #define AIM_GETINFO_AWAYMESSAGE 0x00003 #define AIM_GETINFO_CAPABILITIES 0x0004 struct aim_invite_priv { char *sn; char *roomname; guint16 exchange; guint16 instance; }; #define AIM_COOKIETYPE_UNKNOWN 0x00 #define AIM_COOKIETYPE_ICBM 0x01 #define AIM_COOKIETYPE_ADS 0x02 #define AIM_COOKIETYPE_BOS 0x03 #define AIM_COOKIETYPE_IM 0x04 #define AIM_COOKIETYPE_CHAT 0x05 #define AIM_COOKIETYPE_CHATNAV 0x06 #define AIM_COOKIETYPE_INVITE 0x07 int aim_handlerendconnect(aim_session_t *sess, aim_conn_t *cur); #define AIM_TRANSFER_DENY_NOTSUPPORTED 0x0000 #define AIM_TRANSFER_DENY_DECLINE 0x0001 #define AIM_TRANSFER_DENY_NOTACCEPTING 0x0002 aim_conn_t *aim_accepttransfer(aim_session_t *sess, aim_conn_t *conn, const char *sn, const guint8 *cookie, const guint8 *ip, guint16 listingfiles, guint16 listingtotsize, guint16 listingsize, guint32 listingchecksum, guint16 rendid); int aim_getinfo(aim_session_t *, aim_conn_t *, const char *, unsigned short); #define AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED 0x00000001 #define AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED 0x00000002 /* This is what the server will give you if you don't set them yourself. */ #define AIM_IMPARAM_DEFAULTS { \ 0, \ AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED | AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED, \ 512, /* !! Note how small this is. */ \ (99.9)*10, (99.9)*10, \ 1000 /* !! And how large this is. */ \ } /* This is what most AIM versions use. */ #define AIM_IMPARAM_REASONABLE { \ 0, \ AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED | AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED, \ 8000, \ (99.9)*10, (99.9)*10, \ 0 \ } struct aim_icbmparameters { guint16 maxchan; guint32 flags; /* AIM_IMPARAM_FLAG_ */ guint16 maxmsglen; /* message size that you will accept */ guint16 maxsenderwarn; /* this and below are *10 (999=99.9%) */ guint16 maxrecverwarn; guint32 minmsginterval; /* in milliseconds? */ }; int aim_reqicbmparams(aim_session_t *sess); int aim_seticbmparam(aim_session_t *sess, struct aim_icbmparameters *params); /* auth.c */ int aim_sendcookie(aim_session_t *, aim_conn_t *, const guint8 *); int aim_admin_changepasswd(aim_session_t *, aim_conn_t *, const char *newpw, const char *curpw); int aim_admin_reqconfirm(aim_session_t *sess, aim_conn_t *conn); int aim_admin_getinfo(aim_session_t *sess, aim_conn_t *conn, guint16 info); int aim_admin_setemail(aim_session_t *sess, aim_conn_t *conn, const char *newemail); int aim_admin_setnick(aim_session_t *sess, aim_conn_t *conn, const char *newnick); /* These apply to exchanges as well. */ #define AIM_CHATROOM_FLAG_EVILABLE 0x0001 #define AIM_CHATROOM_FLAG_NAV_ONLY 0x0002 #define AIM_CHATROOM_FLAG_INSTANCING_ALLOWED 0x0004 #define AIM_CHATROOM_FLAG_OCCUPANT_PEEK_ALLOWED 0x0008 struct aim_chat_exchangeinfo { guint16 number; guint16 flags; char *name; char *charset1; char *lang1; char *charset2; char *lang2; }; #define AIM_CHATFLAGS_NOREFLECT 0x0001 #define AIM_CHATFLAGS_AWAY 0x0002 #define AIM_CHATFLAGS_UNICODE 0x0004 #define AIM_CHATFLAGS_ISO_8859_1 0x0008 int aim_chat_send_im(aim_session_t *sess, aim_conn_t *conn, guint16 flags, const char *msg, int msglen); int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance); int aim_chatnav_reqrights(aim_session_t *sess, aim_conn_t *conn); int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, guint16 exchange, const char *roomname, guint16 instance); int aim_chatnav_createroom(aim_session_t *sess, aim_conn_t *conn, const char *name, guint16 exchange); /* aim_util.c */ /* * These are really ugly. You'd think this was LISP. I wish it was. * * XXX With the advent of bstream's, these should be removed to enforce * their use. * */ #define aimutil_put8(buf, data) ((*(buf) = (u_char)(data)&0xff),1) #define aimutil_get8(buf) ((*(buf))&0xff) #define aimutil_put16(buf, data) ( \ (*(buf) = (u_char)((data)>>8)&0xff), \ (*((buf)+1) = (u_char)(data)&0xff), \ 2) #define aimutil_get16(buf) ((((*(buf))<<8)&0xff00) + ((*((buf)+1)) & 0xff)) #define aimutil_put32(buf, data) ( \ (*((buf)) = (u_char)((data)>>24)&0xff), \ (*((buf)+1) = (u_char)((data)>>16)&0xff), \ (*((buf)+2) = (u_char)((data)>>8)&0xff), \ (*((buf)+3) = (u_char)(data)&0xff), \ 4) #define aimutil_get32(buf) ((((*(buf))<<24)&0xff000000) + \ (((*((buf)+1))<<16)&0x00ff0000) + \ (((*((buf)+2))<< 8)&0x0000ff00) + \ (((*((buf)+3) )&0x000000ff))) /* Little-endian versions (damn ICQ) */ #define aimutil_putle8(buf, data) ( \ (*(buf) = (unsigned char)(data) & 0xff), \ 1) #define aimutil_getle8(buf) ( \ (*(buf)) & 0xff \ ) #define aimutil_putle16(buf, data) ( \ (*((buf)+0) = (unsigned char)((data) >> 0) & 0xff), \ (*((buf)+1) = (unsigned char)((data) >> 8) & 0xff), \ 2) #define aimutil_getle16(buf) ( \ (((*((buf)+0)) << 0) & 0x00ff) + \ (((*((buf)+1)) << 8) & 0xff00) \ ) #define aimutil_putle32(buf, data) ( \ (*((buf)+0) = (unsigned char)((data) >> 0) & 0xff), \ (*((buf)+1) = (unsigned char)((data) >> 8) & 0xff), \ (*((buf)+2) = (unsigned char)((data) >> 16) & 0xff), \ (*((buf)+3) = (unsigned char)((data) >> 24) & 0xff), \ 4) #define aimutil_getle32(buf) ( \ (((*((buf)+0)) << 0) & 0x000000ff) + \ (((*((buf)+1)) << 8) & 0x0000ff00) + \ (((*((buf)+2)) << 16) & 0x00ff0000) + \ (((*((buf)+3)) << 24) & 0xff000000)) int aim_sncmp(const char *a, const char *b); #include /* * SNAC Families. */ #define AIM_CB_FAM_ACK 0x0000 #define AIM_CB_FAM_GEN 0x0001 #define AIM_CB_FAM_SPECIAL 0xffff /* Internal libfaim use */ /* * SNAC Family: Ack. * * Not really a family, but treating it as one really * helps it fit into the libfaim callback structure better. * */ #define AIM_CB_ACK_ACK 0x0001 /* * SNAC Family: General. */ #define AIM_CB_GEN_ERROR 0x0001 #define AIM_CB_GEN_CLIENTREADY 0x0002 #define AIM_CB_GEN_SERVERREADY 0x0003 #define AIM_CB_GEN_SERVICEREQ 0x0004 #define AIM_CB_GEN_REDIRECT 0x0005 #define AIM_CB_GEN_RATEINFOREQ 0x0006 #define AIM_CB_GEN_RATEINFO 0x0007 #define AIM_CB_GEN_RATEINFOACK 0x0008 #define AIM_CB_GEN_RATECHANGE 0x000a #define AIM_CB_GEN_SERVERPAUSE 0x000b #define AIM_CB_GEN_SERVERRESUME 0x000d #define AIM_CB_GEN_REQSELFINFO 0x000e #define AIM_CB_GEN_SELFINFO 0x000f #define AIM_CB_GEN_EVIL 0x0010 #define AIM_CB_GEN_SETIDLE 0x0011 #define AIM_CB_GEN_MIGRATIONREQ 0x0012 #define AIM_CB_GEN_MOTD 0x0013 #define AIM_CB_GEN_SETPRIVFLAGS 0x0014 #define AIM_CB_GEN_WELLKNOWNURL 0x0015 #define AIM_CB_GEN_NOP 0x0016 #define AIM_CB_GEN_DEFAULT 0xffff /* * SNAC Family: Advertisement Services */ #define AIM_CB_ADS_ERROR 0x0001 #define AIM_CB_ADS_DEFAULT 0xffff /* * OFT Services * * See non-SNAC note below. */ #define AIM_CB_OFT_DIRECTIMCONNECTREQ 0x0001/* connect request -- actually an OSCAR CAP*/ #define AIM_CB_OFT_DIRECTIMINCOMING 0x0002 #define AIM_CB_OFT_DIRECTIMDISCONNECT 0x0003 #define AIM_CB_OFT_DIRECTIMTYPING 0x0004 #define AIM_CB_OFT_DIRECTIMINITIATE 0x0005 #define AIM_CB_OFT_GETFILECONNECTREQ 0x0006 /* connect request -- actually an OSCAR CAP*/ #define AIM_CB_OFT_GETFILELISTINGREQ 0x0007 /* OFT listing.txt request */ #define AIM_CB_OFT_GETFILEFILEREQ 0x0008 /* received file request */ #define AIM_CB_OFT_GETFILEFILESEND 0x0009 /* received file request confirm -- send data */ #define AIM_CB_OFT_GETFILECOMPLETE 0x000a /* received file send complete*/ #define AIM_CB_OFT_GETFILEINITIATE 0x000b /* request for file get acknowledge */ #define AIM_CB_OFT_GETFILEDISCONNECT 0x000c /* OFT connection disconnected.*/ #define AIM_CB_OFT_GETFILELISTING 0x000d /* OFT listing.txt received.*/ #define AIM_CB_OFT_GETFILERECEIVE 0x000e /* OFT file incoming.*/ #define AIM_CB_OFT_GETFILELISTINGRXCONFIRM 0x000f #define AIM_CB_OFT_GETFILESTATE4 0x0010 #define AIM_CB_OFT_SENDFILEDISCONNECT 0x0020 /* OFT connection disconnected.*/ /* * SNAC Family: Internal Messages * * This isn't truely a SNAC family either, but using * these, we can integrated non-SNAC services into * the SNAC-centered libfaim callback structure. * */ #define AIM_CB_SPECIAL_AUTHSUCCESS 0x0001 #define AIM_CB_SPECIAL_AUTHOTHER 0x0002 #define AIM_CB_SPECIAL_CONNERR 0x0003 #define AIM_CB_SPECIAL_CONNCOMPLETE 0x0004 #define AIM_CB_SPECIAL_FLAPVER 0x0005 #define AIM_CB_SPECIAL_CONNINITDONE 0x0006 #define AIM_CB_SPECIAL_IMAGETRANSFER 0x007 #define AIM_CB_SPECIAL_UNKNOWN 0xffff #define AIM_CB_SPECIAL_DEFAULT AIM_CB_SPECIAL_UNKNOWN #endif /* __AIM_H__ */ bitlbee-3.2.1/protocols/oscar/AUTHORS0000644000175000017500000000114012245474076016715 0ustar wilmerwilmer Anyone else want to be in here? --- N: Adam Fritzler H: mid E: mid@auk.cx W: http://www.auk.cx/~mid,http://www.auk.cx/faim D: Wrote most of the wap of crap that you see before you. N: Josh Myer E: josh@joshisanerd.com D: OFT/ODC (not quite finished yet..), random little things, Munger-At-Large, compile-time warnings. N: Daniel Reed H: n, linuxkitty E: n@ml.org W: http://users.n.ml.org/n/ D: Fixed aim_snac.c N: Eric Warmenhoven E: warmenhoven@linux.com D: Some OFT info, author of the faim interface for gaim N: Brock Wilcox H: awwaiid E: awwaiid@auk.cx D: Figured out original password roasting bitlbee-3.2.1/protocols/oscar/im.h0000644000175000017500000001214412245474076016431 0ustar wilmerwilmer#ifndef __OSCAR_IM_H__ #define __OSCAR_IM_H__ #define AIM_CB_FAM_MSG 0x0004 /* * SNAC Family: Messaging Services. */ #define AIM_CB_MSG_ERROR 0x0001 #define AIM_CB_MSG_PARAMINFO 0x0005 #define AIM_CB_MSG_INCOMING 0x0007 #define AIM_CB_MSG_EVIL 0x0009 #define AIM_CB_MSG_MISSEDCALL 0x000a #define AIM_CB_MSG_CLIENTAUTORESP 0x000b #define AIM_CB_MSG_ACK 0x000c #define AIM_CB_MSG_MTN 0x0014 #define AIM_CB_MSG_DEFAULT 0xffff #define AIM_IMFLAGS_AWAY 0x0001 /* mark as an autoreply */ #define AIM_IMFLAGS_ACK 0x0002 /* request a receipt notice */ #define AIM_IMFLAGS_UNICODE 0x0004 #define AIM_IMFLAGS_ISO_8859_1 0x0008 #define AIM_IMFLAGS_BUDDYREQ 0x0010 /* buddy icon requested */ #define AIM_IMFLAGS_HASICON 0x0020 /* already has icon */ #define AIM_IMFLAGS_SUBENC_MACINTOSH 0x0040 /* damn that Steve Jobs! */ #define AIM_IMFLAGS_CUSTOMFEATURES 0x0080 /* features field present */ #define AIM_IMFLAGS_EXTDATA 0x0100 #define AIM_IMFLAGS_CUSTOMCHARSET 0x0200 /* charset fields set */ #define AIM_IMFLAGS_MULTIPART 0x0400 /* ->mpmsg section valid */ #define AIM_IMFLAGS_OFFLINE 0x0800 /* send to offline user */ /* * Multipart message structures. */ typedef struct aim_mpmsg_section_s { guint16 charset; guint16 charsubset; guint8 *data; guint16 datalen; struct aim_mpmsg_section_s *next; } aim_mpmsg_section_t; typedef struct aim_mpmsg_s { int numparts; aim_mpmsg_section_t *parts; } aim_mpmsg_t; int aim_mpmsg_init(aim_session_t *sess, aim_mpmsg_t *mpm); void aim_mpmsg_free(aim_session_t *sess, aim_mpmsg_t *mpm); /* * Arguments to aim_send_im_ext(). * * This is really complicated. But immensely versatile. * */ struct aim_sendimext_args { /* These are _required_ */ const char *destsn; guint32 flags; /* often 0 */ /* Only required if not using multipart messages */ const char *msg; int msglen; /* Required if ->msg is not provided */ aim_mpmsg_t *mpmsg; /* Only used if AIM_IMFLAGS_HASICON is set */ guint32 iconlen; time_t iconstamp; guint32 iconsum; /* Only used if AIM_IMFLAGS_CUSTOMFEATURES is set */ guint8 *features; guint8 featureslen; /* Only used if AIM_IMFLAGS_CUSTOMCHARSET is set and mpmsg not used */ guint16 charset; guint16 charsubset; }; /* * This information is provided in the Incoming ICBM callback for * Channel 1 ICBM's. * * Note that although CUSTOMFEATURES and CUSTOMCHARSET say they * are optional, both are always set by the current libfaim code. * That may or may not change in the future. It is mainly for * consistency with aim_sendimext_args. * * Multipart messages require some explanation. If you want to use them, * I suggest you read all the comments in im.c. * */ struct aim_incomingim_ch1_args { /* Always provided */ aim_mpmsg_t mpmsg; guint32 icbmflags; /* some flags apply only to ->msg, not all mpmsg */ /* Only provided if message has a human-readable section */ char *msg; int msglen; /* Only provided if AIM_IMFLAGS_HASICON is set */ time_t iconstamp; guint32 iconlen; guint16 iconsum; /* Only provided if AIM_IMFLAGS_CUSTOMFEATURES is set */ guint8 *features; guint8 featureslen; /* Only provided if AIM_IMFLAGS_EXTDATA is set */ guint8 extdatalen; guint8 *extdata; /* Only used if AIM_IMFLAGS_CUSTOMCHARSET is set */ guint16 charset; guint16 charsubset; }; /* Valid values for channel 2 args->status */ #define AIM_RENDEZVOUS_PROPOSE 0x0000 #define AIM_RENDEZVOUS_CANCEL 0x0001 #define AIM_RENDEZVOUS_ACCEPT 0x0002 struct aim_incomingim_ch2_args { guint8 cookie[8]; guint16 reqclass; guint16 status; guint16 errorcode; const char *clientip; const char *clientip2; const char *verifiedip; guint16 port; const char *msg; /* invite message or file description */ const char *encoding; const char *language; union { struct { guint32 checksum; guint32 length; time_t timestamp; guint8 *icon; } icon; struct { struct aim_chat_roominfo roominfo; } chat; struct { guint32 fgcolor; guint32 bgcolor; const char *rtfmsg; } rtfmsg; } info; void *destructor; /* used internally only */ }; /* Valid values for channel 4 args->type */ #define AIM_ICQMSG_AUTHREQUEST 0x0006 #define AIM_ICQMSG_AUTHDENIED 0x0007 #define AIM_ICQMSG_AUTHGRANTED 0x0008 struct aim_incomingim_ch4_args { guint32 uin; /* Of the sender of the ICBM */ guint16 type; char *msg; /* Reason for auth request, deny, or accept */ }; int aim_send_im_ext(aim_session_t *sess, struct aim_sendimext_args *args); int aim_send_im(aim_session_t *, const char *destsn, unsigned short flags, const char *msg); int aim_send_typing(aim_session_t *sess, aim_conn_t *conn, int typing); int aim_send_im_direct(aim_session_t *, aim_conn_t *, const char *msg, int len); const char *aim_directim_getsn(aim_conn_t *conn); aim_conn_t *aim_directim_initiate(aim_session_t *, const char *destsn); aim_conn_t *aim_directim_connect(aim_session_t *, const char *sn, const char *addr, const guint8 *cookie); int aim_im_sendmtn(aim_session_t *sess, guint16 type1, const char *sn, guint16 type2); int aim_send_im_ch2_statusmessage(aim_session_t *sess, const char *sender, const guint8 *cookie, const char *message, const guint8 state, const guint16 dc); #endif /* __OSCAR_IM_H__ */ bitlbee-3.2.1/protocols/oscar/admin.c0000644000175000017500000001124112245474076017104 0ustar wilmerwilmer#include #include "admin.h" /* called for both reply and change-reply */ static int infochange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { /* * struct { * guint16 perms; * guint16 tlvcount; * aim_tlv_t tlvs[tlvcount]; * } admin_info[n]; */ while (aim_bstream_empty(bs)) { guint16 perms, tlvcount; perms = aimbs_get16(bs); tlvcount = aimbs_get16(bs); while (tlvcount && aim_bstream_empty(bs)) { aim_rxcallback_t userfunc; guint16 type, len; guint8 *val; int str = 0; type = aimbs_get16(bs); len = aimbs_get16(bs); if ((type == 0x0011) || (type == 0x0004)) str = 1; if (str) val = (guint8 *)aimbs_getstr(bs, len); else val = aimbs_getraw(bs, len); /* XXX fix so its only called once for the entire packet */ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) userfunc(sess, rx, (snac->subtype == 0x0005) ? 1 : 0, perms, type, len, val, str); g_free(val); tlvcount--; } } return 1; } static int accountconfirm(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_rxcallback_t userfunc; guint16 status; status = aimbs_get16(bs); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) return userfunc(sess, rx, status); return 0; } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if ((snac->subtype == 0x0003) || (snac->subtype == 0x0005)) return infochange(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0007) return accountconfirm(sess, mod, rx, snac, bs); return 0; } int admin_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = AIM_CB_FAM_ADM; mod->version = 0x0001; mod->toolid = AIM_TOOL_NEWWIN; mod->toolversion = 0x0629; mod->flags = 0; strncpy(mod->name, "admin", sizeof(mod->name)); mod->snachandler = snachandler; return 0; } int aim_admin_changepasswd(aim_session_t *sess, aim_conn_t *conn, const char *newpw, const char *curpw) { aim_frame_t *tx; aim_tlvlist_t *tl = NULL; aim_snacid_t snacid; if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4+strlen(curpw)+4+strlen(newpw)))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0); aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid); /* new password TLV t(0002) */ aim_addtlvtochain_raw(&tl, 0x0002, strlen(newpw), (guint8 *)newpw); /* current password TLV t(0012) */ aim_addtlvtochain_raw(&tl, 0x0012, strlen(curpw), (guint8 *)curpw); aim_writetlvchain(&tx->data, &tl); aim_freetlvchain(&tl); aim_tx_enqueue(sess, tx); return 0; } /* * Request account confirmation. * * This will cause an email to be sent to the address associated with * the account. By following the instructions in the mail, you can * get the TRIAL flag removed from your account. * */ int aim_admin_reqconfirm(aim_session_t *sess, aim_conn_t *conn) { return aim_genericreq_n(sess, conn, 0x0007, 0x0006); } /* * Request a bit of account info. * * The only known valid tag is 0x0011 (email address). * */ int aim_admin_getinfo(aim_session_t *sess, aim_conn_t *conn, guint16 info) { aim_frame_t *tx; aim_snacid_t snacid; if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 14))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0002, 0x0002, 0x0000, NULL, 0); aim_putsnac(&tx->data, 0x0007, 0x0002, 0x0000, snacid); aimbs_put16(&tx->data, info); aimbs_put16(&tx->data, 0x0000); aim_tx_enqueue(sess, tx); return 0; } int aim_admin_setemail(aim_session_t *sess, aim_conn_t *conn, const char *newemail) { aim_frame_t *tx; aim_snacid_t snacid; aim_tlvlist_t *tl = NULL; if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(newemail)))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0); aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid); aim_addtlvtochain_raw(&tl, 0x0011, strlen(newemail), (guint8 *)newemail); aim_writetlvchain(&tx->data, &tl); aim_freetlvchain(&tl); aim_tx_enqueue(sess, tx); return 0; } int aim_admin_setnick(aim_session_t *sess, aim_conn_t *conn, const char *newnick) { aim_frame_t *tx; aim_snacid_t snacid; aim_tlvlist_t *tl = NULL; if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(newnick)))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0); aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid); aim_addtlvtochain_raw(&tl, 0x0001, strlen(newnick), (guint8 *)newnick); aim_writetlvchain(&tx->data, &tl); aim_freetlvchain(&tl); aim_tx_enqueue(sess, tx); return 0; } bitlbee-3.2.1/protocols/oscar/icq.h0000644000175000017500000000415112245474076016577 0ustar wilmerwilmer#ifndef __OSCAR_ICQ_H__ #define __OSCAR_ICQ_H__ #define AIM_CB_FAM_ICQ 0x0015 /* * SNAC Family: ICQ * * Most of these are actually special. */ #define AIM_CB_ICQ_ERROR 0x0001 #define AIM_CB_ICQ_OFFLINEMSG 0x00f0 #define AIM_CB_ICQ_OFFLINEMSGCOMPLETE 0x00f1 #define AIM_CB_ICQ_SIMPLEINFO 0x00f2 #define AIM_CB_ICQ_INFO 0x00f2 /* just transitional */ #define AIM_CB_ICQ_DEFAULT 0xffff struct aim_icq_offlinemsg { guint32 sender; guint16 year; guint8 month, day, hour, minute; guint16 type; char *msg; }; struct aim_icq_simpleinfo { guint32 uin; char *nick; char *first; char *last; char *email; }; struct aim_icq_info { gushort reqid; /* simple */ guint32 uin; /* general and "home" information (0x00c8) */ char *nick; char *first; char *last; char *email; char *homecity; char *homestate; char *homephone; char *homefax; char *homeaddr; char *mobile; char *homezip; gushort homecountry; /* guchar timezone; guchar hideemail; */ /* personal (0x00dc) */ guchar age; guchar unknown; guchar gender; char *personalwebpage; gushort birthyear; guchar birthmonth; guchar birthday; guchar language1; guchar language2; guchar language3; /* work (0x00d2) */ char *workcity; char *workstate; char *workphone; char *workfax; char *workaddr; char *workzip; gushort workcountry; char *workcompany; char *workdivision; char *workposition; char *workwebpage; /* additional personal information (0x00e6) */ char *info; /* email (0x00eb) */ gushort numaddresses; char **email2; /* we keep track of these in a linked list because we're 1337 */ struct aim_icq_info *next; }; int aim_icq_reqofflinemsgs(aim_session_t *sess); int aim_icq_ackofflinemsgs(aim_session_t *sess); int aim_icq_getallinfo(aim_session_t *sess, const char *uin); #endif /* __OSCAR_ICQ_H__ */ bitlbee-3.2.1/protocols/oscar/admin.h0000644000175000017500000000042212245474076017110 0ustar wilmerwilmer#ifndef __OSCAR_ADMIN_H__ #define __OSCAR_ADMIN_H__ #define AIM_CB_FAM_ADM 0x0007 /* * SNAC Family: Administrative Services. */ #define AIM_CB_ADM_ERROR 0x0001 #define AIM_CB_ADM_INFOCHANGE_REPLY 0x0005 #define AIM_CB_ADM_DEFAULT 0xffff #endif /* __OSCAR_ADMIN_H__ */ bitlbee-3.2.1/protocols/oscar/ssi.h0000644000175000017500000000606012245474076016622 0ustar wilmerwilmer#ifndef __OSCAR_SSI_H__ #define __OSCAR_SSI_H__ #define AIM_CB_FAM_SSI 0x0013 /* Server stored information */ /* * SNAC Family: Server-Stored Buddy Lists */ #define AIM_CB_SSI_ERROR 0x0001 #define AIM_CB_SSI_REQRIGHTS 0x0002 #define AIM_CB_SSI_RIGHTSINFO 0x0003 #define AIM_CB_SSI_REQFULLLIST 0x0004 #define AIM_CB_SSI_REQLIST 0x0005 #define AIM_CB_SSI_LIST 0x0006 #define AIM_CB_SSI_ACTIVATE 0x0007 #define AIM_CB_SSI_ADD 0x0008 #define AIM_CB_SSI_MOD 0x0009 #define AIM_CB_SSI_DEL 0x000A #define AIM_CB_SSI_SRVACK 0x000E #define AIM_CB_SSI_NOLIST 0x000F #define AIM_CB_SSI_EDITSTART 0x0011 #define AIM_CB_SSI_EDITSTOP 0x0012 #define AIM_CB_SSI_SENDAUTHREQ 0x0018 #define AIM_CB_SSI_SERVAUTHREQ 0x0019 #define AIM_CB_SSI_SENDAUTHREP 0x001A #define AIM_CB_SSI_SERVAUTHREP 0x001B #define AIM_SSI_TYPE_BUDDY 0x0000 #define AIM_SSI_TYPE_GROUP 0x0001 #define AIM_SSI_TYPE_PERMIT 0x0002 #define AIM_SSI_TYPE_DENY 0x0003 #define AIM_SSI_TYPE_PDINFO 0x0004 #define AIM_SSI_TYPE_PRESENCEPREFS 0x0005 struct aim_ssi_item { char *name; guint16 gid; guint16 bid; guint16 type; void *data; struct aim_ssi_item *next; }; /* These build the actual SNACs and queue them to be sent */ int aim_ssi_reqrights(aim_session_t *sess, aim_conn_t *conn); int aim_ssi_reqalldata(aim_session_t *sess, aim_conn_t *conn); int aim_ssi_enable(aim_session_t *sess, aim_conn_t *conn); int aim_ssi_addmoddel(aim_session_t *sess, aim_conn_t *conn, struct aim_ssi_item **items, unsigned int num, guint16 subtype); int aim_ssi_modbegin(aim_session_t *sess, aim_conn_t *conn); int aim_ssi_modend(aim_session_t *sess, aim_conn_t *conn); /* These handle the local variables */ struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_item *list, guint16 gid, guint16 bid); struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_item *list, char *gn, char *sn, guint16 type); struct aim_ssi_item *aim_ssi_itemlist_findparent(struct aim_ssi_item *list, char *sn); int aim_ssi_getpermdeny(struct aim_ssi_item *list); /* Send packets */ int aim_ssi_addbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num, unsigned int flags); int aim_ssi_addmastergroup(aim_session_t *sess, aim_conn_t *conn); int aim_ssi_addgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num); int aim_ssi_addpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type); int aim_ssi_movebuddy(aim_session_t *sess, aim_conn_t *conn, char *oldgn, char *newgn, char *sn); int aim_ssi_delbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num); int aim_ssi_delmastergroup(aim_session_t *sess, aim_conn_t *conn); int aim_ssi_delgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num); int aim_ssi_delpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type); int aim_ssi_auth_request(aim_session_t *sess, aim_conn_t *conn, char *uin, char *reason); int aim_ssi_auth_reply(aim_session_t *sess, aim_conn_t *conn, char *uin, int yesno, char *reason); #endif /* __OSCAR_SSI_H__ */ bitlbee-3.2.1/protocols/oscar/search.h0000644000175000017500000000012712245474076017267 0ustar wilmerwilmer#ifndef __OSCAR_SEARCH_H__ #define __OSCAR_SEARCH_H__ #endif /* __OSCAR_SEARCH_H__ */ bitlbee-3.2.1/protocols/oscar/tlv.c0000644000175000017500000002747512245474076016641 0ustar wilmerwilmer#include static void freetlv(aim_tlv_t **oldtlv) { if (!oldtlv || !*oldtlv) return; g_free((*oldtlv)->value); g_free(*oldtlv); *oldtlv = NULL; } /** * aim_readtlvchain - Read a TLV chain from a buffer. * @buf: Input buffer * @maxlen: Length of input buffer * * Reads and parses a series of TLV patterns from a data buffer; the * returned structure is manipulatable with the rest of the TLV * routines. When done with a TLV chain, aim_freetlvchain() should * be called to free the dynamic substructures. * * XXX There should be a flag setable here to have the tlvlist contain * bstream references, so that at least the ->value portion of each * element doesn't need to be malloc/memcpy'd. This could prove to be * just as effecient as the in-place TLV parsing used in a couple places * in libfaim. * */ aim_tlvlist_t *aim_readtlvchain(aim_bstream_t *bs) { aim_tlvlist_t *list = NULL, *cur; guint16 type, length; while (aim_bstream_empty(bs)) { type = aimbs_get16(bs); length = aimbs_get16(bs); cur = g_new0(aim_tlvlist_t, 1); cur->tlv = g_new0(aim_tlv_t, 1); cur->tlv->type = type; if ((cur->tlv->length = length)) cur->tlv->value = aimbs_getraw(bs, length); cur->next = list; list = cur; } return list; } /** * aim_freetlvchain - Free a TLV chain structure * @list: Chain to be freed * * Walks the list of TLVs in the passed TLV chain and * frees each one. Note that any references to this data * should be removed before calling this. * */ void aim_freetlvchain(aim_tlvlist_t **list) { aim_tlvlist_t *cur; if (!list || !*list) return; for (cur = *list; cur; ) { aim_tlvlist_t *tmp; freetlv(&cur->tlv); tmp = cur->next; g_free(cur); cur = tmp; } list = NULL; return; } /** * aim_counttlvchain - Count the number of TLVs in a chain * @list: Chain to be counted * * Returns the number of TLVs stored in the passed chain. * */ int aim_counttlvchain(aim_tlvlist_t **list) { aim_tlvlist_t *cur; int count; if (!list || !*list) return 0; for (cur = *list, count = 0; cur; cur = cur->next) count++; return count; } /** * aim_sizetlvchain - Count the number of bytes in a TLV chain * @list: Chain to be sized * * Returns the number of bytes that would be needed to * write the passed TLV chain to a data buffer. * */ int aim_sizetlvchain(aim_tlvlist_t **list) { aim_tlvlist_t *cur; int size; if (!list || !*list) return 0; for (cur = *list, size = 0; cur; cur = cur->next) size += (4 + cur->tlv->length); return size; } /** * aim_addtlvtochain_str - Add a string to a TLV chain * @list: Desination chain (%NULL pointer if empty) * @type: TLV type * @str: String to add * @len: Length of string to add (not including %NULL) * * Adds the passed string as a TLV element of the passed type * to the TLV chain. * */ int aim_addtlvtochain_raw(aim_tlvlist_t **list, const guint16 t, const guint16 l, const guint8 *v) { aim_tlvlist_t *newtlv, *cur; if (!list) return 0; if (!(newtlv = g_new0(aim_tlvlist_t, 1))) return 0; if (!(newtlv->tlv = g_new0(aim_tlv_t, 1))) { g_free(newtlv); return 0; } newtlv->tlv->type = t; if ((newtlv->tlv->length = l)) { newtlv->tlv->value = (guint8 *)g_malloc(newtlv->tlv->length); memcpy(newtlv->tlv->value, v, newtlv->tlv->length); } if (!*list) *list = newtlv; else { for(cur = *list; cur->next; cur = cur->next) ; cur->next = newtlv; } return newtlv->tlv->length; } /** * aim_addtlvtochain8 - Add a 8bit integer to a TLV chain * @list: Destination chain * @type: TLV type to add * @val: Value to add * * Adds a one-byte unsigned integer to a TLV chain. * */ int aim_addtlvtochain8(aim_tlvlist_t **list, const guint16 t, const guint8 v) { guint8 v8[1]; aimutil_put8(v8, v); return aim_addtlvtochain_raw(list, t, 1, v8); } /** * aim_addtlvtochain16 - Add a 16bit integer to a TLV chain * @list: Destination chain * @type: TLV type to add * @val: Value to add * * Adds a two-byte unsigned integer to a TLV chain. * */ int aim_addtlvtochain16(aim_tlvlist_t **list, const guint16 t, const guint16 v) { guint8 v16[2]; aimutil_put16(v16, v); return aim_addtlvtochain_raw(list, t, 2, v16); } /** * aim_addtlvtochain32 - Add a 32bit integer to a TLV chain * @list: Destination chain * @type: TLV type to add * @val: Value to add * * Adds a four-byte unsigned integer to a TLV chain. * */ int aim_addtlvtochain32(aim_tlvlist_t **list, const guint16 t, const guint32 v) { guint8 v32[4]; aimutil_put32(v32, v); return aim_addtlvtochain_raw(list, t, 4, v32); } /** * aim_addtlvtochain_caps - Add a capability block to a TLV chain * @list: Destination chain * @type: TLV type to add * @caps: Bitfield of capability flags to send * * Adds a block of capability blocks to a TLV chain. The bitfield * passed in should be a bitwise %OR of any of the %AIM_CAPS constants: * */ int aim_addtlvtochain_caps(aim_tlvlist_t **list, const guint16 t, const guint32 caps) { guint8 buf[16*16]; /* XXX icky fixed length buffer */ aim_bstream_t bs; if (!caps) return 0; /* nothing there anyway */ aim_bstream_init(&bs, buf, sizeof(buf)); aim_putcap(&bs, caps); return aim_addtlvtochain_raw(list, t, aim_bstream_curpos(&bs), buf); } /** * aim_addtlvtochain_noval - Add a blank TLV to a TLV chain * @list: Destination chain * @type: TLV type to add * * Adds a TLV with a zero length to a TLV chain. * */ int aim_addtlvtochain_noval(aim_tlvlist_t **list, const guint16 t) { return aim_addtlvtochain_raw(list, t, 0, NULL); } /* * Note that the inner TLV chain will not be modifiable as a tlvchain once * it is written using this. Or rather, it can be, but updates won't be * made to this. * * XXX should probably support sublists for real. * * This is so neat. * */ int aim_addtlvtochain_frozentlvlist(aim_tlvlist_t **list, guint16 type, aim_tlvlist_t **tl) { guint8 *buf; int buflen; aim_bstream_t bs; buflen = aim_sizetlvchain(tl); if (buflen <= 0) return 0; if (!(buf = g_malloc(buflen))) return 0; aim_bstream_init(&bs, buf, buflen); aim_writetlvchain(&bs, tl); aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf); g_free(buf); return buflen; } int aim_addtlvtochain_chatroom(aim_tlvlist_t **list, guint16 type, guint16 exchange, const char *roomname, guint16 instance) { guint8 *buf; int buflen; aim_bstream_t bs; buflen = 2 + 1 + strlen(roomname) + 2; if (!(buf = g_malloc(buflen))) return 0; aim_bstream_init(&bs, buf, buflen); aimbs_put16(&bs, exchange); aimbs_put8(&bs, strlen(roomname)); aimbs_putraw(&bs, (guint8 *)roomname, strlen(roomname)); aimbs_put16(&bs, instance); aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf); g_free(buf); return 0; } /** * aim_writetlvchain - Write a TLV chain into a data buffer. * @buf: Destination buffer * @buflen: Maximum number of bytes that will be written to buffer * @list: Source TLV chain * * Copies a TLV chain into a raw data buffer, writing only the number * of bytes specified. This operation does not free the chain; * aim_freetlvchain() must still be called to free up the memory used * by the chain structures. * * XXX clean this up, make better use of bstreams */ int aim_writetlvchain(aim_bstream_t *bs, aim_tlvlist_t **list) { int goodbuflen; aim_tlvlist_t *cur; /* do an initial run to test total length */ for (cur = *list, goodbuflen = 0; cur; cur = cur->next) { goodbuflen += 2 + 2; /* type + len */ goodbuflen += cur->tlv->length; } if (goodbuflen > aim_bstream_empty(bs)) return 0; /* not enough buffer */ /* do the real write-out */ for (cur = *list; cur; cur = cur->next) { aimbs_put16(bs, cur->tlv->type); aimbs_put16(bs, cur->tlv->length); if (cur->tlv->length) aimbs_putraw(bs, cur->tlv->value, cur->tlv->length); } return 1; /* XXX this is a nonsensical return */ } /** * aim_gettlv - Grab the Nth TLV of type type in the TLV list list. * @list: Source chain * @type: Requested TLV type * @nth: Index of TLV of type to get * * Returns a pointer to an aim_tlv_t of the specified type; * %NULL on error. The @nth parameter is specified starting at %1. * In most cases, there will be no more than one TLV of any type * in a chain. * */ aim_tlv_t *aim_gettlv(aim_tlvlist_t *list, const guint16 t, const int n) { aim_tlvlist_t *cur; int i; for (cur = list, i = 0; cur; cur = cur->next) { if (cur && cur->tlv) { if (cur->tlv->type == t) i++; if (i >= n) return cur->tlv; } } return NULL; } /** * aim_gettlv_str - Retrieve the Nth TLV in chain as a string. * @list: Source TLV chain * @type: TLV type to search for * @nth: Index of TLV to return * * Same as aim_gettlv(), except that the return value is a %NULL- * terminated string instead of an aim_tlv_t. This is a * dynamic buffer and must be freed by the caller. * */ char *aim_gettlv_str(aim_tlvlist_t *list, const guint16 t, const int n) { aim_tlv_t *tlv; char *newstr; if (!(tlv = aim_gettlv(list, t, n))) return NULL; newstr = (char *) g_malloc(tlv->length + 1); memcpy(newstr, tlv->value, tlv->length); *(newstr + tlv->length) = '\0'; return newstr; } /** * aim_gettlv8 - Retrieve the Nth TLV in chain as a 8bit integer. * @list: Source TLV chain * @type: TLV type to search for * @nth: Index of TLV to return * * Same as aim_gettlv(), except that the return value is a * 8bit integer instead of an aim_tlv_t. * */ guint8 aim_gettlv8(aim_tlvlist_t *list, const guint16 t, const int n) { aim_tlv_t *tlv; if (!(tlv = aim_gettlv(list, t, n))) return 0; /* erm */ return aimutil_get8(tlv->value); } /** * aim_gettlv16 - Retrieve the Nth TLV in chain as a 16bit integer. * @list: Source TLV chain * @type: TLV type to search for * @nth: Index of TLV to return * * Same as aim_gettlv(), except that the return value is a * 16bit integer instead of an aim_tlv_t. * */ guint16 aim_gettlv16(aim_tlvlist_t *list, const guint16 t, const int n) { aim_tlv_t *tlv; if (!(tlv = aim_gettlv(list, t, n))) return 0; /* erm */ return aimutil_get16(tlv->value); } /** * aim_gettlv32 - Retrieve the Nth TLV in chain as a 32bit integer. * @list: Source TLV chain * @type: TLV type to search for * @nth: Index of TLV to return * * Same as aim_gettlv(), except that the return value is a * 32bit integer instead of an aim_tlv_t. * */ guint32 aim_gettlv32(aim_tlvlist_t *list, const guint16 t, const int n) { aim_tlv_t *tlv; if (!(tlv = aim_gettlv(list, t, n))) return 0; /* erm */ return aimutil_get32(tlv->value); } #if 0 /** * aim_puttlv_8 - Write a one-byte TLV. * @buf: Destination buffer * @t: TLV type * @v: Value * * Writes a TLV with a one-byte integer value portion. * */ int aim_puttlv_8(guint8 *buf, const guint16 t, const guint8 v) { guint8 v8[1]; aimutil_put8(v8, v); return aim_puttlv_raw(buf, t, 1, v8); } /** * aim_puttlv_16 - Write a two-byte TLV. * @buf: Destination buffer * @t: TLV type * @v: Value * * Writes a TLV with a two-byte integer value portion. * */ int aim_puttlv_16(guint8 *buf, const guint16 t, const guint16 v) { guint8 v16[2]; aimutil_put16(v16, v); return aim_puttlv_raw(buf, t, 2, v16); } /** * aim_puttlv_32 - Write a four-byte TLV. * @buf: Destination buffer * @t: TLV type * @v: Value * * Writes a TLV with a four-byte integer value portion. * */ int aim_puttlv_32(guint8 *buf, const guint16 t, const guint32 v) { guint8 v32[4]; aimutil_put32(v32, v); return aim_puttlv_raw(buf, t, 4, v32); } /** * aim_puttlv_raw - Write a raw TLV. * @buf: Destination buffer * @t: TLV type * @l: Length of string * @v: String to write * * Writes a TLV with a raw value portion. (Only the first @l * bytes of the passed buffer will be written, which should not * include a terminating NULL.) * */ int aim_puttlv_raw(guint8 *buf, const guint16 t, const guint16 l, const guint8 *v) { int i; i = aimutil_put16(buf, t); i += aimutil_put16(buf+i, l); if (l) memcpy(buf+i, v, l); i += l; return i; } #endif bitlbee-3.2.1/protocols/oscar/oscar.c0000644000175000017500000022153512245474076017134 0ustar wilmerwilmer/* * gaim * * Some code copyright (C) 2002-2006, Jelmer Vernooij * and the BitlBee team. * Some code copyright (C) 1998-1999, Mark Spencer * libfaim code copyright 1998, 1999 Adam Fritzler * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include #include #include "nogaim.h" #include "bitlbee.h" #include "proxy.h" #include "sock.h" #include "aim.h" #include "icq.h" #include "bos.h" #include "ssi.h" #include "im.h" #include "info.h" #include "buddylist.h" #include "chat.h" #include "chatnav.h" /* constants to identify proto_opts */ #define USEROPT_AUTH 0 #define USEROPT_AUTHPORT 1 #define UC_AOL 0x02 #define UC_ADMIN 0x04 #define UC_UNCONFIRMED 0x08 #define UC_NORMAL 0x10 #define UC_AB 0x20 #define UC_WIRELESS 0x40 #define AIMHASHDATA "http://gaim.sourceforge.net/aim_data.php3" #define OSCAR_GROUP "Friends" #define BUF_LEN 2048 #define BUF_LONG ( BUF_LEN * 2 ) /* Don't know if support for UTF8 is really working. For now it's UTF16 here. static int gaim_caps = AIM_CAPS_UTF8; */ static int gaim_caps = AIM_CAPS_INTEROP | AIM_CAPS_ICHAT | AIM_CAPS_ICQSERVERRELAY | AIM_CAPS_CHAT; static guint8 gaim_features[] = {0x01, 0x01, 0x01, 0x02}; struct oscar_data { aim_session_t *sess; aim_conn_t *conn; guint cnpa; guint paspa; GSList *create_rooms; gboolean conf; gboolean reqemail; gboolean setemail; char *email; gboolean setnick; char *newsn; gboolean chpass; char *oldp; char *newp; GSList *oscar_chats; gboolean killme, no_reconnect; gboolean icq; GSList *evilhack; GHashTable *ips; struct { guint maxbuddies; /* max users you can watch */ guint maxwatchers; /* max users who can watch you */ guint maxpermits; /* max users on permit list */ guint maxdenies; /* max users on deny list */ guint maxsiglen; /* max size (bytes) of profile */ guint maxawaymsglen; /* max size (bytes) of posted away message */ } rights; }; struct create_room { char *name; int exchange; }; struct chat_connection { char *name; char *show; /* AOL did something funny to us */ guint16 exchange; guint16 instance; int fd; /* this is redundant since we have the conn below */ aim_conn_t *conn; int inpa; int id; struct im_connection *ic; /* i hate this. */ struct groupchat *cnv; /* bah. */ int maxlen; int maxvis; }; struct ask_direct { struct im_connection *ic; char *sn; char ip[64]; guint8 cookie[8]; }; struct icq_auth { struct im_connection *ic; guint32 uin; }; static char *extract_name(const char *name) { char *tmp; int i, j; char *x = strchr(name, '-'); if (!x) return g_strdup(name); x = strchr(++x, '-'); if (!x) return g_strdup(name); tmp = g_strdup(++x); for (i = 0, j = 0; x[i]; i++) { char hex[3]; if (x[i] != '%') { tmp[j++] = x[i]; continue; } strncpy(hex, x + ++i, 2); hex[2] = 0; i++; tmp[j++] = (char)strtol(hex, NULL, 16); } tmp[j] = 0; return tmp; } static struct chat_connection *find_oscar_chat_by_conn(struct im_connection *ic, aim_conn_t *conn) { GSList *g = ((struct oscar_data *)ic->proto_data)->oscar_chats; struct chat_connection *c = NULL; while (g) { c = (struct chat_connection *)g->data; if (c->conn == conn) break; g = g->next; c = NULL; } return c; } static int gaim_parse_auth_resp (aim_session_t *, aim_frame_t *, ...); static int gaim_parse_login (aim_session_t *, aim_frame_t *, ...); static int gaim_parse_logout (aim_session_t *, aim_frame_t *, ...); static int gaim_handle_redirect (aim_session_t *, aim_frame_t *, ...); static int gaim_parse_oncoming (aim_session_t *, aim_frame_t *, ...); static int gaim_parse_offgoing (aim_session_t *, aim_frame_t *, ...); static int gaim_parse_incoming_im(aim_session_t *, aim_frame_t *, ...); static int gaim_parse_misses (aim_session_t *, aim_frame_t *, ...); static int gaim_parse_motd (aim_session_t *, aim_frame_t *, ...); static int gaim_chatnav_info (aim_session_t *, aim_frame_t *, ...); static int gaim_chat_join (aim_session_t *, aim_frame_t *, ...); static int gaim_chat_leave (aim_session_t *, aim_frame_t *, ...); static int gaim_chat_info_update (aim_session_t *, aim_frame_t *, ...); static int gaim_chat_incoming_msg(aim_session_t *, aim_frame_t *, ...); static int gaim_parse_ratechange (aim_session_t *, aim_frame_t *, ...); static int gaim_bosrights (aim_session_t *, aim_frame_t *, ...); static int conninitdone_bos (aim_session_t *, aim_frame_t *, ...); static int conninitdone_admin (aim_session_t *, aim_frame_t *, ...); static int conninitdone_chat (aim_session_t *, aim_frame_t *, ...); static int conninitdone_chatnav (aim_session_t *, aim_frame_t *, ...); static int gaim_parse_msgerr (aim_session_t *, aim_frame_t *, ...); static int gaim_parse_locaterights(aim_session_t *, aim_frame_t *, ...); static int gaim_parse_buddyrights(aim_session_t *, aim_frame_t *, ...); static int gaim_parse_locerr (aim_session_t *, aim_frame_t *, ...); static int gaim_icbm_param_info (aim_session_t *, aim_frame_t *, ...); static int gaim_parse_genericerr (aim_session_t *, aim_frame_t *, ...); static int gaim_selfinfo (aim_session_t *, aim_frame_t *, ...); static int gaim_offlinemsg (aim_session_t *, aim_frame_t *, ...); static int gaim_offlinemsgdone (aim_session_t *, aim_frame_t *, ...); static int gaim_ssi_parserights (aim_session_t *, aim_frame_t *, ...); static int gaim_ssi_parselist (aim_session_t *, aim_frame_t *, ...); static int gaim_ssi_parseack (aim_session_t *, aim_frame_t *, ...); static int gaim_parsemtn (aim_session_t *, aim_frame_t *, ...); static int gaim_icqinfo (aim_session_t *, aim_frame_t *, ...); static int gaim_parseaiminfo (aim_session_t *, aim_frame_t *, ...); static char *msgerrreason[] = { "Invalid error", "Invalid SNAC", "Rate to host", "Rate to client", "Not logged in", "Service unavailable", "Service not defined", "Obsolete SNAC", "Not supported by host", "Not supported by client", "Refused by client", "Reply too big", "Responses lost", "Request denied", "Busted SNAC payload", "Insufficient rights", "In local permit/deny", "Too evil (sender)", "Too evil (receiver)", "User temporarily unavailable", "No match", "List overflow", "Request ambiguous", "Queue full", "Not while on AOL" }; static int msgerrreasonlen = 25; /* Hurray, this function is NOT thread-safe \o/ */ static char *normalize(const char *s) { static char buf[BUF_LEN]; char *t, *u; int x = 0; g_return_val_if_fail((s != NULL), NULL); u = t = g_ascii_strdown(s, -1); while (*t && (x < BUF_LEN - 1)) { if (*t != ' ' && *t != '!') { buf[x] = *t; x++; } t++; } buf[x] = '\0'; g_free(u); return buf; } static gboolean oscar_callback(gpointer data, gint source, b_input_condition condition) { aim_conn_t *conn = (aim_conn_t *)data; aim_session_t *sess = aim_conn_getsess(conn); struct im_connection *ic = sess ? sess->aux_data : NULL; struct oscar_data *odata; if (!ic) { /* ic is null. we return, else we seg SIGSEG on next line. */ return FALSE; } if (!g_slist_find(get_connections(), ic)) { /* oh boy. this is probably bad. i guess the only thing we * can really do is return? */ return FALSE; } odata = (struct oscar_data *)ic->proto_data; if (condition & B_EV_IO_READ) { if (aim_get_command(odata->sess, conn) >= 0) { aim_rxdispatch(odata->sess); if (odata->killme) imc_logout(ic, !odata->no_reconnect); } else { if ((conn->type == AIM_CONN_TYPE_BOS) || !(aim_getconn_type(odata->sess, AIM_CONN_TYPE_BOS))) { imcb_error(ic, _("Disconnected.")); imc_logout(ic, TRUE); } else if (conn->type == AIM_CONN_TYPE_CHAT) { struct chat_connection *c = find_oscar_chat_by_conn(ic, conn); c->conn = NULL; if (c->inpa > 0) b_event_remove(c->inpa); c->inpa = 0; c->fd = -1; aim_conn_kill(odata->sess, &conn); imcb_error(sess->aux_data, _("You have been disconnected from chat room %s."), c->name); } else if (conn->type == AIM_CONN_TYPE_CHATNAV) { if (odata->cnpa > 0) b_event_remove(odata->cnpa); odata->cnpa = 0; while (odata->create_rooms) { struct create_room *cr = odata->create_rooms->data; g_free(cr->name); odata->create_rooms = g_slist_remove(odata->create_rooms, cr); g_free(cr); imcb_error(sess->aux_data, _("Chat is currently unavailable")); } aim_conn_kill(odata->sess, &conn); } else if (conn->type == AIM_CONN_TYPE_AUTH) { if (odata->paspa > 0) b_event_remove(odata->paspa); odata->paspa = 0; aim_conn_kill(odata->sess, &conn); } else { aim_conn_kill(odata->sess, &conn); } } } else { /* WTF??? */ return FALSE; } return TRUE; } static gboolean oscar_login_connect(gpointer data, gint source, b_input_condition cond) { struct im_connection *ic = data; struct oscar_data *odata; aim_session_t *sess; aim_conn_t *conn; if (!g_slist_find(get_connections(), ic)) { closesocket(source); return FALSE; } odata = ic->proto_data; sess = odata->sess; conn = aim_getconn_type_all(sess, AIM_CONN_TYPE_AUTH); if (source < 0) { imcb_error(ic, _("Couldn't connect to host")); imc_logout(ic, TRUE); return FALSE; } aim_conn_completeconnect(sess, conn); ic->inpa = b_input_add(conn->fd, B_EV_IO_READ, oscar_callback, conn); return FALSE; } static void oscar_init(account_t *acc) { set_t *s; gboolean icq = isdigit(acc->user[0]); if (icq) { set_add(&acc->set, "ignore_auth_requests", "false", set_eval_bool, acc); set_add(&acc->set, "old_icq_auth", "false", set_eval_bool, acc); } s = set_add(&acc->set, "server", icq ? AIM_DEFAULT_LOGIN_SERVER_ICQ : AIM_DEFAULT_LOGIN_SERVER_AIM, set_eval_account, acc); s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY; if (icq) { s = set_add(&acc->set, "web_aware", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; } acc->flags |= ACC_FLAG_AWAY_MESSAGE; } static void oscar_login(account_t *acc) { aim_session_t *sess; aim_conn_t *conn; struct im_connection *ic = imcb_new(acc); struct oscar_data *odata = ic->proto_data = g_new0(struct oscar_data, 1); if (isdigit(acc->user[0])) odata->icq = TRUE; else ic->flags |= OPT_DOES_HTML; sess = g_new0(aim_session_t, 1); aim_session_init(sess, AIM_SESS_FLAGS_NONBLOCKCONNECT, 0); /* we need an immediate queue because we don't use a while-loop to * see if things need to be sent. */ aim_tx_setenqueue(sess, AIM_TX_IMMEDIATE, NULL); odata->sess = sess; sess->aux_data = ic; conn = aim_newconn(sess, AIM_CONN_TYPE_AUTH, NULL); if (conn == NULL) { imcb_error(ic, _("Unable to login to AIM")); imc_logout(ic, TRUE); return; } imcb_log(ic, _("Signon: %s"), ic->acc->user); aim_conn_addhandler(sess, conn, 0x0017, 0x0007, gaim_parse_login, 0); aim_conn_addhandler(sess, conn, 0x0017, 0x0003, gaim_parse_auth_resp, 0); conn->status |= AIM_CONN_STATUS_INPROGRESS; conn->fd = proxy_connect(set_getstr(&acc->set, "server"), AIM_LOGIN_PORT, oscar_login_connect, ic); if (conn->fd < 0) { imcb_error(ic, _("Couldn't connect to host")); imc_logout(ic, TRUE); return; } aim_request_login(sess, conn, ic->acc->user); } static void oscar_logout(struct im_connection *ic) { struct oscar_data *odata = (struct oscar_data *)ic->proto_data; while (odata->oscar_chats) { struct chat_connection *n = odata->oscar_chats->data; if (n->inpa > 0) b_event_remove(n->inpa); g_free(n->name); g_free(n->show); odata->oscar_chats = g_slist_remove(odata->oscar_chats, n); g_free(n); } while (odata->create_rooms) { struct create_room *cr = odata->create_rooms->data; g_free(cr->name); odata->create_rooms = g_slist_remove(odata->create_rooms, cr); g_free(cr); } if (odata->ips) g_hash_table_destroy(odata->ips); if (odata->email) g_free(odata->email); if (odata->newp) g_free(odata->newp); if (odata->oldp) g_free(odata->oldp); if (ic->inpa > 0) b_event_remove(ic->inpa); if (odata->cnpa > 0) b_event_remove(odata->cnpa); if (odata->paspa > 0) b_event_remove(odata->paspa); aim_session_kill(odata->sess); g_free(odata->sess); odata->sess = NULL; g_free(ic->proto_data); ic->proto_data = NULL; } static gboolean oscar_bos_connect(gpointer data, gint source, b_input_condition cond) { struct im_connection *ic = data; struct oscar_data *odata; aim_session_t *sess; aim_conn_t *bosconn; if (!g_slist_find(get_connections(), ic)) { closesocket(source); return FALSE; } odata = ic->proto_data; sess = odata->sess; bosconn = odata->conn; if (source < 0) { imcb_error(ic, _("Could Not Connect")); imc_logout(ic, TRUE); return FALSE; } aim_conn_completeconnect(sess, bosconn); ic->inpa = b_input_add(bosconn->fd, B_EV_IO_READ, oscar_callback, bosconn); imcb_log(ic, _("Connection established, cookie sent")); return FALSE; } static int gaim_parse_auth_resp(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; struct aim_authresp_info *info; int i; char *host; int port; aim_conn_t *bosconn; struct im_connection *ic = sess->aux_data; struct oscar_data *od = ic->proto_data; port = AIM_LOGIN_PORT; va_start(ap, fr); info = va_arg(ap, struct aim_authresp_info *); va_end(ap); if (info->errorcode || !info->bosip || !info->cookie) { switch (info->errorcode) { case 0x05: /* Incorrect nick/password */ imcb_error(ic, _("Incorrect nickname or password.")); { int max = od->icq ? 8 : 16; if (strlen(ic->acc->pass) > max) imcb_log(ic, "Note that the maximum password " "length supported by this protocol is " "%d characters, try logging in using " "a shorter password.", max); } // plugin_event(event_error, (void *)980, 0, 0, 0); break; case 0x11: /* Suspended account */ imcb_error(ic, _("Your account is currently suspended.")); break; case 0x18: /* connecting too frequently */ od->no_reconnect = TRUE; imcb_error(ic, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer.")); break; case 0x1c: /* client too old */ imcb_error(ic, _("The client version you are using is too old. Please upgrade at " WEBSITE)); break; default: imcb_error(ic, _("Authentication Failed")); break; } od->killme = TRUE; return 1; } aim_conn_kill(sess, &fr->conn); bosconn = aim_newconn(sess, AIM_CONN_TYPE_BOS, NULL); if (bosconn == NULL) { imcb_error(ic, _("Internal Error")); od->killme = TRUE; return 0; } aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_bos, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BOS, AIM_CB_BOS_RIGHTS, gaim_bosrights, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ACK, AIM_CB_ACK_ACK, NULL, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_REDIRECT, gaim_handle_redirect, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_RIGHTSINFO, gaim_parse_locaterights, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_RIGHTSINFO, gaim_parse_buddyrights, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_ONCOMING, gaim_parse_oncoming, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_OFFGOING, gaim_parse_offgoing, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_INCOMING, gaim_parse_incoming_im, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_ERROR, gaim_parse_locerr, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_MISSEDCALL, gaim_parse_misses, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_RATECHANGE, gaim_parse_ratechange, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_ERROR, gaim_parse_msgerr, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_MOTD, gaim_parse_motd, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_PARAMINFO, gaim_icbm_param_info, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_ERROR, gaim_parse_genericerr, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_ERROR, gaim_parse_genericerr, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BOS, AIM_CB_BOS_ERROR, gaim_parse_genericerr, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_SELFINFO, gaim_selfinfo, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG, gaim_offlinemsg, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSGCOMPLETE, gaim_offlinemsgdone, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_INFO, gaim_icqinfo, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_RIGHTSINFO, gaim_ssi_parserights, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_LIST, gaim_ssi_parselist, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_SRVACK, gaim_ssi_parseack, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_USERINFO, gaim_parseaiminfo, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_MTN, gaim_parsemtn, 0); aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNERR, gaim_parse_logout, 0); ((struct oscar_data *)ic->proto_data)->conn = bosconn; for (i = 0; i < (int)strlen(info->bosip); i++) { if (info->bosip[i] == ':') { port = atoi(&(info->bosip[i+1])); break; } } host = g_strndup(info->bosip, i); bosconn->status |= AIM_CONN_STATUS_INPROGRESS; bosconn->fd = proxy_connect(host, port, oscar_bos_connect, ic); g_free(host); if (bosconn->fd < 0) { imcb_error(ic, _("Could Not Connect")); od->killme = TRUE; return 0; } aim_sendcookie(sess, bosconn, info->cookie); b_event_remove(ic->inpa); return 1; } /* size of icbmui.ocm, the largest module in AIM 3.5 */ #define AIM_MAX_FILE_SIZE 98304 static int gaim_parse_login(aim_session_t *sess, aim_frame_t *fr, ...) { #if 0 struct client_info_s info = {"gaim", 4, 1, 2010, "us", "en", 0x0004, 0x0000, 0x04b}; #else struct client_info_s info = AIM_CLIENTINFO_KNOWNGOOD; #endif char *key; va_list ap; struct im_connection *ic = sess->aux_data; va_start(ap, fr); key = va_arg(ap, char *); va_end(ap); aim_send_login(sess, fr->conn, ic->acc->user, ic->acc->pass, &info, key); return 1; } static int gaim_parse_logout(aim_session_t *sess, aim_frame_t *fr, ...) { struct im_connection *ic = sess->aux_data; struct oscar_data *odata = ic->proto_data; int code; va_list ap; va_start(ap, fr); code = va_arg(ap, int); va_end(ap); imcb_error( ic, "Connection aborted by server: %s", code == 1 ? "someone else logged in with your account" : "unknown reason" ); /* Tell BitlBee to disable auto_reconnect if code == 1, since that means a concurrent login somewhere else. */ odata->no_reconnect = code == 1; /* DO NOT log out here! Just tell the callback to do it. */ odata->killme = TRUE; return 1; } static int conninitdone_chat(aim_session_t *sess, aim_frame_t *fr, ...) { struct im_connection *ic = sess->aux_data; struct chat_connection *chatcon; struct groupchat *c = NULL; static int id = 1; aim_conn_addhandler(sess, fr->conn, 0x000e, 0x0001, gaim_parse_genericerr, 0); aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_USERJOIN, gaim_chat_join, 0); aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_USERLEAVE, gaim_chat_leave, 0); aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_ROOMINFOUPDATE, gaim_chat_info_update, 0); aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_INCOMINGMSG, gaim_chat_incoming_msg, 0); aim_clientready(sess, fr->conn); chatcon = find_oscar_chat_by_conn(ic, fr->conn); chatcon->id = id; c = bee_chat_by_title(ic->bee, ic, chatcon->show); if (c && !c->data) chatcon->cnv = c; else chatcon->cnv = imcb_chat_new(ic, chatcon->show); chatcon->cnv->data = chatcon; return 1; } static int conninitdone_chatnav(aim_session_t *sess, aim_frame_t *fr, ...) { aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CTN, AIM_CB_CTN_ERROR, gaim_parse_genericerr, 0); aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CTN, AIM_CB_CTN_INFO, gaim_chatnav_info, 0); aim_clientready(sess, fr->conn); aim_chatnav_reqrights(sess, fr->conn); return 1; } static gboolean oscar_chatnav_connect(gpointer data, gint source, b_input_condition cond) { struct im_connection *ic = data; struct oscar_data *odata; aim_session_t *sess; aim_conn_t *tstconn; if (!g_slist_find(get_connections(), ic)) { closesocket(source); return FALSE; } odata = ic->proto_data; sess = odata->sess; tstconn = aim_getconn_type_all(sess, AIM_CONN_TYPE_CHATNAV); if (source < 0) { aim_conn_kill(sess, &tstconn); return FALSE; } aim_conn_completeconnect(sess, tstconn); odata->cnpa = b_input_add(tstconn->fd, B_EV_IO_READ, oscar_callback, tstconn); return FALSE; } static gboolean oscar_auth_connect(gpointer data, gint source, b_input_condition cond) { struct im_connection *ic = data; struct oscar_data *odata; aim_session_t *sess; aim_conn_t *tstconn; if (!g_slist_find(get_connections(), ic)) { closesocket(source); return FALSE; } odata = ic->proto_data; sess = odata->sess; tstconn = aim_getconn_type_all(sess, AIM_CONN_TYPE_AUTH); if (source < 0) { aim_conn_kill(sess, &tstconn); return FALSE; } aim_conn_completeconnect(sess, tstconn); odata->paspa = b_input_add(tstconn->fd, B_EV_IO_READ, oscar_callback, tstconn); return FALSE; } static gboolean oscar_chat_connect(gpointer data, gint source, b_input_condition cond) { struct chat_connection *ccon = data; struct im_connection *ic = ccon->ic; struct oscar_data *odata; aim_session_t *sess; aim_conn_t *tstconn; if (!g_slist_find(get_connections(), ic)) { closesocket(source); g_free(ccon->show); g_free(ccon->name); g_free(ccon); return FALSE; } odata = ic->proto_data; sess = odata->sess; tstconn = ccon->conn; if (source < 0) { aim_conn_kill(sess, &tstconn); g_free(ccon->show); g_free(ccon->name); g_free(ccon); return FALSE; } aim_conn_completeconnect(sess, ccon->conn); ccon->inpa = b_input_add(tstconn->fd, B_EV_IO_READ, oscar_callback, tstconn); odata->oscar_chats = g_slist_append(odata->oscar_chats, ccon); return FALSE; } /* Hrmph. I don't know how to make this look better. --mid */ static int gaim_handle_redirect(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; struct aim_redirect_data *redir; struct im_connection *ic = sess->aux_data; aim_conn_t *tstconn; int i; char *host; int port; va_start(ap, fr); redir = va_arg(ap, struct aim_redirect_data *); va_end(ap); port = AIM_LOGIN_PORT; for (i = 0; i < (int)strlen(redir->ip); i++) { if (redir->ip[i] == ':') { port = atoi(&(redir->ip[i+1])); break; } } host = g_strndup(redir->ip, i); switch(redir->group) { case 0x7: /* Authorizer */ tstconn = aim_newconn(sess, AIM_CONN_TYPE_AUTH, NULL); if (tstconn == NULL) { g_free(host); return 1; } aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_admin, 0); // aim_conn_addhandler(sess, tstconn, 0x0007, 0x0003, gaim_info_change, 0); // aim_conn_addhandler(sess, tstconn, 0x0007, 0x0005, gaim_info_change, 0); // aim_conn_addhandler(sess, tstconn, 0x0007, 0x0007, gaim_account_confirm, 0); tstconn->status |= AIM_CONN_STATUS_INPROGRESS; tstconn->fd = proxy_connect(host, port, oscar_auth_connect, ic); if (tstconn->fd < 0) { aim_conn_kill(sess, &tstconn); g_free(host); return 1; } aim_sendcookie(sess, tstconn, redir->cookie); break; case 0xd: /* ChatNav */ tstconn = aim_newconn(sess, AIM_CONN_TYPE_CHATNAV, NULL); if (tstconn == NULL) { g_free(host); return 1; } aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_chatnav, 0); tstconn->status |= AIM_CONN_STATUS_INPROGRESS; tstconn->fd = proxy_connect(host, port, oscar_chatnav_connect, ic); if (tstconn->fd < 0) { aim_conn_kill(sess, &tstconn); g_free(host); return 1; } aim_sendcookie(sess, tstconn, redir->cookie); break; case 0xe: /* Chat */ { struct chat_connection *ccon; tstconn = aim_newconn(sess, AIM_CONN_TYPE_CHAT, NULL); if (tstconn == NULL) { g_free(host); return 1; } aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_chat, 0); ccon = g_new0(struct chat_connection, 1); ccon->conn = tstconn; ccon->ic = ic; ccon->fd = -1; ccon->name = g_strdup(redir->chat.room); ccon->exchange = redir->chat.exchange; ccon->instance = redir->chat.instance; ccon->show = extract_name(redir->chat.room); ccon->conn->status |= AIM_CONN_STATUS_INPROGRESS; ccon->conn->fd = proxy_connect(host, port, oscar_chat_connect, ccon); if (ccon->conn->fd < 0) { aim_conn_kill(sess, &tstconn); g_free(host); g_free(ccon->show); g_free(ccon->name); g_free(ccon); return 1; } aim_sendcookie(sess, tstconn, redir->cookie); } break; default: /* huh? */ break; } g_free(host); return 1; } static int gaim_parse_oncoming(aim_session_t *sess, aim_frame_t *fr, ...) { struct im_connection *ic = sess->aux_data; struct oscar_data *od = ic->proto_data; aim_userinfo_t *info; time_t time_idle = 0, signon = 0; int flags = OPT_LOGGED_IN; char *tmp, *state_string = NULL; va_list ap; va_start(ap, fr); info = va_arg(ap, aim_userinfo_t *); va_end(ap); if ((!od->icq) && (info->present & AIM_USERINFO_PRESENT_FLAGS)) { if (info->flags & AIM_FLAG_AWAY) flags |= OPT_AWAY; } /* Maybe this should be done just for AIM contacts, not sure. */ if (info->flags & AIM_FLAG_WIRELESS) flags |= OPT_MOBILE; if (info->present & AIM_USERINFO_PRESENT_ICQEXTSTATUS) { if (!(info->icqinfo.status & AIM_ICQ_STATE_CHAT) && (info->icqinfo.status != AIM_ICQ_STATE_NORMAL)) { flags |= OPT_AWAY; } if( info->icqinfo.status & AIM_ICQ_STATE_DND ) state_string = "Do Not Disturb"; else if( info->icqinfo.status & AIM_ICQ_STATE_OUT ) state_string = "Not Available"; else if( info->icqinfo.status & AIM_ICQ_STATE_BUSY ) state_string = "Occupied"; else if( info->icqinfo.status & AIM_ICQ_STATE_INVISIBLE ) state_string = "Invisible"; } if (info->present & AIM_USERINFO_PRESENT_IDLE) { time(&time_idle); time_idle -= info->idletime*60; } if (info->present & AIM_USERINFO_PRESENT_SESSIONLEN) signon = time(NULL) - info->sessionlen; if (info->present & AIM_USERINFO_PRESENT_ICQIPADDR) { uint32_t *uin = g_new0(uint32_t, 1); if (od->ips == NULL) od->ips = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, NULL); if (sscanf(info->sn, "%d", uin) == 1) g_hash_table_insert(od->ips, uin, (gpointer) (long) info->icqinfo.ipaddr); } tmp = normalize(info->sn); imcb_buddy_status(ic, tmp, flags, state_string, NULL); imcb_buddy_times(ic, tmp, signon, time_idle); return 1; } static int gaim_parse_offgoing(aim_session_t *sess, aim_frame_t *fr, ...) { aim_userinfo_t *info; va_list ap; struct im_connection *ic = sess->aux_data; va_start(ap, fr); info = va_arg(ap, aim_userinfo_t *); va_end(ap); imcb_buddy_status(ic, normalize(info->sn), 0, NULL, NULL ); return 1; } static int incomingim_chan1(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch1_args *args) { char *tmp = g_malloc(BUF_LONG + 1); struct im_connection *ic = sess->aux_data; int flags = 0; if (args->icbmflags & AIM_IMFLAGS_AWAY) flags |= OPT_AWAY; if ((args->icbmflags & AIM_IMFLAGS_UNICODE) || (args->icbmflags & AIM_IMFLAGS_ISO_8859_1)) { char *src; if (args->icbmflags & AIM_IMFLAGS_UNICODE) src = "UCS-2BE"; else src = "ISO8859-1"; /* Try to use iconv first to convert the message to UTF8 - which is what BitlBee expects */ if (do_iconv(src, "UTF-8", args->msg, tmp, args->msglen, BUF_LONG) >= 0) { // Successfully converted! } else if (args->icbmflags & AIM_IMFLAGS_UNICODE) { int i; for (i = 0, tmp[0] = '\0'; i < args->msglen; i += 2) { unsigned short uni; uni = ((args->msg[i] & 0xff) << 8) | (args->msg[i+1] & 0xff); if ((uni < 128) || ((uni >= 160) && (uni <= 255))) { /* ISO 8859-1 */ g_snprintf(tmp+strlen(tmp), BUF_LONG-strlen(tmp), "%c", uni); } else { /* something else, do UNICODE entity */ g_snprintf(tmp+strlen(tmp), BUF_LONG-strlen(tmp), "&#%04x;", uni); } } } else { g_snprintf(tmp, BUF_LONG, "%s", args->msg); } } else if (args->mpmsg.numparts == 0) { g_snprintf(tmp, BUF_LONG, "%s", args->msg); } else { aim_mpmsg_section_t *part; *tmp = 0; for (part = args->mpmsg.parts; part; part = part->next) { if (part->data) { g_strlcat(tmp, (char*) part->data, BUF_LONG); g_strlcat(tmp, "\n", BUF_LONG); } } } strip_linefeed(tmp); imcb_buddy_msg(ic, normalize(userinfo->sn), tmp, flags, 0); g_free(tmp); return 1; } void oscar_accept_chat(void *data); void oscar_reject_chat(void *data); static int incomingim_chan2(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args) { struct im_connection *ic = sess->aux_data; if (args->status != AIM_RENDEZVOUS_PROPOSE) return 1; if (args->reqclass & AIM_CAPS_CHAT) { char *name = extract_name(args->info.chat.roominfo.name); int *exch = g_new0(int, 1); GList *m = NULL; char txt[1024]; struct aim_chat_invitation * inv = g_new0(struct aim_chat_invitation, 1); m = g_list_append(m, g_strdup(name ? name : args->info.chat.roominfo.name)); *exch = args->info.chat.roominfo.exchange; m = g_list_append(m, exch); g_snprintf(txt, 1024, "Got an invitation to chatroom %s from %s: %s", name, userinfo->sn, args->msg); inv->ic = ic; inv->exchange = *exch; inv->name = g_strdup(name); imcb_ask(ic, txt, inv, oscar_accept_chat, oscar_reject_chat); if (name) g_free(name); } else if (args->reqclass & AIM_CAPS_ICQRTF) { // TODO: constify char text[strlen(args->info.rtfmsg.rtfmsg)+1]; strncpy(text, args->info.rtfmsg.rtfmsg, sizeof(text)); imcb_buddy_msg(ic, normalize(userinfo->sn), text, 0, 0); } return 1; } static void gaim_icq_authgrant(void *data_) { struct icq_auth *data = data_; char *uin; struct oscar_data *od = (struct oscar_data *)data->ic->proto_data; uin = g_strdup_printf("%u", data->uin); aim_ssi_auth_reply(od->sess, od->conn, uin, 1, ""); // char *message = 0; // aim_send_im_ch4(od->sess, uin, AIM_ICQMSG_AUTHGRANTED, &message); imcb_ask_add(data->ic, uin, NULL); g_free(uin); g_free(data); } static void gaim_icq_authdeny(void *data_) { struct icq_auth *data = data_; char *uin, *message; struct oscar_data *od = (struct oscar_data *)data->ic->proto_data; uin = g_strdup_printf("%u", data->uin); message = g_strdup_printf("No reason given."); aim_ssi_auth_reply(od->sess, od->conn, uin, 0, ""); // aim_send_im_ch4(od->sess, uin, AIM_ICQMSG_AUTHDENIED, message); g_free(message); g_free(uin); g_free(data); } /* * For when other people ask you for authorization */ static void gaim_icq_authask(struct im_connection *ic, guint32 uin, char *msg) { struct icq_auth *data; char *reason = NULL; char *dialog_msg; if (set_getbool(&ic->acc->set, "ignore_auth_requests")) return; data = g_new(struct icq_auth, 1); if (strlen(msg) > 6) reason = msg + 6; dialog_msg = g_strdup_printf("The user %u wants to add you to their buddy list for the following reason: %s", uin, reason ? reason : "No reason given."); data->ic = ic; data->uin = uin; imcb_ask(ic, dialog_msg, data, gaim_icq_authgrant, gaim_icq_authdeny); g_free(dialog_msg); } static int incomingim_chan4(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch4_args *args) { struct im_connection *ic = sess->aux_data; switch (args->type) { case 0x0001: { /* An almost-normal instant message. Mac ICQ sends this. It's peculiar. */ char *uin, *message; uin = g_strdup_printf("%u", args->uin); message = g_strdup(args->msg); strip_linefeed(message); imcb_buddy_msg(ic, normalize(uin), message, 0, 0); g_free(uin); g_free(message); } break; case 0x0004: { /* Someone sent you a URL */ char *uin, *message; char **m; uin = g_strdup_printf("%u", args->uin); m = g_strsplit(args->msg, "\376", 2); if ((strlen(m[0]) != 0)) { message = g_strjoinv(" -- ", m); } else { message = m[1]; } strip_linefeed(message); imcb_buddy_msg(ic, normalize(uin), message, 0, 0); g_free(uin); g_free(m); g_free(message); } break; case 0x0006: { /* Someone requested authorization */ gaim_icq_authask(ic, args->uin, args->msg); } break; case 0x0007: { /* Someone has denied you authorization */ imcb_log(sess->aux_data, "The user %u has denied your request to add them to your contact list for the following reason:\n%s", args->uin, args->msg ? args->msg : _("No reason given.") ); } break; case 0x0008: { /* Someone has granted you authorization */ imcb_log(sess->aux_data, "The user %u has granted your request to add them to your contact list for the following reason:\n%s", args->uin, args->msg ? args->msg : _("No reason given.") ); } break; case 0x0012: { /* Ack for authorizing/denying someone. Or possibly an ack for sending any system notice */ } break; default: {; } break; } return 1; } static int gaim_parse_incoming_im(aim_session_t *sess, aim_frame_t *fr, ...) { int channel, ret = 0; aim_userinfo_t *userinfo; va_list ap; va_start(ap, fr); channel = va_arg(ap, int); userinfo = va_arg(ap, aim_userinfo_t *); switch (channel) { case 1: { /* standard message */ struct aim_incomingim_ch1_args *args; args = va_arg(ap, struct aim_incomingim_ch1_args *); ret = incomingim_chan1(sess, fr->conn, userinfo, args); } break; case 2: { /* rendevous */ struct aim_incomingim_ch2_args *args; args = va_arg(ap, struct aim_incomingim_ch2_args *); ret = incomingim_chan2(sess, fr->conn, userinfo, args); } break; case 4: { /* ICQ */ struct aim_incomingim_ch4_args *args; args = va_arg(ap, struct aim_incomingim_ch4_args *); ret = incomingim_chan4(sess, fr->conn, userinfo, args); } break; default: {; } break; } va_end(ap); return ret; } static int gaim_parse_misses(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; guint16 nummissed, reason; aim_userinfo_t *userinfo; va_start(ap, fr); va_arg(ap, unsigned int); /* chan */ userinfo = va_arg(ap, aim_userinfo_t *); nummissed = (guint16)va_arg(ap, unsigned int); reason = (guint16)va_arg(ap, unsigned int); va_end(ap); switch(reason) { case 0: /* Invalid (0) */ imcb_error(sess->aux_data, nummissed == 1 ? _("You missed %d message from %s because it was invalid.") : _("You missed %d messages from %s because they were invalid."), nummissed, userinfo->sn); break; case 1: /* Message too large */ imcb_error(sess->aux_data, nummissed == 1 ? _("You missed %d message from %s because it was too large.") : _("You missed %d messages from %s because they were too large."), nummissed, userinfo->sn); break; case 2: /* Rate exceeded */ imcb_error(sess->aux_data, nummissed == 1 ? _("You missed %d message from %s because the rate limit has been exceeded.") : _("You missed %d messages from %s because the rate limit has been exceeded."), nummissed, userinfo->sn); break; case 3: /* Evil Sender */ imcb_error(sess->aux_data, nummissed == 1 ? _("You missed %d message from %s because it was too evil.") : _("You missed %d messages from %s because they are too evil."), nummissed, userinfo->sn); break; case 4: /* Evil Receiver */ imcb_error(sess->aux_data, nummissed == 1 ? _("You missed %d message from %s because you are too evil.") : _("You missed %d messages from %s because you are too evil."), nummissed, userinfo->sn); break; default: imcb_error(sess->aux_data, nummissed == 1 ? _("You missed %d message from %s for unknown reasons.") : _("You missed %d messages from %s for unknown reasons."), nummissed, userinfo->sn); break; } return 1; } static int gaim_parse_genericerr(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; guint16 reason; va_start(ap, fr); reason = (guint16)va_arg(ap, unsigned int); va_end(ap); imcb_error(sess->aux_data, _("SNAC threw error: %s"), reason < msgerrreasonlen ? msgerrreason[reason] : "Unknown error"); return 1; } static int gaim_parse_msgerr(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; char *destn; guint16 reason; va_start(ap, fr); reason = (guint16)va_arg(ap, unsigned int); destn = va_arg(ap, char *); va_end(ap); imcb_error(sess->aux_data, _("Your message to %s did not get sent: %s"), destn, (reason < msgerrreasonlen) ? msgerrreason[reason] : _("Reason unknown")); return 1; } static int gaim_parse_locerr(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; char *destn; guint16 reason; va_start(ap, fr); reason = (guint16)va_arg(ap, unsigned int); destn = va_arg(ap, char *); va_end(ap); imcb_error(sess->aux_data, _("User information for %s unavailable: %s"), destn, (reason < msgerrreasonlen) ? msgerrreason[reason] : _("Reason unknown")); return 1; } static int gaim_parse_motd(aim_session_t *sess, aim_frame_t *fr, ...) { guint16 id; va_list ap; va_start(ap, fr); id = (guint16)va_arg(ap, unsigned int); va_arg(ap, char *); /* msg */ va_end(ap); if (id < 4) imcb_error(sess->aux_data, _("Your connection may be lost.")); return 1; } static int gaim_chatnav_info(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; guint16 type; struct im_connection *ic = sess->aux_data; struct oscar_data *odata = (struct oscar_data *)ic->proto_data; va_start(ap, fr); type = (guint16)va_arg(ap, unsigned int); switch(type) { case 0x0002: { va_arg(ap, unsigned int); /* maxrooms */ va_arg(ap, int); /* exchangecount */ va_arg(ap, struct aim_chat_exchangeinfo *); /* exchanges */ va_end(ap); while (odata->create_rooms) { struct create_room *cr = odata->create_rooms->data; aim_chatnav_createroom(sess, fr->conn, cr->name, cr->exchange); g_free(cr->name); odata->create_rooms = g_slist_remove(odata->create_rooms, cr); g_free(cr); } } break; case 0x0008: { char *ck; guint16 instance, exchange; va_arg(ap, char *); /* fqcn */ instance = (guint16)va_arg(ap, unsigned int); exchange = (guint16)va_arg(ap, unsigned int); va_arg(ap, unsigned int); /* flags */ va_arg(ap, guint32); /* createtime */ va_arg(ap, unsigned int); /* maxmsglen */ va_arg(ap, unsigned int); /* maxoccupancy */ va_arg(ap, int); /* createperms */ va_arg(ap, unsigned int); /* unknown */ va_arg(ap, char *); /* name */ ck = va_arg(ap, char *); va_end(ap); aim_chat_join(odata->sess, odata->conn, exchange, ck, instance); } break; default: va_end(ap); break; } return 1; } static int gaim_chat_join(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; int count, i; aim_userinfo_t *info; struct im_connection *g = sess->aux_data; struct chat_connection *c = NULL; va_start(ap, fr); count = va_arg(ap, int); info = va_arg(ap, aim_userinfo_t *); va_end(ap); c = find_oscar_chat_by_conn(g, fr->conn); if (!c) return 1; for (i = 0; i < count; i++) imcb_chat_add_buddy(c->cnv, normalize(info[i].sn)); return 1; } static int gaim_chat_leave(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; int count, i; aim_userinfo_t *info; struct im_connection *g = sess->aux_data; struct chat_connection *c = NULL; va_start(ap, fr); count = va_arg(ap, int); info = va_arg(ap, aim_userinfo_t *); va_end(ap); c = find_oscar_chat_by_conn(g, fr->conn); if (!c) return 1; for (i = 0; i < count; i++) imcb_chat_remove_buddy(c->cnv, normalize(info[i].sn), NULL); return 1; } static int gaim_chat_info_update(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; guint16 maxmsglen, maxvisiblemsglen; struct im_connection *ic = sess->aux_data; struct chat_connection *ccon = find_oscar_chat_by_conn(ic, fr->conn); va_start(ap, fr); va_arg(ap, struct aim_chat_roominfo *); /* roominfo */ va_arg(ap, char *); /* roomname */ va_arg(ap, int); /* usercount */ va_arg(ap, aim_userinfo_t *); /* userinfo */ va_arg(ap, char *); /* roomdesc */ va_arg(ap, int); /* unknown_c9 */ va_arg(ap, unsigned long); /* creationtime */ maxmsglen = (guint16)va_arg(ap, int); va_arg(ap, int); /* unknown_d2 */ va_arg(ap, int); /* unknown_d5 */ maxvisiblemsglen = (guint16)va_arg(ap, int); va_end(ap); ccon->maxlen = maxmsglen; ccon->maxvis = maxvisiblemsglen; return 1; } static int gaim_chat_incoming_msg(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; aim_userinfo_t *info; char *msg; struct im_connection *ic = sess->aux_data; struct chat_connection *ccon = find_oscar_chat_by_conn(ic, fr->conn); char *tmp; va_start(ap, fr); info = va_arg(ap, aim_userinfo_t *); msg = va_arg(ap, char *); tmp = g_malloc(BUF_LONG); g_snprintf(tmp, BUF_LONG, "%s", msg); imcb_chat_msg(ccon->cnv, normalize(info->sn), tmp, 0, 0); g_free(tmp); return 1; } static int gaim_parse_ratechange(aim_session_t *sess, aim_frame_t *fr, ...) { #if 0 static const char *codes[5] = { "invalid", "change", "warning", "limit", "limit cleared", }; #endif va_list ap; guint16 code; guint32 windowsize, clear, currentavg; va_start(ap, fr); code = (guint16)va_arg(ap, unsigned int); va_arg(ap, unsigned int); /* rateclass */ windowsize = (guint32)va_arg(ap, unsigned long); clear = (guint32)va_arg(ap, unsigned long); va_arg(ap, unsigned long); /* alert */ va_arg(ap, unsigned long); /* limit */ va_arg(ap, unsigned long); /* disconnect */ currentavg = (guint32)va_arg(ap, unsigned long); va_arg(ap, unsigned long); /* maxavg */ va_end(ap); /* XXX fix these values */ if (code == AIM_RATE_CODE_CHANGE) { if (currentavg >= clear) aim_conn_setlatency(fr->conn, 0); } else if (code == AIM_RATE_CODE_WARNING) { aim_conn_setlatency(fr->conn, windowsize/4); } else if (code == AIM_RATE_CODE_LIMIT) { imcb_error(sess->aux_data, _("The last message was not sent because you are over the rate limit. " "Please wait 10 seconds and try again.")); aim_conn_setlatency(fr->conn, windowsize/2); } else if (code == AIM_RATE_CODE_CLEARLIMIT) { aim_conn_setlatency(fr->conn, 0); } return 1; } static int gaim_selfinfo(aim_session_t *sess, aim_frame_t *fr, ...) { return 1; } static int conninitdone_bos(aim_session_t *sess, aim_frame_t *fr, ...) { aim_reqpersonalinfo(sess, fr->conn); aim_bos_reqlocaterights(sess, fr->conn); aim_bos_reqbuddyrights(sess, fr->conn); aim_reqicbmparams(sess); aim_bos_reqrights(sess, fr->conn); aim_bos_setgroupperm(sess, fr->conn, AIM_FLAG_ALLUSERS); aim_bos_setprivacyflags(sess, fr->conn, AIM_PRIVFLAGS_ALLOWIDLE | AIM_PRIVFLAGS_ALLOWMEMBERSINCE); return 1; } static int conninitdone_admin(aim_session_t *sess, aim_frame_t *fr, ...) { struct im_connection *ic = sess->aux_data; struct oscar_data *od = ic->proto_data; aim_clientready(sess, fr->conn); if (od->chpass) { aim_admin_changepasswd(sess, fr->conn, od->newp, od->oldp); g_free(od->oldp); od->oldp = NULL; g_free(od->newp); od->newp = NULL; od->chpass = FALSE; } if (od->setnick) { aim_admin_setnick(sess, fr->conn, od->newsn); g_free(od->newsn); od->newsn = NULL; od->setnick = FALSE; } if (od->conf) { aim_admin_reqconfirm(sess, fr->conn); od->conf = FALSE; } if (od->reqemail) { aim_admin_getinfo(sess, fr->conn, 0x0011); od->reqemail = FALSE; } if (od->setemail) { aim_admin_setemail(sess, fr->conn, od->email); g_free(od->email); od->setemail = FALSE; } return 1; } static int gaim_icbm_param_info(aim_session_t *sess, aim_frame_t *fr, ...) { struct aim_icbmparameters *params; va_list ap; va_start(ap, fr); params = va_arg(ap, struct aim_icbmparameters *); va_end(ap); /* Maybe senderwarn and recverwarn should be user preferences... */ params->flags = 0x0000000b; params->maxmsglen = 8000; params->minmsginterval = 0; aim_seticbmparam(sess, params); return 1; } static int gaim_parse_locaterights(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; guint16 maxsiglen; struct im_connection *ic = sess->aux_data; struct oscar_data *odata = (struct oscar_data *)ic->proto_data; va_start(ap, fr); maxsiglen = va_arg(ap, int); va_end(ap); odata->rights.maxsiglen = odata->rights.maxawaymsglen = (guint)maxsiglen; /* FIXME: It seems we're not really using this, and it broke now that struct aim_user is dead. aim_bos_setprofile(sess, fr->conn, ic->user->user_info, NULL, gaim_caps); */ return 1; } static int gaim_parse_buddyrights(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; guint16 maxbuddies, maxwatchers; struct im_connection *ic = sess->aux_data; struct oscar_data *odata = (struct oscar_data *)ic->proto_data; va_start(ap, fr); maxbuddies = (guint16)va_arg(ap, unsigned int); maxwatchers = (guint16)va_arg(ap, unsigned int); va_end(ap); odata->rights.maxbuddies = (guint)maxbuddies; odata->rights.maxwatchers = (guint)maxwatchers; return 1; } static int gaim_bosrights(aim_session_t *sess, aim_frame_t *fr, ...) { guint16 maxpermits, maxdenies; va_list ap; struct im_connection *ic = sess->aux_data; struct oscar_data *odata = (struct oscar_data *)ic->proto_data; va_start(ap, fr); maxpermits = (guint16)va_arg(ap, unsigned int); maxdenies = (guint16)va_arg(ap, unsigned int); va_end(ap); odata->rights.maxpermits = (guint)maxpermits; odata->rights.maxdenies = (guint)maxdenies; aim_clientready(sess, fr->conn); aim_reqservice(sess, fr->conn, AIM_CONN_TYPE_CHATNAV); aim_ssi_reqrights(sess, fr->conn); aim_ssi_reqalldata(sess, fr->conn); return 1; } static int gaim_offlinemsg(aim_session_t *sess, aim_frame_t *fr, ...) { va_list ap; struct aim_icq_offlinemsg *msg; struct im_connection *ic = sess->aux_data; va_start(ap, fr); msg = va_arg(ap, struct aim_icq_offlinemsg *); va_end(ap); switch (msg->type) { case 0x0001: { /* Basic offline message */ char sender[32]; char *dialog_msg = g_strdup(msg->msg); time_t t = get_time(msg->year, msg->month, msg->day, msg->hour, msg->minute, 0); g_snprintf(sender, sizeof(sender), "%u", msg->sender); strip_linefeed(dialog_msg); imcb_buddy_msg(ic, normalize(sender), dialog_msg, 0, t); g_free(dialog_msg); } break; case 0x0004: { /* Someone sent you a URL */ char sender[32]; char *dialog_msg; char **m; time_t t = get_time(msg->year, msg->month, msg->day, msg->hour, msg->minute, 0); g_snprintf(sender, sizeof(sender), "%u", msg->sender); m = g_strsplit(msg->msg, "\376", 2); if ((strlen(m[0]) != 0)) { dialog_msg = g_strjoinv(" -- ", m); } else { dialog_msg = m[1]; } strip_linefeed(dialog_msg); imcb_buddy_msg(ic, normalize(sender), dialog_msg, 0, t); g_free(dialog_msg); g_free(m); } break; case 0x0006: { /* Authorization request */ gaim_icq_authask(ic, msg->sender, msg->msg); } break; case 0x0007: { /* Someone has denied you authorization */ imcb_log(sess->aux_data, "The user %u has denied your request to add them to your contact list for the following reason:\n%s", msg->sender, msg->msg ? msg->msg : _("No reason given.") ); } break; case 0x0008: { /* Someone has granted you authorization */ imcb_log(sess->aux_data, "The user %u has granted your request to add them to your contact list for the following reason:\n%s", msg->sender, msg->msg ? msg->msg : _("No reason given.") ); } break; case 0x0012: { /* Ack for authorizing/denying someone. Or possibly an ack for sending any system notice */ } break; default: {; } } return 1; } static int gaim_offlinemsgdone(aim_session_t *sess, aim_frame_t *fr, ...) { aim_icq_ackofflinemsgs(sess); return 1; } static void oscar_keepalive(struct im_connection *ic) { struct oscar_data *odata = (struct oscar_data *)ic->proto_data; aim_flap_nop(odata->sess, odata->conn); } static int oscar_buddy_msg(struct im_connection *ic, char *name, char *message, int imflags) { struct oscar_data *odata = (struct oscar_data *)ic->proto_data; int ret = 0, len = strlen(message); if (imflags & OPT_AWAY) { ret = aim_send_im(odata->sess, name, AIM_IMFLAGS_AWAY, message); } else { struct aim_sendimext_args args; char *s; args.flags = AIM_IMFLAGS_ACK; if (odata->icq) args.flags |= AIM_IMFLAGS_OFFLINE; for (s = message; *s; s++) if (*s & 128) break; /* Message contains high ASCII chars, time for some translation! */ if (*s) { s = g_malloc(BUF_LONG); /* Try if we can put it in an ISO8859-1 string first. If we can't, fall back to UTF16. */ if ((ret = do_iconv("UTF-8", "ISO8859-1", message, s, len, BUF_LONG)) >= 0) { args.flags |= AIM_IMFLAGS_ISO_8859_1; len = ret; } else if ((ret = do_iconv("UTF-8", "UCS-2BE", message, s, len, BUF_LONG)) >= 0) { args.flags |= AIM_IMFLAGS_UNICODE; len = ret; } else { /* OOF, translation failed... Oh well.. */ g_free( s ); s = message; } } else { s = message; } args.features = gaim_features; args.featureslen = sizeof(gaim_features); args.destsn = name; args.msg = s; args.msglen = len; ret = aim_send_im_ext(odata->sess, &args); if (s != message) { g_free(s); } } if (ret >= 0) return 1; return ret; } static void oscar_get_info(struct im_connection *g, char *name) { struct oscar_data *odata = (struct oscar_data *)g->proto_data; if (odata->icq) aim_icq_getallinfo(odata->sess, name); else { aim_getinfo(odata->sess, odata->conn, name, AIM_GETINFO_AWAYMESSAGE); aim_getinfo(odata->sess, odata->conn, name, AIM_GETINFO_GENERALINFO); } } static void oscar_get_away(struct im_connection *g, char *who) { struct oscar_data *odata = (struct oscar_data *)g->proto_data; if (odata->icq) { /** FIXME(wilmer): Hmm, lost the ability to get away msgs here, do we care to get that back? struct buddy *budlight = imcb_find_buddy(g, who); if (budlight) if ((budlight->uc & 0xff80) >> 7) if (budlight->caps & AIM_CAPS_ICQSERVERRELAY) aim_send_im_ch2_geticqmessage(odata->sess, who, (budlight->uc & 0xff80) >> 7); */ } else aim_getinfo(odata->sess, odata->conn, who, AIM_GETINFO_AWAYMESSAGE); } static void oscar_set_away_aim(struct im_connection *ic, struct oscar_data *od, const char *state, const char *message) { if (state == NULL) state = ""; if (!g_strcasecmp(state, _("Visible"))) { aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL); return; } else if (!g_strcasecmp(state, _("Invisible"))) { aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_INVISIBLE); return; } else if (message == NULL) { message = state; } if (od->rights.maxawaymsglen == 0) imcb_error(ic, "oscar_set_away_aim called before locate rights received"); aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL); g_free(ic->away); ic->away = NULL; if (!message) { aim_bos_setprofile(od->sess, od->conn, NULL, "", gaim_caps); return; } if (strlen(message) > od->rights.maxawaymsglen) { imcb_error(ic, "Maximum away message length of %d bytes exceeded, truncating", od->rights.maxawaymsglen); } ic->away = g_strndup(message, od->rights.maxawaymsglen); aim_bos_setprofile(od->sess, od->conn, NULL, ic->away, gaim_caps); return; } static void oscar_set_away_icq(struct im_connection *ic, struct oscar_data *od, const char *state, const char *message) { const char *msg = NULL; gboolean no_message = FALSE; /* clean old states */ g_free(ic->away); ic->away = NULL; od->sess->aim_icq_state = 0; /* if no message, then use an empty message */ if (message) { msg = message; } else { msg = ""; no_message = TRUE; } if (state == NULL) { aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL); } else if (!g_strcasecmp(state, "Away")) { aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY); ic->away = g_strdup(msg); od->sess->aim_icq_state = AIM_MTYPE_AUTOAWAY; } else if (!g_strcasecmp(state, "Do Not Disturb")) { aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_DND | AIM_ICQ_STATE_BUSY); ic->away = g_strdup(msg); od->sess->aim_icq_state = AIM_MTYPE_AUTODND; } else if (!g_strcasecmp(state, "Not Available")) { aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_OUT | AIM_ICQ_STATE_AWAY); ic->away = g_strdup(msg); od->sess->aim_icq_state = AIM_MTYPE_AUTONA; } else if (!g_strcasecmp(state, "Occupied")) { aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_BUSY); ic->away = g_strdup(msg); od->sess->aim_icq_state = AIM_MTYPE_AUTOBUSY; } else if (!g_strcasecmp(state, "Free For Chat")) { aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_CHAT); ic->away = g_strdup(msg); od->sess->aim_icq_state = AIM_MTYPE_AUTOFFC; } else if (!g_strcasecmp(state, "Invisible")) { aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_INVISIBLE); ic->away = g_strdup(msg); } else { if (no_message) { aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL); } else { aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY); ic->away = g_strdup(msg); od->sess->aim_icq_state = AIM_MTYPE_AUTOAWAY; } } return; } static void oscar_set_away(struct im_connection *ic, char *state, char *message) { struct oscar_data *od = (struct oscar_data *)ic->proto_data; oscar_set_away_aim(ic, od, state, message); if (od->icq) oscar_set_away_icq(ic, od, state, message); return; } static void oscar_add_buddy(struct im_connection *g, char *name, char *group) { struct oscar_data *odata = (struct oscar_data *)g->proto_data; bee_user_t *bu; if (group && (bu = bee_user_by_handle(g->bee, g, name)) && bu->group) aim_ssi_movebuddy(odata->sess, odata->conn, bu->group->name, group, name); else aim_ssi_addbuddies(odata->sess, odata->conn, group ? : OSCAR_GROUP, &name, 1, 0); } static void oscar_remove_buddy(struct im_connection *g, char *name, char *group) { struct oscar_data *odata = (struct oscar_data *)g->proto_data; struct aim_ssi_item *ssigroup; while ((ssigroup = aim_ssi_itemlist_findparent(odata->sess->ssi.items, name)) && !aim_ssi_delbuddies(odata->sess, odata->conn, ssigroup->name, &name, 1)); } static int gaim_ssi_parserights(aim_session_t *sess, aim_frame_t *fr, ...) { return 1; } static int gaim_ssi_parselist(aim_session_t *sess, aim_frame_t *fr, ...) { struct im_connection *ic = sess->aux_data; struct aim_ssi_item *curitem, *curgroup = NULL; int tmp; char *nrm; /* Add from server list to local list */ tmp = 0; for (curitem=sess->ssi.items; curitem; curitem=curitem->next) { nrm = curitem->name ? normalize(curitem->name) : NULL; switch (curitem->type) { case 0x0000: /* Buddy */ if ((curitem->name) && (!imcb_buddy_by_handle(ic, nrm))) { char *realname = NULL; if (curitem->data && aim_gettlv(curitem->data, 0x0131, 1)) realname = aim_gettlv_str(curitem->data, 0x0131, 1); imcb_add_buddy(ic, nrm, curgroup ? (curgroup->gid == curitem->gid ? curgroup->name : NULL) : NULL); if (realname) { imcb_buddy_nick_hint(ic, nrm, realname); imcb_rename_buddy(ic, nrm, realname); g_free(realname); } } break; case 0x0001: /* Group */ curgroup = curitem; break; case 0x0002: /* Permit buddy */ if (curitem->name) { GSList *list; for (list=ic->permit; (list && aim_sncmp(curitem->name, list->data)); list=list->next); if (!list) { char *name; name = g_strdup(nrm); ic->permit = g_slist_append(ic->permit, name); tmp++; } } break; case 0x0003: /* Deny buddy */ if (curitem->name) { GSList *list; for (list=ic->deny; (list && aim_sncmp(curitem->name, list->data)); list=list->next); if (!list) { char *name; name = g_strdup(nrm); ic->deny = g_slist_append(ic->deny, name); tmp++; } } break; case 0x0004: /* Permit/deny setting */ if (curitem->data) { guint8 permdeny; if ((permdeny = aim_ssi_getpermdeny(sess->ssi.items)) && (permdeny != ic->permdeny)) { ic->permdeny = permdeny; tmp++; } } break; case 0x0005: /* Presence setting */ /* We don't want to change Gaim's setting because it applies to all accounts */ break; } /* End of switch on curitem->type */ } /* End of for loop */ aim_ssi_enable(sess, fr->conn); /* Request offline messages, now that the buddy list is complete. */ aim_icq_reqofflinemsgs(sess); /* Now that we have a buddy list, we can tell BitlBee that we're online. */ imcb_connected(ic); return 1; } static int gaim_ssi_parseack( aim_session_t *sess, aim_frame_t *fr, ... ) { aim_snac_t *origsnac; va_list ap; va_start( ap, fr ); origsnac = va_arg( ap, aim_snac_t * ); va_end( ap ); if( origsnac && origsnac->family == AIM_CB_FAM_SSI && origsnac->type == AIM_CB_SSI_ADD && origsnac->data ) { int i, st, count = aim_bstream_empty( &fr->data ); char *list; if( count & 1 ) { /* Hmm, the length should be even... */ imcb_error( sess->aux_data, "Received SSI ACK package with non-even length"); return( 0 ); } count >>= 1; list = (char *) origsnac->data; for( i = 0; i < count; i ++ ) { struct aim_ssi_item *ssigroup = aim_ssi_itemlist_findparent( sess->ssi.items, list ); char *group = ssigroup ? ssigroup->name : NULL; st = aimbs_get16( &fr->data ); if( st == 0x00 ) { imcb_add_buddy( sess->aux_data, normalize(list), group ); } else if( st == 0x0E ) { imcb_log( sess->aux_data, "Buddy %s can't be added without authorization, requesting authorization", list ); aim_ssi_auth_request( sess, fr->conn, list, "" ); aim_ssi_addbuddies( sess, fr->conn, OSCAR_GROUP, &list, 1, 1 ); } else if( st == 0x0A ) { imcb_error( sess->aux_data, "Buddy %s is already in your list", list ); } else { imcb_error( sess->aux_data, "Error while adding buddy: 0x%04x", st ); } list += strlen( list ) + 1; } } return( 1 ); } static void oscar_add_permit(struct im_connection *ic, char *who) { struct oscar_data *od = (struct oscar_data *)ic->proto_data; if (od->icq) { aim_ssi_auth_reply(od->sess, od->conn, who, 1, ""); } else { if (od->sess->ssi.received_data) aim_ssi_addpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_PERMIT); } } static void oscar_add_deny(struct im_connection *ic, char *who) { struct oscar_data *od = (struct oscar_data *)ic->proto_data; if (od->icq) { aim_ssi_auth_reply(od->sess, od->conn, who, 0, ""); } else { if (od->sess->ssi.received_data) aim_ssi_addpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_DENY); } } static void oscar_rem_permit(struct im_connection *ic, char *who) { struct oscar_data *od = (struct oscar_data *)ic->proto_data; if (!od->icq) { if (od->sess->ssi.received_data) aim_ssi_delpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_PERMIT); } } static void oscar_rem_deny(struct im_connection *ic, char *who) { struct oscar_data *od = (struct oscar_data *)ic->proto_data; if (!od->icq) { if (od->sess->ssi.received_data) aim_ssi_delpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_DENY); } } static GList *oscar_away_states(struct im_connection *ic) { struct oscar_data *od = ic->proto_data; if (od->icq) { static GList *m = NULL; m = g_list_append(m, "Away"); m = g_list_append(m, "Do Not Disturb"); m = g_list_append(m, "Not Available"); m = g_list_append(m, "Occupied"); m = g_list_append(m, "Free For Chat"); m = g_list_append(m, "Invisible"); return m; } else { static GList *m = NULL; m = g_list_append(m, "Away"); return m; } } static int gaim_icqinfo(aim_session_t *sess, aim_frame_t *fr, ...) { struct im_connection *ic = sess->aux_data; struct oscar_data *od = ic->proto_data; gchar who[16]; GString *str; va_list ap; struct aim_icq_info *info; uint32_t ip; va_start(ap, fr); info = va_arg(ap, struct aim_icq_info *); va_end(ap); if (!info->uin) return 0; str = g_string_sized_new(512); g_snprintf(who, sizeof(who), "%u", info->uin); g_string_printf(str, "%s: %s - %s: %s", _("UIN"), who, _("Nick"), info->nick ? info->nick : "-"); g_string_append_printf(str, "\n%s: %s", _("First Name"), info->first); g_string_append_printf(str, "\n%s: %s", _("Last Name"), info->last); g_string_append_printf(str, "\n%s: %s", _("Email Address"), info->email); if (info->numaddresses && info->email2) { int i; for (i = 0; i < info->numaddresses; i++) { g_string_append_printf(str, "\n%s: %s", _("Email Address"), info->email2[i]); } } if (od->ips && (ip = (long) g_hash_table_lookup(od->ips, &info->uin)) != 0) { g_string_append_printf(str, "\n%s: %d.%d.%d.%d", _("Last used IP address"), (ip >> 24), (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); } g_string_append_printf(str, "\n%s: %s", _("Mobile Phone"), info->mobile); if (info->gender != 0) g_string_append_printf(str, "\n%s: %s", _("Gender"), info->gender==1 ? _("Female") : _("Male")); if (info->birthyear || info->birthmonth || info->birthday) { char date[30]; struct tm tm; memset(&tm, 0, sizeof(struct tm)); tm.tm_mday = (int)info->birthday; tm.tm_mon = (int)info->birthmonth-1; tm.tm_year = (int)info->birthyear%100; strftime(date, sizeof(date), "%Y-%m-%d", &tm); g_string_append_printf(str, "\n%s: %s", _("Birthday"), date); } if (info->age) { char age[5]; g_snprintf(age, sizeof(age), "%hhd", info->age); g_string_append_printf(str, "\n%s: %s", _("Age"), age); } g_string_append_printf(str, "\n%s: %s", _("Personal Web Page"), info->personalwebpage); if (info->info && info->info[0]) { g_string_sprintfa(str, "\n%s:\n%s\n%s", _("Additional Information"), info->info, _("End of Additional Information")); } g_string_append_c(str, '\n'); if ((info->homeaddr && (info->homeaddr[0])) || (info->homecity && info->homecity[0]) || (info->homestate && info->homestate[0]) || (info->homezip && info->homezip[0])) { g_string_append_printf(str, "%s:", _("Home Address")); g_string_append_printf(str, "\n%s: %s", _("Address"), info->homeaddr); g_string_append_printf(str, "\n%s: %s", _("City"), info->homecity); g_string_append_printf(str, "\n%s: %s", _("State"), info->homestate); g_string_append_printf(str, "\n%s: %s", _("Zip Code"), info->homezip); g_string_append_c(str, '\n'); } if ((info->workaddr && info->workaddr[0]) || (info->workcity && info->workcity[0]) || (info->workstate && info->workstate[0]) || (info->workzip && info->workzip[0])) { g_string_append_printf(str, "%s:", _("Work Address")); g_string_append_printf(str, "\n%s: %s", _("Address"), info->workaddr); g_string_append_printf(str, "\n%s: %s", _("City"), info->workcity); g_string_append_printf(str, "\n%s: %s", _("State"), info->workstate); g_string_append_printf(str, "\n%s: %s", _("Zip Code"), info->workzip); g_string_append_c(str, '\n'); } if ((info->workcompany && info->workcompany[0]) || (info->workdivision && info->workdivision[0]) || (info->workposition && info->workposition[0]) || (info->workwebpage && info->workwebpage[0])) { g_string_append_printf(str, "%s:", _("Work Information")); g_string_append_printf(str, "\n%s: %s", _("Company"), info->workcompany); g_string_append_printf(str, "\n%s: %s", _("Division"), info->workdivision); g_string_append_printf(str, "\n%s: %s", _("Position"), info->workposition); if (info->workwebpage && info->workwebpage[0]) { g_string_append_printf(str, "\n%s: %s", _("Web Page"), info->workwebpage); } g_string_append_c(str, '\n'); } imcb_log(ic, "%s\n%s", _("User Info"), str->str); g_string_free(str, TRUE); return 1; } static char *oscar_encoding_extract(const char *encoding) { char *ret = NULL; char *begin, *end; g_return_val_if_fail(encoding != NULL, NULL); /* Make sure encoding begins with charset= */ if (strncmp(encoding, "text/plain; charset=", 20) && strncmp(encoding, "text/aolrtf; charset=", 21) && strncmp(encoding, "text/x-aolrtf; charset=", 23)) { return NULL; } begin = strchr(encoding, '"'); end = strrchr(encoding, '"'); if ((begin == NULL) || (end == NULL) || (begin >= end)) return NULL; ret = g_strndup(begin+1, (end-1) - begin); return ret; } static char *oscar_encoding_to_utf8(char *encoding, char *text, int textlen) { char *utf8 = g_new0(char, 8192); if ((encoding == NULL) || encoding[0] == '\0') { /* gaim_debug_info("oscar", "Empty encoding, assuming UTF-8\n");*/ } else if (!g_strcasecmp(encoding, "iso-8859-1")) { do_iconv("iso-8859-1", "UTF-8", text, utf8, textlen, 8192); } else if (!g_strcasecmp(encoding, "ISO-8859-1-Windows-3.1-Latin-1")) { do_iconv("Windows-1252", "UTF-8", text, utf8, textlen, 8192); } else if (!g_strcasecmp(encoding, "unicode-2-0")) { do_iconv("UCS-2BE", "UTF-8", text, utf8, textlen, 8192); } else if (g_strcasecmp(encoding, "us-ascii") && strcmp(encoding, "utf-8")) { /* gaim_debug_warning("oscar", "Unrecognized character encoding \"%s\", " "attempting to convert to UTF-8 anyway\n", encoding);*/ do_iconv(encoding, "UTF-8", text, utf8, textlen, 8192); } /* * If utf8 is still NULL then either the encoding is us-ascii/utf-8 or * we have been unable to convert the text to utf-8 from the encoding * that was specified. So we assume it's UTF-8 and hope for the best. */ if (*utf8 == 0) { strncpy(utf8, text, textlen); } return utf8; } static int gaim_parseaiminfo(aim_session_t *sess, aim_frame_t *fr, ...) { struct im_connection *ic = sess->aux_data; va_list ap; aim_userinfo_t *userinfo; guint16 infotype; char *text_encoding = NULL, *text = NULL, *extracted_encoding = NULL; guint16 text_length; char *utf8 = NULL; va_start(ap, fr); userinfo = va_arg(ap, aim_userinfo_t *); infotype = va_arg(ap, int); text_encoding = va_arg(ap, char*); text = va_arg(ap, char*); text_length = va_arg(ap, int); va_end(ap); if(text_encoding) extracted_encoding = oscar_encoding_extract(text_encoding); if(infotype == AIM_GETINFO_GENERALINFO) { /*Display idle time*/ char buff[256]; struct tm idletime; if(userinfo->idletime) { memset(&idletime, 0, sizeof(struct tm)); idletime.tm_mday = (userinfo->idletime / 60) / 24; idletime.tm_hour = (userinfo->idletime / 60) % 24; idletime.tm_min = userinfo->idletime % 60; idletime.tm_sec = 0; strftime(buff, 256, _("%d days %H hours %M minutes"), &idletime); imcb_log(ic, "%s: %s", _("Idle Time"), buff); } if(text) { utf8 = oscar_encoding_to_utf8(extracted_encoding, text, text_length); imcb_log(ic, "%s\n%s", _("User Info"), utf8); } else { imcb_log(ic, _("No user info available.")); } } else if(infotype == AIM_GETINFO_AWAYMESSAGE && userinfo->flags & AIM_FLAG_AWAY) { utf8 = oscar_encoding_to_utf8(extracted_encoding, text, text_length); imcb_log(ic, "%s\n%s", _("Away Message"), utf8); } g_free(utf8); return 1; } int gaim_parsemtn(aim_session_t *sess, aim_frame_t *fr, ...) { struct im_connection * ic = sess->aux_data; va_list ap; guint16 type2; char * sn; va_start(ap, fr); va_arg(ap, int); /* type1 */ sn = va_arg(ap, char*); type2 = va_arg(ap, int); va_end(ap); if(type2 == 0x0002) { /* User is typing */ imcb_buddy_typing(ic, normalize(sn), OPT_TYPING); } else if (type2 == 0x0001) { /* User has typed something, but is not actively typing (stale) */ imcb_buddy_typing(ic, normalize(sn), OPT_THINKING); } else { /* User has stopped typing */ imcb_buddy_typing(ic, normalize(sn), 0); } return 1; } int oscar_send_typing(struct im_connection *ic, char * who, int typing) { struct oscar_data *od = ic->proto_data; return( aim_im_sendmtn(od->sess, 1, who, (typing & OPT_TYPING) ? 0x0002 : 0x0000) ); } void oscar_chat_msg(struct groupchat *c, char *message, int msgflags) { struct im_connection *ic = c->ic; struct oscar_data * od = (struct oscar_data*)ic->proto_data; struct chat_connection * ccon; int ret; guint8 len = strlen(message); guint16 flags; char *s; if (!(ccon = c->data)) return; for (s = message; *s; s++) if (*s & 128) break; flags = AIM_CHATFLAGS_NOREFLECT; /* Message contains high ASCII chars, time for some translation! */ if (*s) { s = g_malloc(BUF_LONG); /* Try if we can put it in an ISO8859-1 string first. If we can't, fall back to UTF16. */ if ((ret = do_iconv("UTF-8", "ISO8859-1", message, s, len, BUF_LONG)) >= 0) { flags |= AIM_CHATFLAGS_ISO_8859_1; len = ret; } else if ((ret = do_iconv("UTF-8", "UCS-2BE", message, s, len, BUF_LONG)) >= 0) { flags |= AIM_CHATFLAGS_UNICODE; len = ret; } else { /* OOF, translation failed... Oh well.. */ g_free( s ); s = message; } } else { s = message; } ret = aim_chat_send_im(od->sess, ccon->conn, flags, s, len); if (s != message) { g_free(s); } /* return (ret >= 0); */ } void oscar_chat_invite(struct groupchat *c, char *who, char *message) { struct im_connection *ic = c->ic; struct oscar_data * od = (struct oscar_data *)ic->proto_data; struct chat_connection *ccon; if (!(ccon = c->data)) return; aim_chat_invite(od->sess, od->conn, who, message ? message : "", ccon->exchange, ccon->name, 0x0); } void oscar_chat_kill(struct im_connection *ic, struct chat_connection *cc) { struct oscar_data *od = (struct oscar_data *)ic->proto_data; /* Notify the conversation window that we've left the chat */ imcb_chat_free(cc->cnv); /* Destroy the chat_connection */ od->oscar_chats = g_slist_remove(od->oscar_chats, cc); if (cc->inpa > 0) b_event_remove(cc->inpa); aim_conn_kill(od->sess, &cc->conn); g_free(cc->name); g_free(cc->show); g_free(cc); } void oscar_chat_leave(struct groupchat *c) { if (!c->data) return; oscar_chat_kill(c->ic, c->data); } struct groupchat *oscar_chat_join_internal(struct im_connection *ic, const char *room, const char *nick, const char *password, int exchange_number) { struct oscar_data * od = (struct oscar_data *)ic->proto_data; struct groupchat *ret = imcb_chat_new(ic, room); aim_conn_t * cur; if((cur = aim_getconn_type(od->sess, AIM_CONN_TYPE_CHATNAV))) { aim_chatnav_createroom(od->sess, cur, room, exchange_number); return ret; } else { struct create_room * cr = g_new0(struct create_room, 1); cr->exchange = exchange_number; cr->name = g_strdup(room); od->create_rooms = g_slist_append(od->create_rooms, cr); aim_reqservice(od->sess, od->conn, AIM_CONN_TYPE_CHATNAV); return ret; } } struct groupchat *oscar_chat_join(struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets) { return oscar_chat_join_internal(ic, room, nick, password, set_getint(sets, "exchange_number")); } struct groupchat *oscar_chat_with(struct im_connection * ic, char *who) { struct oscar_data * od = (struct oscar_data *)ic->proto_data; struct groupchat *ret; static int chat_id = 0; char * chatname, *s; chatname = g_strdup_printf("%s%s%d", isdigit(*ic->acc->user) ? "icq" : "", ic->acc->user, chat_id++); for (s = chatname; *s; s ++) if (!isalnum(*s)) *s = '0'; ret = oscar_chat_join_internal(ic, chatname, NULL, NULL, 4); aim_chat_invite(od->sess, od->conn, who, "", 4, chatname, 0x0); g_free(chatname); return ret; } void oscar_accept_chat(void *data) { struct aim_chat_invitation * inv = data; oscar_chat_join_internal(inv->ic, inv->name, NULL, NULL, 4); g_free(inv->name); g_free(inv); } void oscar_reject_chat(void *data) { struct aim_chat_invitation * inv = data; g_free(inv->name); g_free(inv); } void oscar_chat_add_settings(account_t *acc, set_t **head) { set_add(head, "exchange_number", "4", set_eval_int, NULL); } void oscar_chat_free_settings(account_t *acc, set_t **head) { set_del(head, "exchange_number"); } void oscar_initmodule() { struct prpl *ret = g_new0(struct prpl, 1); ret->name = "oscar"; ret->mms = 2343; /* this guess taken from libotr UPGRADING file */ ret->away_states = oscar_away_states; ret->init = oscar_init; ret->login = oscar_login; ret->keepalive = oscar_keepalive; ret->logout = oscar_logout; ret->buddy_msg = oscar_buddy_msg; ret->get_info = oscar_get_info; ret->set_away = oscar_set_away; ret->get_away = oscar_get_away; ret->add_buddy = oscar_add_buddy; ret->remove_buddy = oscar_remove_buddy; ret->chat_msg = oscar_chat_msg; ret->chat_invite = oscar_chat_invite; ret->chat_leave = oscar_chat_leave; ret->chat_with = oscar_chat_with; ret->chat_join = oscar_chat_join; ret->chat_add_settings = oscar_chat_add_settings; ret->chat_free_settings = oscar_chat_free_settings; ret->add_permit = oscar_add_permit; ret->add_deny = oscar_add_deny; ret->rem_permit = oscar_rem_permit; ret->rem_deny = oscar_rem_deny; ret->send_typing = oscar_send_typing; ret->handle_cmp = aim_sncmp; register_protocol(ret); } bitlbee-3.2.1/protocols/oscar/aim_internal.h0000644000175000017500000001634012245474076020470 0ustar wilmerwilmer/* * aim_internal.h -- prototypes/structs for the guts of libfaim * */ #ifndef __AIM_INTERNAL_H__ #define __AIM_INTERNAL_H__ 1 typedef struct { guint16 family; guint16 subtype; guint16 flags; guint32 id; } aim_modsnac_t; #define AIM_MODULENAME_MAXLEN 16 #define AIM_MODFLAG_MULTIFAMILY 0x0001 typedef struct aim_module_s { guint16 family; guint16 version; guint16 toolid; guint16 toolversion; guint16 flags; char name[AIM_MODULENAME_MAXLEN+1]; int (*snachandler)(aim_session_t *sess, struct aim_module_s *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs); void (*shutdown)(aim_session_t *sess, struct aim_module_s *mod); void *priv; struct aim_module_s *next; } aim_module_t; int aim__registermodule(aim_session_t *sess, int (*modfirst)(aim_session_t *, aim_module_t *)); void aim__shutdownmodules(aim_session_t *sess); aim_module_t *aim__findmodulebygroup(aim_session_t *sess, guint16 group); int buddylist_modfirst(aim_session_t *sess, aim_module_t *mod); int admin_modfirst(aim_session_t *sess, aim_module_t *mod); int bos_modfirst(aim_session_t *sess, aim_module_t *mod); int search_modfirst(aim_session_t *sess, aim_module_t *mod); int stats_modfirst(aim_session_t *sess, aim_module_t *mod); int auth_modfirst(aim_session_t *sess, aim_module_t *mod); int msg_modfirst(aim_session_t *sess, aim_module_t *mod); int misc_modfirst(aim_session_t *sess, aim_module_t *mod); int chatnav_modfirst(aim_session_t *sess, aim_module_t *mod); int chat_modfirst(aim_session_t *sess, aim_module_t *mod); int locate_modfirst(aim_session_t *sess, aim_module_t *mod); int general_modfirst(aim_session_t *sess, aim_module_t *mod); int ssi_modfirst(aim_session_t *sess, aim_module_t *mod); int icq_modfirst(aim_session_t *sess, aim_module_t *mod); int aim_genericreq_n(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype); int aim_genericreq_n_snacid(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype); int aim_genericreq_l(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype, guint32 *); int aim_genericreq_s(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype, guint16 *); #define AIMBS_CURPOSPAIR(x) ((x)->data + (x)->offset), ((x)->len - (x)->offset) void aim_rxqueue_cleanbyconn(aim_session_t *sess, aim_conn_t *conn); int aim_recv(int fd, void *buf, size_t count); int aim_bstream_init(aim_bstream_t *bs, guint8 *data, int len); int aim_bstream_empty(aim_bstream_t *bs); int aim_bstream_curpos(aim_bstream_t *bs); int aim_bstream_setpos(aim_bstream_t *bs, int off); void aim_bstream_rewind(aim_bstream_t *bs); int aim_bstream_advance(aim_bstream_t *bs, int n); guint8 aimbs_get8(aim_bstream_t *bs); guint16 aimbs_get16(aim_bstream_t *bs); guint32 aimbs_get32(aim_bstream_t *bs); guint8 aimbs_getle8(aim_bstream_t *bs); guint16 aimbs_getle16(aim_bstream_t *bs); guint32 aimbs_getle32(aim_bstream_t *bs); int aimbs_put8(aim_bstream_t *bs, guint8 v); int aimbs_put16(aim_bstream_t *bs, guint16 v); int aimbs_put32(aim_bstream_t *bs, guint32 v); int aimbs_putle8(aim_bstream_t *bs, guint8 v); int aimbs_putle16(aim_bstream_t *bs, guint16 v); int aimbs_putle32(aim_bstream_t *bs, guint32 v); int aimbs_getrawbuf(aim_bstream_t *bs, guint8 *buf, int len); guint8 *aimbs_getraw(aim_bstream_t *bs, int len); char *aimbs_getstr(aim_bstream_t *bs, int len); int aimbs_putraw(aim_bstream_t *bs, const guint8 *v, int len); int aimbs_putbs(aim_bstream_t *bs, aim_bstream_t *srcbs, int len); int aim_get_command_rendezvous(aim_session_t *sess, aim_conn_t *conn); int aim_tx_sendframe(aim_session_t *sess, aim_frame_t *cur); flap_seqnum_t aim_get_next_txseqnum(aim_conn_t *); aim_frame_t *aim_tx_new(aim_session_t *sess, aim_conn_t *conn, guint8 framing, guint8 chan, int datalen); void aim_frame_destroy(aim_frame_t *); int aim_tx_enqueue(aim_session_t *, aim_frame_t *); int aim_tx_printqueue(aim_session_t *); void aim_tx_cleanqueue(aim_session_t *, aim_conn_t *); aim_rxcallback_t aim_callhandler(aim_session_t *sess, aim_conn_t *conn, u_short family, u_short type); /* * Generic SNAC structure. Rarely if ever used. */ typedef struct aim_snac_s { aim_snacid_t id; guint16 family; guint16 type; guint16 flags; void *data; time_t issuetime; struct aim_snac_s *next; } aim_snac_t; void aim_initsnachash(aim_session_t *sess); aim_snacid_t aim_cachesnac(aim_session_t *sess, const guint16 family, const guint16 type, const guint16 flags, const void *data, const int datalen); aim_snac_t *aim_remsnac(aim_session_t *, aim_snacid_t id); void aim_cleansnacs(aim_session_t *, int maxage); int aim_putsnac(aim_bstream_t *, guint16 family, guint16 type, guint16 flags, aim_snacid_t id); int aim_oft_buildheader(unsigned char *,struct aim_fileheader_t *); int aim_parse_unknown(aim_session_t *, aim_frame_t *, ...); /* Stored in ->priv of the service request SNAC for chats. */ struct chatsnacinfo { guint16 exchange; char name[128]; guint16 instance; }; /* these are used by aim_*_clientready */ #define AIM_TOOL_JAVA 0x0001 #define AIM_TOOL_MAC 0x0002 #define AIM_TOOL_WIN16 0x0003 #define AIM_TOOL_WIN32 0x0004 #define AIM_TOOL_MAC68K 0x0005 #define AIM_TOOL_MACPPC 0x0006 #define AIM_TOOL_NEWWIN 0x0010 struct aim_tool_version { guint16 group; guint16 version; guint16 tool; guint16 toolversion; }; /* * In SNACland, the terms 'family' and 'group' are synonymous -- the former * is my term, the latter is AOL's. */ struct snacgroup { guint16 group; struct snacgroup *next; }; struct snacpair { guint16 group; guint16 subtype; struct snacpair *next; }; struct rateclass { guint16 classid; guint32 windowsize; guint32 clear; guint32 alert; guint32 limit; guint32 disconnect; guint32 current; guint32 max; guint8 unknown[5]; /* only present in versions >= 3 */ struct snacpair *members; struct rateclass *next; }; /* * This is inside every connection. But it is a void * to anything * outside of libfaim. It should remain that way. It's called data * abstraction. Maybe you've heard of it. (Probably not if you're a * libfaim user.) * */ typedef struct aim_conn_inside_s { struct snacgroup *groups; struct rateclass *rates; } aim_conn_inside_t; void aim_conn_addgroup(aim_conn_t *conn, guint16 group); guint32 aim_getcap(aim_session_t *sess, aim_bstream_t *bs, int len); int aim_putcap(aim_bstream_t *bs, guint32 caps); int aim_cachecookie(aim_session_t *sess, aim_msgcookie_t *cookie); aim_msgcookie_t *aim_uncachecookie(aim_session_t *sess, guint8 *cookie, int type); aim_msgcookie_t *aim_mkcookie(guint8 *, int, void *); aim_msgcookie_t *aim_checkcookie(aim_session_t *sess, const unsigned char *, const int); int aim_freecookie(aim_session_t *sess, aim_msgcookie_t *cookie); int aim_cookie_free(aim_session_t *sess, aim_msgcookie_t *cookie); int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *); int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo); void aim_conn_close_rend(aim_session_t *sess, aim_conn_t *conn); void aim_conn_kill_rend(aim_session_t *sess, aim_conn_t *conn); void aim_conn_kill_chat(aim_session_t *sess, aim_conn_t *conn); /* These are all handled internally now. */ int aim_setversions(aim_session_t *sess, aim_conn_t *conn); int aim_reqrates(aim_session_t *, aim_conn_t *); int aim_rates_addparam(aim_session_t *, aim_conn_t *); #endif /* __AIM_INTERNAL_H__ */ bitlbee-3.2.1/protocols/oscar/icq.c0000644000175000017500000002612412245474076016576 0ustar wilmerwilmer/* * Encapsulated ICQ. * */ #include #include "icq.h" int aim_icq_reqofflinemsgs(aim_session_t *sess) { aim_conn_t *conn; aim_frame_t *fr; aim_snacid_t snacid; int bslen; if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015))) return -EINVAL; bslen = 2 + 4 + 2 + 2; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid); /* For simplicity, don't bother using a tlvlist */ aimbs_put16(&fr->data, 0x0001); aimbs_put16(&fr->data, bslen); aimbs_putle16(&fr->data, bslen - 2); aimbs_putle32(&fr->data, atoi(sess->sn)); aimbs_putle16(&fr->data, 0x003c); /* I command thee. */ aimbs_putle16(&fr->data, snacid); /* eh. */ aim_tx_enqueue(sess, fr); return 0; } int aim_icq_ackofflinemsgs(aim_session_t *sess) { aim_conn_t *conn; aim_frame_t *fr; aim_snacid_t snacid; int bslen; if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015))) return -EINVAL; bslen = 2 + 4 + 2 + 2; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid); /* For simplicity, don't bother using a tlvlist */ aimbs_put16(&fr->data, 0x0001); aimbs_put16(&fr->data, bslen); aimbs_putle16(&fr->data, bslen - 2); aimbs_putle32(&fr->data, atoi(sess->sn)); aimbs_putle16(&fr->data, 0x003e); /* I command thee. */ aimbs_putle16(&fr->data, snacid); /* eh. */ aim_tx_enqueue(sess, fr); return 0; } int aim_icq_getallinfo(aim_session_t *sess, const char *uin) { aim_conn_t *conn; aim_frame_t *fr; aim_snacid_t snacid; int bslen; struct aim_icq_info *info; if (!uin || uin[0] < '0' || uin[0] > '9') return -EINVAL; if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015))) return -EINVAL; bslen = 2 + 4 + 2 + 2 + 2 + 4; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid); /* For simplicity, don't bother using a tlvlist */ aimbs_put16(&fr->data, 0x0001); aimbs_put16(&fr->data, bslen); aimbs_putle16(&fr->data, bslen - 2); aimbs_putle32(&fr->data, atoi(sess->sn)); aimbs_putle16(&fr->data, 0x07d0); /* I command thee. */ aimbs_putle16(&fr->data, snacid); /* eh. */ aimbs_putle16(&fr->data, 0x04b2); /* shrug. */ aimbs_putle32(&fr->data, atoi(uin)); aim_tx_enqueue(sess, fr); /* Keep track of this request and the ICQ number and request ID */ info = g_new0(struct aim_icq_info, 1); info->reqid = snacid; info->uin = atoi(uin); info->next = sess->icq_info; sess->icq_info = info; return 0; } static void aim_icq_freeinfo(struct aim_icq_info *info) { int i; if (!info) return; g_free(info->nick); g_free(info->first); g_free(info->last); g_free(info->email); g_free(info->homecity); g_free(info->homestate); g_free(info->homephone); g_free(info->homefax); g_free(info->homeaddr); g_free(info->mobile); g_free(info->homezip); g_free(info->personalwebpage); if (info->email2) for (i = 0; i < info->numaddresses; i++) g_free(info->email2[i]); g_free(info->email2); g_free(info->workcity); g_free(info->workstate); g_free(info->workphone); g_free(info->workfax); g_free(info->workaddr); g_free(info->workzip); g_free(info->workcompany); g_free(info->workdivision); g_free(info->workposition); g_free(info->workwebpage); g_free(info->info); g_free(info); } /** * Subtype 0x0003 - Response to 0x0015/0x002, contains an ICQesque packet. */ static int icqresponse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; aim_tlvlist_t *tl; aim_tlv_t *datatlv; aim_bstream_t qbs; guint16 cmd, reqid; if (!(tl = aim_readtlvchain(bs)) || !(datatlv = aim_gettlv(tl, 0x0001, 1))) { aim_freetlvchain(&tl); imcb_error(sess->aux_data, "corrupt ICQ response\n"); return 0; } aim_bstream_init(&qbs, datatlv->value, datatlv->length); aimbs_getle16(&qbs); /* cmdlen */ aimbs_getle32(&qbs); /* ouruin */ cmd = aimbs_getle16(&qbs); reqid = aimbs_getle16(&qbs); if (cmd == 0x0041) { /* offline message */ guint16 msglen; struct aim_icq_offlinemsg msg; aim_rxcallback_t userfunc; memset(&msg, 0, sizeof(msg)); msg.sender = aimbs_getle32(&qbs); msg.year = aimbs_getle16(&qbs); msg.month = aimbs_getle8(&qbs); msg.day = aimbs_getle8(&qbs); msg.hour = aimbs_getle8(&qbs); msg.minute = aimbs_getle8(&qbs); msg.type = aimbs_getle16(&qbs); msglen = aimbs_getle16(&qbs); msg.msg = aimbs_getstr(&qbs, msglen); if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG))) ret = userfunc(sess, rx, &msg); g_free(msg.msg); } else if (cmd == 0x0042) { aim_rxcallback_t userfunc; if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSGCOMPLETE))) ret = userfunc(sess, rx); } else if (cmd == 0x07da) { /* information */ guint16 subtype; struct aim_icq_info *info; aim_rxcallback_t userfunc; subtype = aimbs_getle16(&qbs); aim_bstream_advance(&qbs, 1); /* 0x0a */ /* find another data from the same request */ for (info = sess->icq_info; info && (info->reqid != reqid); info = info->next); if (!info) { info = g_new0(struct aim_icq_info, 1); info->reqid = reqid; info->next = sess->icq_info; sess->icq_info = info; } switch (subtype) { case 0x00a0: { /* hide ip status */ /* nothing */ } break; case 0x00aa: { /* password change status */ /* nothing */ } break; case 0x00c8: { /* general and "home" information */ info->nick = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->first = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->last = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->email = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->homecity = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->homestate = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->homephone = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->homefax = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->homeaddr = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->mobile = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->homezip = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->homecountry = aimbs_getle16(&qbs); /* 0x0a 00 02 00 */ /* 1 byte timezone? */ /* 1 byte hide email flag? */ } break; case 0x00dc: { /* personal information */ info->age = aimbs_getle8(&qbs); info->unknown = aimbs_getle8(&qbs); info->gender = aimbs_getle8(&qbs); info->personalwebpage = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->birthyear = aimbs_getle16(&qbs); info->birthmonth = aimbs_getle8(&qbs); info->birthday = aimbs_getle8(&qbs); info->language1 = aimbs_getle8(&qbs); info->language2 = aimbs_getle8(&qbs); info->language3 = aimbs_getle8(&qbs); /* 0x00 00 01 00 00 01 00 00 00 00 00 */ } break; case 0x00d2: { /* work information */ info->workcity = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->workstate = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->workphone = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->workfax = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->workaddr = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->workzip = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->workcountry = aimbs_getle16(&qbs); info->workcompany = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->workdivision = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->workposition = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); aim_bstream_advance(&qbs, 2); /* 0x01 00 */ info->workwebpage = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); } break; case 0x00e6: { /* additional personal information */ info->info = aimbs_getstr(&qbs, aimbs_getle16(&qbs)-1); } break; case 0x00eb: { /* email address(es) */ int i; info->numaddresses = aimbs_getle16(&qbs); info->email2 = g_new0(char *, info->numaddresses); for (i = 0; i < info->numaddresses; i++) { info->email2[i] = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); if (i+1 != info->numaddresses) aim_bstream_advance(&qbs, 1); /* 0x00 */ } } break; case 0x00f0: { /* personal interests */ } break; case 0x00fa: { /* past background and current organizations */ } break; case 0x0104: { /* alias info */ info->nick = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->first = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->last = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); aim_bstream_advance(&qbs, aimbs_getle16(&qbs)); /* email address? */ /* Then 0x00 02 00 */ } break; case 0x010e: { /* unknown */ /* 0x00 00 */ } break; case 0x019a: { /* simple info */ aim_bstream_advance(&qbs, 2); info->uin = aimbs_getle32(&qbs); info->nick = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->first = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->last = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); info->email = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); /* Then 0x00 02 00 00 00 00 00 */ } break; } /* End switch statement */ if (!(snac->flags & 0x0001)) { if (subtype != 0x0104) if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_INFO))) ret = userfunc(sess, rx, info); /* Bitlbee - not supported, yet if (info->uin && info->nick) if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_ALIAS))) ret = userfunc(sess, rx, info); */ if (sess->icq_info == info) { sess->icq_info = info->next; } else { struct aim_icq_info *cur; for (cur=sess->icq_info; (cur->next && (cur->next!=info)); cur=cur->next); if (cur->next) cur->next = cur->next->next; } aim_icq_freeinfo(info); } } aim_freetlvchain(&tl); return ret; } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if (snac->subtype == 0x0003) return icqresponse(sess, mod, rx, snac, bs); return 0; } int icq_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = 0x0015; mod->version = 0x0001; mod->toolid = 0x0110; mod->toolversion = 0x047c; mod->flags = 0; strncpy(mod->name, "icq", sizeof(mod->name)); mod->snachandler = snachandler; return 0; } bitlbee-3.2.1/protocols/oscar/chat.h0000644000175000017500000000064012245474076016741 0ustar wilmerwilmer#ifndef __OSCAR_CHAT_H__ #define __OSCAR_CHAT_H__ #define AIM_CB_FAM_CHT 0x000e /* Chat */ /* * SNAC Family: Chat Services */ #define AIM_CB_CHT_ERROR 0x0001 #define AIM_CB_CHT_ROOMINFOUPDATE 0x0002 #define AIM_CB_CHT_USERJOIN 0x0003 #define AIM_CB_CHT_USERLEAVE 0x0004 #define AIM_CB_CHT_OUTGOINGMSG 0x0005 #define AIM_CB_CHT_INCOMINGMSG 0x0006 #define AIM_CB_CHT_DEFAULT 0xffff #endif /* __OSCAR_CHAT_H__ */ bitlbee-3.2.1/protocols/oscar/oscar_util.c0000644000175000017500000000244212245474076020163 0ustar wilmerwilmer#include #include /* * int snlen(const char *) * * This takes a screen name and returns its length without * spaces. If there are no spaces in the SN, then the * return is equal to that of strlen(). * */ static int aim_snlen(const char *sn) { int i = 0; const char *curPtr = NULL; if (!sn) return 0; curPtr = sn; while ( (*curPtr) != (char) '\0') { if ((*curPtr) != ' ') i++; curPtr++; } return i; } /* * int sncmp(const char *, const char *) * * This takes two screen names and compares them using the rules * on screen names for AIM/AOL. Mainly, this means case and space * insensitivity (all case differences and spacing differences are * ignored). * * Return: 0 if equal * non-0 if different * */ int aim_sncmp(const char *sn1, const char *sn2) { const char *curPtr1 = NULL, *curPtr2 = NULL; if (aim_snlen(sn1) != aim_snlen(sn2)) return 1; curPtr1 = sn1; curPtr2 = sn2; while ( (*curPtr1 != (char) '\0') && (*curPtr2 != (char) '\0') ) { if ( (*curPtr1 == ' ') || (*curPtr2 == ' ') ) { if (*curPtr1 == ' ') curPtr1++; if (*curPtr2 == ' ') curPtr2++; } else { if ( toupper(*curPtr1) != toupper(*curPtr2)) return 1; curPtr1++; curPtr2++; } } /* Should both be NULL */ if (*curPtr1 != *curPtr2) return 1; return 0; } bitlbee-3.2.1/protocols/oscar/chatnav.h0000644000175000017500000000047312245474076017452 0ustar wilmerwilmer#ifndef __OSCAR_CHATNAV_H__ #define __OSCAR_CHATNAV_H__ #define AIM_CB_FAM_CTN 0x000d /* ChatNav */ /* * SNAC Family: Chat Navigation Services */ #define AIM_CB_CTN_ERROR 0x0001 #define AIM_CB_CTN_CREATE 0x0008 #define AIM_CB_CTN_INFO 0x0009 #define AIM_CB_CTN_DEFAULT 0xffff #endif /* __OSCAR_CHATNAV_H__ */ bitlbee-3.2.1/protocols/oscar/COPYING0000644000175000017500000006347412245474076016722 0ustar wilmerwilmer GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! bitlbee-3.2.1/protocols/oscar/buddylist.c0000644000175000017500000000431612245474076020024 0ustar wilmerwilmer#include #include "buddylist.h" /* * Oncoming Buddy notifications contain a subset of the * user information structure. Its close enough to run * through aim_extractuserinfo() however. * * Although the offgoing notification contains no information, * it is still in a format parsable by extractuserinfo. * */ static int buddychange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_userinfo_t userinfo; aim_rxcallback_t userfunc; aim_extractuserinfo(sess, bs, &userinfo); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) return userfunc(sess, rx, &userinfo); return 0; } static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_rxcallback_t userfunc; aim_tlvlist_t *tlvlist; guint16 maxbuddies = 0, maxwatchers = 0; int ret = 0; /* * TLVs follow */ tlvlist = aim_readtlvchain(bs); /* * TLV type 0x0001: Maximum number of buddies. */ if (aim_gettlv(tlvlist, 0x0001, 1)) maxbuddies = aim_gettlv16(tlvlist, 0x0001, 1); /* * TLV type 0x0002: Maximum number of watchers. * * Watchers are other users who have you on their buddy * list. (This is called the "reverse list" by a certain * other IM protocol.) * */ if (aim_gettlv(tlvlist, 0x0002, 1)) maxwatchers = aim_gettlv16(tlvlist, 0x0002, 1); /* * TLV type 0x0003: Unknown. * * ICQ only? */ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, maxbuddies, maxwatchers); aim_freetlvchain(&tlvlist); return ret; } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if (snac->subtype == 0x0003) return rights(sess, mod, rx, snac, bs); else if ((snac->subtype == 0x000b) || (snac->subtype == 0x000c)) return buddychange(sess, mod, rx, snac, bs); return 0; } int buddylist_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = 0x0003; mod->version = 0x0001; mod->toolid = 0x0110; mod->toolversion = 0x0629; mod->flags = 0; strncpy(mod->name, "buddylist", sizeof(mod->name)); mod->snachandler = snachandler; return 0; } bitlbee-3.2.1/protocols/oscar/info.c0000644000175000017500000003316512245474076016760 0ustar wilmerwilmer/* * aim_info.c * * The functions here are responsible for requesting and parsing information- * gathering SNACs. Or something like that. * */ #include #include "info.h" struct aim_priv_inforeq { char sn[MAXSNLEN+1]; guint16 infotype; }; int aim_getinfo(aim_session_t *sess, aim_conn_t *conn, const char *sn, guint16 infotype) { struct aim_priv_inforeq privdata; aim_frame_t *fr; aim_snacid_t snacid; if (!sess || !conn || !sn) return -EINVAL; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 12+1+strlen(sn)))) return -ENOMEM; strncpy(privdata.sn, sn, sizeof(privdata.sn)); privdata.infotype = infotype; snacid = aim_cachesnac(sess, 0x0002, 0x0005, 0x0000, &privdata, sizeof(struct aim_priv_inforeq)); aim_putsnac(&fr->data, 0x0002, 0x0005, 0x0000, snacid); aimbs_put16(&fr->data, infotype); aimbs_put8(&fr->data, strlen(sn)); aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); aim_tx_enqueue(sess, fr); return 0; } /* * Capability blocks. * * These are CLSIDs. They should actually be of the form: * * {0x0946134b, 0x4c7f, 0x11d1, * {0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}}, * * But, eh. */ static const struct { guint32 flag; guint8 data[16]; } aim_caps[] = { /* * Chat is oddball. */ {AIM_CAPS_CHAT, {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, /* * These are mostly in order. */ {AIM_CAPS_VOICE, {0x09, 0x46, 0x13, 0x41, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, {AIM_CAPS_SENDFILE, {0x09, 0x46, 0x13, 0x43, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, /* * Advertised by the EveryBuddy client. */ {AIM_CAPS_ICQ, {0x09, 0x46, 0x13, 0x44, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, {AIM_CAPS_IMIMAGE, {0x09, 0x46, 0x13, 0x45, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, {AIM_CAPS_BUDDYICON, {0x09, 0x46, 0x13, 0x46, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, {AIM_CAPS_SAVESTOCKS, {0x09, 0x46, 0x13, 0x47, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, {AIM_CAPS_GETFILE, {0x09, 0x46, 0x13, 0x48, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, /* * Client supports channel 2 extended, TLV(0x2711) based messages. * Currently used only by ICQ clients. ICQ clients and clones use this GUID * as message format sign. Trillian client use another GUID in channel 2 * messages to implement its own message format (trillian doesn't use * TLV(x2711) in SecureIM channel 2 messages!). */ {AIM_CAPS_ICQSERVERRELAY, {0x09, 0x46, 0x13, 0x49, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, /* * Indeed, there are two of these. The former appears to be correct, * but in some versions of winaim, the second one is set. Either they * forgot to fix endianness, or they made a typo. It really doesn't * matter which. */ {AIM_CAPS_GAMES, {0x09, 0x46, 0x13, 0x4a, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, {AIM_CAPS_GAMES2, {0x09, 0x46, 0x13, 0x4a, 0x4c, 0x7f, 0x11, 0xd1, 0x22, 0x82, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, {AIM_CAPS_SENDBUDDYLIST, {0x09, 0x46, 0x13, 0x4b, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, {AIM_CAPS_UTF8, {0x09, 0x46, 0x13, 0x4E, 0x4C, 0x7F, 0x11, 0xD1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, {AIM_CAPS_ICQRTF, {0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34, 0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x92}}, {AIM_CAPS_ICQUNKNOWN, {0x2e, 0x7a, 0x64, 0x75, 0xfa, 0xdf, 0x4d, 0xc8, 0x88, 0x6f, 0xea, 0x35, 0x95, 0xfd, 0xb6, 0xdf}}, {AIM_CAPS_EMPTY, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, {AIM_CAPS_TRILLIANCRYPT, {0xf2, 0xe7, 0xc7, 0xf4, 0xfe, 0xad, 0x4d, 0xfb, 0xb2, 0x35, 0x36, 0x79, 0x8b, 0xdf, 0x00, 0x00}}, {AIM_CAPS_APINFO, {0xAA, 0x4A, 0x32, 0xB5, 0xF8, 0x84, 0x48, 0xc6, 0xA3, 0xD7, 0x8C, 0x50, 0x97, 0x19, 0xFD, 0x5B}}, {AIM_CAPS_INTEROP, {0x09, 0x46, 0x13, 0x4d, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, {AIM_CAPS_ICHAT, {0x09, 0x46, 0x00, 0x00, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, {AIM_CAPS_LAST} }; /* * This still takes a length parameter even with a bstream because capabilities * are not naturally bounded. * */ guint32 aim_getcap(aim_session_t *sess, aim_bstream_t *bs, int len) { guint32 flags = 0; int offset; for (offset = 0; aim_bstream_empty(bs) && (offset < len); offset += 0x10) { guint8 *cap; int i, identified; cap = aimbs_getraw(bs, 0x10); for (i = 0, identified = 0; !(aim_caps[i].flag & AIM_CAPS_LAST); i++) { if (memcmp(&aim_caps[i].data, cap, 0x10) == 0) { flags |= aim_caps[i].flag; identified++; break; /* should only match once... */ } } if (!identified) { /*FIXME*/ /*REMOVEME :-) g_strdup_printf("unknown capability: {%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x}\n", cap[0], cap[1], cap[2], cap[3], cap[4], cap[5], cap[6], cap[7], cap[8], cap[9], cap[10], cap[11], cap[12], cap[13], cap[14], cap[15]); */ } g_free(cap); } return flags; } int aim_putcap(aim_bstream_t *bs, guint32 caps) { int i; if (!bs) return -EINVAL; for (i = 0; aim_bstream_empty(bs); i++) { if (aim_caps[i].flag == AIM_CAPS_LAST) break; if (caps & aim_caps[i].flag) aimbs_putraw(bs, aim_caps[i].data, 0x10); } return 0; } /* * AIM is fairly regular about providing user info. This is a generic * routine to extract it in its standard form. */ int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *outinfo) { int curtlv, tlvcnt; guint8 snlen; if (!bs || !outinfo) return -EINVAL; /* Clear out old data first */ memset(outinfo, 0x00, sizeof(aim_userinfo_t)); /* * Screen name. Stored as an unterminated string prepended with a * byte containing its length. */ snlen = aimbs_get8(bs); aimbs_getrawbuf(bs, (guint8 *)outinfo->sn, snlen); /* * Warning Level. Stored as an unsigned short. */ outinfo->warnlevel = aimbs_get16(bs); /* * TLV Count. Unsigned short representing the number of * Type-Length-Value triples that follow. */ tlvcnt = aimbs_get16(bs); /* * Parse out the Type-Length-Value triples as they're found. */ for (curtlv = 0; curtlv < tlvcnt; curtlv++) { int endpos; guint16 type, length; type = aimbs_get16(bs); length = aimbs_get16(bs); endpos = aim_bstream_curpos(bs) + length; if (type == 0x0001) { /* * Type = 0x0001: User flags * * Specified as any of the following ORed together: * 0x0001 Trial (user less than 60days) * 0x0002 Unknown bit 2 * 0x0004 AOL Main Service user * 0x0008 Unknown bit 4 * 0x0010 Free (AIM) user * 0x0020 Away * 0x0400 ActiveBuddy * */ outinfo->flags = aimbs_get16(bs); outinfo->present |= AIM_USERINFO_PRESENT_FLAGS; } else if (type == 0x0002) { /* * Type = 0x0002: Member-Since date. * * The time/date that the user originally registered for * the service, stored in time_t format. */ outinfo->membersince = aimbs_get32(bs); outinfo->present |= AIM_USERINFO_PRESENT_MEMBERSINCE; } else if (type == 0x0003) { /* * Type = 0x0003: On-Since date. * * The time/date that the user started their current * session, stored in time_t format. */ outinfo->onlinesince = aimbs_get32(bs); outinfo->present |= AIM_USERINFO_PRESENT_ONLINESINCE; } else if (type == 0x0004) { /* * Type = 0x0004: Idle time. * * Number of seconds since the user actively used the * service. * * Note that the client tells the server when to start * counting idle times, so this may or may not be * related to reality. */ outinfo->idletime = aimbs_get16(bs); outinfo->present |= AIM_USERINFO_PRESENT_IDLE; } else if (type == 0x0006) { /* * Type = 0x0006: ICQ Online Status * * ICQ's Away/DND/etc "enriched" status. Some decoding * of values done by Scott */ aimbs_get16(bs); outinfo->icqinfo.status = aimbs_get16(bs); outinfo->present |= AIM_USERINFO_PRESENT_ICQEXTSTATUS; } else if (type == 0x000a) { /* * Type = 0x000a * * ICQ User IP Address. * Ahh, the joy of ICQ security. */ outinfo->icqinfo.ipaddr = aimbs_get32(bs); outinfo->present |= AIM_USERINFO_PRESENT_ICQIPADDR; } else if (type == 0x000c) { /* * Type = 0x000c * * random crap containing the IP address, * apparently a port number, and some Other Stuff. * */ aimbs_getrawbuf(bs, outinfo->icqinfo.crap, 0x25); outinfo->present |= AIM_USERINFO_PRESENT_ICQDATA; } else if (type == 0x000d) { /* * Type = 0x000d * * Capability information. * */ outinfo->capabilities = aim_getcap(sess, bs, length); outinfo->present |= AIM_USERINFO_PRESENT_CAPABILITIES; } else if (type == 0x000e) { /* * Type = 0x000e * * Unknown. Always of zero length, and always only * on AOL users. * * Ignore. * */ } else if ((type == 0x000f) || (type == 0x0010)) { /* * Type = 0x000f: Session Length. (AIM) * Type = 0x0010: Session Length. (AOL) * * The duration, in seconds, of the user's current * session. * * Which TLV type this comes in depends on the * service the user is using (AIM or AOL). * */ outinfo->sessionlen = aimbs_get32(bs); outinfo->present |= AIM_USERINFO_PRESENT_SESSIONLEN; } else { /* * Reaching here indicates that either AOL has * added yet another TLV for us to deal with, * or the parsing has gone Terribly Wrong. * * Either way, inform the owner and attempt * recovery. * */ #ifdef DEBUG // imcb_error(sess->aux_data, G_STRLOC); #endif } /* Save ourselves. */ aim_bstream_setpos(bs, endpos); } return 0; } /* * Normally contains: * t(0001) - short containing max profile length (value = 1024) * t(0002) - short - unknown (value = 16) [max MIME type length?] * t(0003) - short - unknown (value = 10) * t(0004) - short - unknown (value = 2048) [ICQ only?] */ static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_tlvlist_t *tlvlist; aim_rxcallback_t userfunc; int ret = 0; guint16 maxsiglen = 0; tlvlist = aim_readtlvchain(bs); if (aim_gettlv(tlvlist, 0x0001, 1)) maxsiglen = aim_gettlv16(tlvlist, 0x0001, 1); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, maxsiglen); aim_freetlvchain(&tlvlist); return ret; } static int userinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_userinfo_t userinfo; char *text_encoding = NULL, *text = NULL; guint16 text_length = 0; aim_rxcallback_t userfunc; aim_tlvlist_t *tlvlist; aim_tlv_t *tlv; aim_snac_t *origsnac = NULL; struct aim_priv_inforeq *inforeq; int ret = 0; origsnac = aim_remsnac(sess, snac->id); if (!origsnac || !origsnac->data) { imcb_error(sess->aux_data, "major problem: no snac stored!"); return 0; } inforeq = (struct aim_priv_inforeq *)origsnac->data; if ((inforeq->infotype != AIM_GETINFO_GENERALINFO) && (inforeq->infotype != AIM_GETINFO_AWAYMESSAGE) && (inforeq->infotype != AIM_GETINFO_CAPABILITIES)) { imcb_error(sess->aux_data, "unknown infotype in request!"); return 0; } aim_extractuserinfo(sess, bs, &userinfo); tlvlist = aim_readtlvchain(bs); /* * Depending on what informational text was requested, different * TLVs will appear here. * * Profile will be 1 and 2, away message will be 3 and 4, caps * will be 5. */ if (inforeq->infotype == AIM_GETINFO_GENERALINFO) { text_encoding = aim_gettlv_str(tlvlist, 0x0001, 1); if((tlv = aim_gettlv(tlvlist, 0x0002, 1))) { text = g_new0(char, tlv->length); memcpy(text, tlv->value, tlv->length); text_length = tlv->length; } } else if (inforeq->infotype == AIM_GETINFO_AWAYMESSAGE) { text_encoding = aim_gettlv_str(tlvlist, 0x0003, 1); if((tlv = aim_gettlv(tlvlist, 0x0004, 1))) { text = g_new0(char, tlv->length); memcpy(text, tlv->value, tlv->length); text_length = tlv->length; } } else if (inforeq->infotype == AIM_GETINFO_CAPABILITIES) { aim_tlv_t *ct; if ((ct = aim_gettlv(tlvlist, 0x0005, 1))) { aim_bstream_t cbs; aim_bstream_init(&cbs, ct->value, ct->length); userinfo.capabilities = aim_getcap(sess, &cbs, ct->length); userinfo.present = AIM_USERINFO_PRESENT_CAPABILITIES; } } if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, &userinfo, inforeq->infotype, text_encoding, text, text_length); g_free(text_encoding); g_free(text); aim_freetlvchain(&tlvlist); if (origsnac) g_free(origsnac->data); g_free(origsnac); return ret; } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if (snac->subtype == 0x0003) return rights(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0006) return userinfo(sess, mod, rx, snac, bs); return 0; } int locate_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = 0x0002; mod->version = 0x0001; mod->toolid = 0x0110; mod->toolversion = 0x0629; mod->flags = 0; strncpy(mod->name, "locate", sizeof(mod->name)); mod->snachandler = snachandler; return 0; } bitlbee-3.2.1/protocols/oscar/im.c0000644000175000017500000012202312245474076016422 0ustar wilmerwilmer/* * aim_im.c * * The routines for sending/receiving Instant Messages. * * Note the term ICBM (Inter-Client Basic Message) which blankets * all types of genericly routed through-server messages. Within * the ICBM types (family 4), a channel is defined. Each channel * represents a different type of message. Channel 1 is used for * what would commonly be called an "instant message". Channel 2 * is used for negotiating "rendezvous". These transactions end in * something more complex happening, such as a chat invitation, or * a file transfer. * * In addition to the channel, every ICBM contains a cookie. For * standard IMs, these are only used for error messages. However, * the more complex rendezvous messages make suitably more complex * use of this field. * */ #include #include "im.h" #include "info.h" /* * Send an ICBM (instant message). * * * Possible flags: * AIM_IMFLAGS_AWAY -- Marks the message as an autoresponse * AIM_IMFLAGS_ACK -- Requests that the server send an ack * when the message is received (of type 0x0004/0x000c) * AIM_IMFLAGS_OFFLINE--If destination is offline, store it until they are * online (probably ICQ only). * AIM_IMFLAGS_UNICODE--Instead of ASCII7, the passed message is * made up of UNICODE duples. If you set * this, you'd better be damn sure you know * what you're doing. * AIM_IMFLAGS_ISO_8859_1 -- The message contains the ASCII8 subset * known as ISO-8859-1. * * Generally, you should use the lowest encoding possible to send * your message. If you only use basic punctuation and the generic * Latin alphabet, use ASCII7 (no flags). If you happen to use non-ASCII7 * characters, but they are all clearly defined in ISO-8859-1, then * use that. Keep in mind that not all characters in the PC ASCII8 * character set are defined in the ISO standard. For those cases (most * notably when the (r) symbol is used), you must use the full UNICODE * encoding for your message. In UNICODE mode, _all_ characters must * occupy 16bits, including ones that are not special. (Remember that * the first 128 UNICODE symbols are equivelent to ASCII7, however they * must be prefixed with a zero high order byte.) * * I strongly discourage the use of UNICODE mode, mainly because none * of the clients I use can parse those messages (and besides that, * wchars are difficult and non-portable to handle in most UNIX environments). * If you really need to include special characters, use the HTML UNICODE * entities. These are of the form ߪ where 2026 is the hex * representation of the UNICODE index (in this case, UNICODE * "Horizontal Ellipsis", or 133 in in ASCII8). * * Implementation note: Since this is one of the most-used functions * in all of libfaim, it is written with performance in mind. As such, * it is not as clear as it could be in respect to how this message is * supposed to be layed out. Most obviously, tlvlists should be used * instead of writing out the bytes manually. * * XXX more precise verification that we never send SNACs larger than 8192 * XXX check SNAC size for multipart * */ int aim_send_im_ext(aim_session_t *sess, struct aim_sendimext_args *args) { static const guint8 deffeatures[] = { 0x01, 0x01, 0x01, 0x02 }; aim_conn_t *conn; int i, msgtlvlen; aim_frame_t *fr; aim_snacid_t snacid; if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) return -EINVAL; if (!args) return -EINVAL; if (args->flags & AIM_IMFLAGS_MULTIPART) { if (args->mpmsg->numparts <= 0) return -EINVAL; } else { if (!args->msg || (args->msglen <= 0)) return -EINVAL; if (args->msglen >= MAXMSGLEN) return -E2BIG; } /* Painfully calculate the size of the message TLV */ msgtlvlen = 1 + 1; /* 0501 */ if (args->flags & AIM_IMFLAGS_CUSTOMFEATURES) msgtlvlen += 2 + args->featureslen; else msgtlvlen += 2 + sizeof(deffeatures); if (args->flags & AIM_IMFLAGS_MULTIPART) { aim_mpmsg_section_t *sec; for (sec = args->mpmsg->parts; sec; sec = sec->next) { msgtlvlen += 2 /* 0101 */ + 2 /* block len */; msgtlvlen += 4 /* charset */ + sec->datalen; } } else { msgtlvlen += 2 /* 0101 */ + 2 /* block len */; msgtlvlen += 4 /* charset */ + args->msglen; } if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, msgtlvlen+128))) return -ENOMEM; /* XXX should be optional */ snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, args->destsn, strlen(args->destsn)+1); aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); /* * Generate a random message cookie * * We could cache these like we do SNAC IDs. (In fact, it * might be a good idea.) In the message error functions, * the 8byte message cookie is returned as well as the * SNAC ID. * */ for (i = 0; i < 8; i++) aimbs_put8(&fr->data, (guint8) rand()); /* * Channel ID */ aimbs_put16(&fr->data, 0x0001); /* * Destination SN (prepended with byte length) */ aimbs_put8(&fr->data, strlen(args->destsn)); aimbs_putraw(&fr->data, (guint8 *)args->destsn, strlen(args->destsn)); /* * Message TLV (type 2). */ aimbs_put16(&fr->data, 0x0002); aimbs_put16(&fr->data, msgtlvlen); /* * Features * */ aimbs_put8(&fr->data, 0x05); aimbs_put8(&fr->data, 0x01); if (args->flags & AIM_IMFLAGS_CUSTOMFEATURES) { aimbs_put16(&fr->data, args->featureslen); aimbs_putraw(&fr->data, args->features, args->featureslen); } else { aimbs_put16(&fr->data, sizeof(deffeatures)); aimbs_putraw(&fr->data, deffeatures, sizeof(deffeatures)); } if (args->flags & AIM_IMFLAGS_MULTIPART) { aim_mpmsg_section_t *sec; for (sec = args->mpmsg->parts; sec; sec = sec->next) { aimbs_put16(&fr->data, 0x0101); aimbs_put16(&fr->data, sec->datalen + 4); aimbs_put16(&fr->data, sec->charset); aimbs_put16(&fr->data, sec->charsubset); aimbs_putraw(&fr->data, sec->data, sec->datalen); } } else { aimbs_put16(&fr->data, 0x0101); /* * Message block length. */ aimbs_put16(&fr->data, args->msglen + 0x04); /* * Character set. */ if (args->flags & AIM_IMFLAGS_CUSTOMCHARSET) { aimbs_put16(&fr->data, args->charset); aimbs_put16(&fr->data, args->charsubset); } else { if (args->flags & AIM_IMFLAGS_UNICODE) aimbs_put16(&fr->data, 0x0002); else if (args->flags & AIM_IMFLAGS_ISO_8859_1) aimbs_put16(&fr->data, 0x0003); else aimbs_put16(&fr->data, 0x0000); aimbs_put16(&fr->data, 0x0000); } /* * Message. Not terminated. */ aimbs_putraw(&fr->data, (guint8 *)args->msg, args->msglen); } /* * Set the Request Acknowledge flag. */ if (args->flags & AIM_IMFLAGS_ACK) { aimbs_put16(&fr->data, 0x0003); aimbs_put16(&fr->data, 0x0000); } /* * Set the Autoresponse flag. */ if (args->flags & AIM_IMFLAGS_AWAY) { aimbs_put16(&fr->data, 0x0004); aimbs_put16(&fr->data, 0x0000); } if (args->flags & AIM_IMFLAGS_OFFLINE) { aimbs_put16(&fr->data, 0x0006); aimbs_put16(&fr->data, 0x0000); } /* * Set the I HAVE A REALLY PURTY ICON flag. */ if (args->flags & AIM_IMFLAGS_HASICON) { aimbs_put16(&fr->data, 0x0008); aimbs_put16(&fr->data, 0x000c); aimbs_put32(&fr->data, args->iconlen); aimbs_put16(&fr->data, 0x0001); aimbs_put16(&fr->data, args->iconsum); aimbs_put32(&fr->data, args->iconstamp); } /* * Set the Buddy Icon Requested flag. */ if (args->flags & AIM_IMFLAGS_BUDDYREQ) { aimbs_put16(&fr->data, 0x0009); aimbs_put16(&fr->data, 0x0000); } aim_tx_enqueue(sess, fr); if (!(sess->flags & AIM_SESS_FLAGS_DONTTIMEOUTONICBM)) aim_cleansnacs(sess, 60); /* clean out SNACs over 60sec old */ return 0; } /* * Simple wrapper for aim_send_im_ext() * * You cannot use aim_send_im if you need the HASICON flag. You must * use aim_send_im_ext directly for that. * * aim_send_im also cannot be used if you require UNICODE messages, because * that requires an explicit message length. Use aim_send_im_ext(). * */ int aim_send_im(aim_session_t *sess, const char *destsn, guint16 flags, const char *msg) { struct aim_sendimext_args args; args.destsn = destsn; args.flags = flags; args.msg = msg; args.msglen = strlen(msg); /* Make these don't get set by accident -- they need aim_send_im_ext */ args.flags &= ~(AIM_IMFLAGS_CUSTOMFEATURES | AIM_IMFLAGS_HASICON | AIM_IMFLAGS_MULTIPART); return aim_send_im_ext(sess, &args); } /** * answers status message requests * @param sess the oscar session * @param sender the guy whos asking * @param cookie message id which we are answering for * @param message away message * @param state our current away state the way icq requests it (0xE8 for away, 0xE9 occupied, ...) * @return 0 if no error */ int aim_send_im_ch2_statusmessage(aim_session_t *sess, const char *sender, const guint8 *cookie, const char *message, const guint8 state, const guint16 dc) { aim_conn_t *conn; aim_frame_t *fr; aim_snacid_t snacid; if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) return -EINVAL; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sender)+2+0x1d+0x10+9+strlen(message)+1))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0004, 0x000b, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x0004, 0x000b, 0x0000, snacid); aimbs_putraw(&fr->data, cookie, 8); aimbs_put16(&fr->data, 0x0002); /* channel */ aimbs_put8(&fr->data, strlen(sender)); aimbs_putraw(&fr->data, (guint8 *)sender, strlen(sender)); aimbs_put16(&fr->data, 0x0003); /* reason: channel specific */ aimbs_putle16(&fr->data, 0x001b); /* length of data SEQ1 */ aimbs_putle16(&fr->data, 0x0008); /* protocol version */ aimbs_putle32(&fr->data, 0x0000); /* no plugin -> 16 times 0x00 */ aimbs_putle32(&fr->data, 0x0000); aimbs_putle32(&fr->data, 0x0000); aimbs_putle32(&fr->data, 0x0000); aimbs_putle16(&fr->data, 0x0000); /* unknown */ aimbs_putle32(&fr->data, 0x0003); /* client features */ aimbs_putle8(&fr->data, 0x00); /* unknown */ aimbs_putle16(&fr->data, dc); /* Sequence number? XXX - This should decrement by 1 with each request */ /* end of SEQ1 */ aimbs_putle16(&fr->data, 0x000e); /* Length of SEQ2 */ aimbs_putle16(&fr->data, dc); /* Sequence number? same as above * XXX - This should decrement by 1 with each request */ aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ /* end of SEQ2 */ /* now for the real fun */ aimbs_putle8(&fr->data, state); /* away state */ aimbs_putle8(&fr->data, 0x03); /* msg-flag: 03 for states */ aimbs_putle16(&fr->data, 0x0000); /* status code ? */ aimbs_putle16(&fr->data, 0x0000); /* priority code */ aimbs_putle16(&fr->data, strlen(message) + 1); /* message length + termination */ aimbs_putraw(&fr->data, (guint8 *) message, strlen(message) + 1); /* null terminated string */ aim_tx_enqueue(sess, fr); return 0; } static int outgoingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int i, ret = 0; aim_rxcallback_t userfunc; guint16 channel; aim_tlvlist_t *tlvlist; char *sn; int snlen; guint16 icbmflags = 0; guint8 flag1 = 0, flag2 = 0; char *msg = NULL; aim_tlv_t *msgblock; /* ICBM Cookie. */ for (i = 0; i < 8; i++) aimbs_get8(bs); /* Channel ID */ channel = aimbs_get16(bs); if (channel != 0x01) { imcb_error(sess->aux_data, "icbm: ICBM received on unsupported channel. Ignoring."); return 0; } snlen = aimbs_get8(bs); sn = aimbs_getstr(bs, snlen); tlvlist = aim_readtlvchain(bs); if (aim_gettlv(tlvlist, 0x0003, 1)) icbmflags |= AIM_IMFLAGS_ACK; if (aim_gettlv(tlvlist, 0x0004, 1)) icbmflags |= AIM_IMFLAGS_AWAY; if ((msgblock = aim_gettlv(tlvlist, 0x0002, 1))) { aim_bstream_t mbs; int featurelen, msglen; aim_bstream_init(&mbs, msgblock->value, msgblock->length); aimbs_get8(&mbs); aimbs_get8(&mbs); for (featurelen = aimbs_get16(&mbs); featurelen; featurelen--) aimbs_get8(&mbs); aimbs_get8(&mbs); aimbs_get8(&mbs); msglen = aimbs_get16(&mbs) - 4; /* final block length */ flag1 = aimbs_get16(&mbs); flag2 = aimbs_get16(&mbs); msg = aimbs_getstr(&mbs, msglen); } if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, channel, sn, msg, icbmflags, flag1, flag2); g_free(sn); aim_freetlvchain(&tlvlist); return ret; } /* * Ahh, the joys of nearly ridiculous over-engineering. * * Not only do AIM ICBM's support multiple channels. Not only do they * support multiple character sets. But they support multiple character * sets / encodings within the same ICBM. * * These multipart messages allow for complex space savings techniques, which * seem utterly unnecessary by today's standards. In fact, there is only * one client still in popular use that still uses this method: AOL for the * Macintosh, Version 5.0. Obscure, yes, I know. * * In modern (non-"legacy") clients, if the user tries to send a character * that is not ISO-8859-1 or ASCII, the client will send the entire message * as UNICODE, meaning that every character in the message will occupy the * full 16 bit UNICODE field, even if the high order byte would be zero. * Multipart messages prevent this wasted space by allowing the client to * only send the characters in UNICODE that need to be sent that way, and * the rest of the message can be sent in whatever the native character * set is (probably ASCII). * * An important note is that sections will be displayed in the order that * they appear in the ICBM. There is no facility for merging or rearranging * sections at run time. So if you have, say, ASCII then UNICODE then ASCII, * you must supply two ASCII sections with a UNICODE in the middle, and incur * the associated overhead. * * Normally I would have laughed and given a firm 'no' to supporting this * seldom-used feature, but something is attracting me to it. In the future, * it may be possible to abuse this to send mixed-media messages to other * open source clients (like encryption or something) -- see faimtest for * examples of how to do this. * * I would definitly recommend avoiding this feature unless you really * know what you are doing, and/or you have something neat to do with it. * */ int aim_mpmsg_init(aim_session_t *sess, aim_mpmsg_t *mpm) { memset(mpm, 0, sizeof(aim_mpmsg_t)); return 0; } static int mpmsg_addsection(aim_session_t *sess, aim_mpmsg_t *mpm, guint16 charset, guint16 charsubset, guint8 *data, guint16 datalen) { aim_mpmsg_section_t *sec; if (!(sec = g_new0(aim_mpmsg_section_t,1))) return -1; sec->charset = charset; sec->charsubset = charsubset; sec->data = data; sec->datalen = datalen; sec->next = NULL; if (!mpm->parts) mpm->parts = sec; else { aim_mpmsg_section_t *cur; for (cur = mpm->parts; cur->next; cur = cur->next) ; cur->next = sec; } mpm->numparts++; return 0; } void aim_mpmsg_free(aim_session_t *sess, aim_mpmsg_t *mpm) { aim_mpmsg_section_t *cur; for (cur = mpm->parts; cur; ) { aim_mpmsg_section_t *tmp; tmp = cur->next; g_free(cur->data); g_free(cur); cur = tmp; } mpm->numparts = 0; mpm->parts = NULL; return; } /* * Start by building the multipart structures, then pick the first * human-readable section and stuff it into args->msg so no one gets * suspicious. * */ static int incomingim_ch1_parsemsgs(aim_session_t *sess, guint8 *data, int len, struct aim_incomingim_ch1_args *args) { static const guint16 charsetpri[] = { 0x0000, /* ASCII first */ 0x0003, /* then ISO-8859-1 */ 0x0002, /* UNICODE as last resort */ }; static const int charsetpricount = 3; int i; aim_bstream_t mbs; aim_mpmsg_section_t *sec; aim_bstream_init(&mbs, data, len); while (aim_bstream_empty(&mbs)) { guint16 msglen, flag1, flag2; char *msgbuf; aimbs_get8(&mbs); /* 01 */ aimbs_get8(&mbs); /* 01 */ /* Message string length, including character set info. */ msglen = aimbs_get16(&mbs); /* Character set info */ flag1 = aimbs_get16(&mbs); flag2 = aimbs_get16(&mbs); /* Message. */ msglen -= 4; /* * For now, we don't care what the encoding is. Just copy * it into a multipart struct and deal with it later. However, * always pad the ending with a NULL. This makes it easier * to treat ASCII sections as strings. It won't matter for * UNICODE or binary data, as you should never read past * the specified data length, which will not include the pad. * * XXX There's an API bug here. For sending, the UNICODE is * given in host byte order (aim_mpmsg_addunicode), but here * the received messages are given in network byte order. * */ msgbuf = aimbs_getstr(&mbs, msglen); mpmsg_addsection(sess, &args->mpmsg, flag1, flag2, (guint8 *)msgbuf, (guint16) msglen); } /* while */ args->icbmflags |= AIM_IMFLAGS_MULTIPART; /* always set */ /* * Clients that support multiparts should never use args->msg, as it * will point to an arbitrary section. * * Here, we attempt to provide clients that do not support multipart * messages with something to look at -- hopefully a human-readable * string. But, failing that, a UNICODE message, or nothing at all. * * Which means that even if args->msg is NULL, it does not mean the * message was blank. * */ for (i = 0; i < charsetpricount; i++) { for (sec = args->mpmsg.parts; sec; sec = sec->next) { if (sec->charset != charsetpri[i]) continue; /* Great. We found one. Fill it in. */ args->charset = sec->charset; args->charsubset = sec->charsubset; args->icbmflags |= AIM_IMFLAGS_CUSTOMCHARSET; /* Set up the simple flags */ if (args->charset == 0x0000) ; /* ASCII */ else if (args->charset == 0x0002) args->icbmflags |= AIM_IMFLAGS_UNICODE; else if (args->charset == 0x0003) args->icbmflags |= AIM_IMFLAGS_ISO_8859_1; else if (args->charset == 0xffff) ; /* no encoding (yeep!) */ if (args->charsubset == 0x0000) ; /* standard subencoding? */ else if (args->charsubset == 0x000b) args->icbmflags |= AIM_IMFLAGS_SUBENC_MACINTOSH; else if (args->charsubset == 0xffff) ; /* no subencoding */ #if 0 /* XXX this isn't really necesary... */ if ( ((args.flag1 != 0x0000) && (args.flag1 != 0x0002) && (args.flag1 != 0x0003) && (args.flag1 != 0xffff)) || ((args.flag2 != 0x0000) && (args.flag2 != 0x000b) && (args.flag2 != 0xffff))) { faimdprintf(sess, 0, "icbm: **warning: encoding flags are being used! {%04x, %04x}\n", args.flag1, args.flag2); } #endif args->msg = (char *)sec->data; args->msglen = sec->datalen; return 0; } } /* No human-readable sections found. Oh well. */ args->charset = args->charsubset = 0xffff; args->msg = NULL; args->msglen = 0; return 0; } static int incomingim_ch1(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, aim_bstream_t *bs, guint8 *cookie) { guint16 type, length; aim_rxcallback_t userfunc; int ret = 0; struct aim_incomingim_ch1_args args; int endpos; memset(&args, 0, sizeof(args)); aim_mpmsg_init(sess, &args.mpmsg); /* * This used to be done using tlvchains. For performance reasons, * I've changed it to process the TLVs in-place. This avoids lots * of per-IM memory allocations. */ while (aim_bstream_empty(bs)) { type = aimbs_get16(bs); length = aimbs_get16(bs); endpos = aim_bstream_curpos(bs) + length; if (type == 0x0002) { /* Message Block */ /* * This TLV consists of the following: * - 0501 -- Unknown * - Features: Don't know how to interpret these * - 0101 -- Unknown * - Message * */ aimbs_get8(bs); /* 05 */ aimbs_get8(bs); /* 01 */ args.featureslen = aimbs_get16(bs); /* XXX XXX this is all evil! */ args.features = bs->data + bs->offset; aim_bstream_advance(bs, args.featureslen); args.icbmflags |= AIM_IMFLAGS_CUSTOMFEATURES; /* * The rest of the TLV contains one or more message * blocks... */ incomingim_ch1_parsemsgs(sess, bs->data + bs->offset /* XXX evil!!! */, length - 2 - 2 - args.featureslen, &args); } else if (type == 0x0003) { /* Server Ack Requested */ args.icbmflags |= AIM_IMFLAGS_ACK; } else if (type == 0x0004) { /* Message is Auto Response */ args.icbmflags |= AIM_IMFLAGS_AWAY; } else if (type == 0x0006) { /* Message was received offline. */ /* XXX not sure if this actually gets sent. */ args.icbmflags |= AIM_IMFLAGS_OFFLINE; } else if (type == 0x0008) { /* I-HAVE-A-REALLY-PURTY-ICON Flag */ args.iconlen = aimbs_get32(bs); aimbs_get16(bs); /* 0x0001 */ args.iconsum = aimbs_get16(bs); args.iconstamp = aimbs_get32(bs); /* * This looks to be a client bug. MacAIM 4.3 will * send this tag, but with all zero values, in the * first message of a conversation. This makes no * sense whatsoever, so I'm going to say its a bug. * * You really shouldn't advertise a zero-length icon * anyway. * */ if (args.iconlen) args.icbmflags |= AIM_IMFLAGS_HASICON; } else if (type == 0x0009) { args.icbmflags |= AIM_IMFLAGS_BUDDYREQ; } else if (type == 0x0017) { args.extdatalen = length; args.extdata = aimbs_getraw(bs, args.extdatalen); } else { // imcb_error(sess->aux_data, "Unknown TLV encountered"); } /* * This is here to protect ourselves from ourselves. That * is, if something above doesn't completly parse its value * section, or, worse, overparses it, this will set the * stream where it needs to be in order to land on the next * TLV when the loop continues. * */ aim_bstream_setpos(bs, endpos); } if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, channel, userinfo, &args); aim_mpmsg_free(sess, &args.mpmsg); g_free(args.extdata); return ret; } static void incomingim_ch2_chat_free(aim_session_t *sess, struct aim_incomingim_ch2_args *args) { /* XXX aim_chat_roominfo_free() */ g_free(args->info.chat.roominfo.name); return; } static void incomingim_ch2_chat(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args, aim_bstream_t *servdata) { /* * Chat room info. */ if (servdata) aim_chat_readroominfo(servdata, &args->info.chat.roominfo); args->destructor = (void *)incomingim_ch2_chat_free; return; } static void incomingim_ch2_icqserverrelay_free(aim_session_t *sess, struct aim_incomingim_ch2_args *args) { g_free((char *)args->info.rtfmsg.rtfmsg); return; } /* * The relationship between AIM_CAPS_ICQSERVERRELAY and AIM_CAPS_ICQRTF is * kind of odd. This sends the client ICQRTF since that is all that I've seen * SERVERRELAY used for. * * Note that this is all little-endian. Cringe. * * This cap is used for auto status message replies, too [ft] * */ static void incomingim_ch2_icqserverrelay(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args, aim_bstream_t *servdata) { guint16 hdrlen, msglen, dc; guint8 msgtype; guint8 *plugin; int i = 0, tmp = 0; struct im_connection *ic = sess->aux_data; /* at the moment we just can deal with requests, not with cancel or accept */ if (args->status != 0) return; hdrlen = aimbs_getle16(servdata); aim_bstream_advance(servdata, 0x02); /* protocol version */ plugin = aimbs_getraw(servdata, 0x10); /* following data is a message or something plugin specific */ /* as there is no plugin handling, just skip the rest */ aim_bstream_advance(servdata, hdrlen - 0x12); hdrlen = aimbs_getle16(servdata); dc = aimbs_getle16(servdata); /* save the sequence number */ aim_bstream_advance(servdata, hdrlen - 0x02); /* TODO is it a message or something for a plugin? */ for (i = 0; i < 0x10; i++) { tmp |= plugin[i]; } if (!tmp) { /* message follows */ msgtype = aimbs_getle8(servdata); aimbs_getle8(servdata); /* msgflags */ aim_bstream_advance(servdata, 0x04); /* status code and priority code */ msglen = aimbs_getle16(servdata); /* message string length */ args->info.rtfmsg.rtfmsg = aimbs_getstr(servdata, msglen); switch(msgtype) { case AIM_MTYPE_PLAIN: args->info.rtfmsg.fgcolor = aimbs_getle32(servdata); args->info.rtfmsg.bgcolor = aimbs_getle32(servdata); hdrlen = aimbs_getle32(servdata); aim_bstream_advance(servdata, hdrlen); /* XXX This is such a hack. */ args->reqclass = AIM_CAPS_ICQRTF; break; case AIM_MTYPE_AUTOAWAY: case AIM_MTYPE_AUTOBUSY: case AIM_MTYPE_AUTONA: case AIM_MTYPE_AUTODND: case AIM_MTYPE_AUTOFFC: case 0x9c: /* ICQ 5 seems to send this */ aim_send_im_ch2_statusmessage(sess, userinfo->sn, args->cookie, ic->away ? ic->away : "", sess->aim_icq_state, dc); break; } } /* message or plugin specific */ g_free(plugin); args->destructor = (void *)incomingim_ch2_icqserverrelay_free; return; } typedef void (*ch2_args_destructor_t)(aim_session_t *sess, struct aim_incomingim_ch2_args *args); static int incomingim_ch2(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, aim_tlvlist_t *tlvlist, guint8 *cookie) { aim_rxcallback_t userfunc; aim_tlv_t *block1, *servdatatlv; aim_tlvlist_t *list2; struct aim_incomingim_ch2_args args; aim_bstream_t bbs, sdbs, *sdbsptr = NULL; guint8 *cookie2; int ret = 0; char clientip1[30] = {""}; char clientip2[30] = {""}; char verifiedip[30] = {""}; memset(&args, 0, sizeof(args)); /* * There's another block of TLVs embedded in the type 5 here. */ block1 = aim_gettlv(tlvlist, 0x0005, 1); aim_bstream_init(&bbs, block1->value, block1->length); /* * First two bytes represent the status of the connection. * * 0 is a request, 1 is a deny (?), 2 is an accept */ args.status = aimbs_get16(&bbs); /* * Next comes the cookie. Should match the ICBM cookie. */ cookie2 = aimbs_getraw(&bbs, 8); if (memcmp(cookie, cookie2, 8) != 0) imcb_error(sess->aux_data, "rend: warning cookies don't match!"); memcpy(args.cookie, cookie2, 8); g_free(cookie2); /* * The next 16bytes are a capability block so we can * identify what type of rendezvous this is. */ args.reqclass = aim_getcap(sess, &bbs, 0x10); /* * What follows may be TLVs or nothing, depending on the * purpose of the message. * * Ack packets for instance have nothing more to them. */ list2 = aim_readtlvchain(&bbs); /* * IP address from the perspective of the client. */ if (aim_gettlv(list2, 0x0002, 1)) { aim_tlv_t *iptlv; iptlv = aim_gettlv(list2, 0x0002, 1); g_snprintf(clientip1, sizeof(clientip1), "%d.%d.%d.%d", aimutil_get8(iptlv->value+0), aimutil_get8(iptlv->value+1), aimutil_get8(iptlv->value+2), aimutil_get8(iptlv->value+3)); } /* * Secondary IP address from the perspective of the client. */ if (aim_gettlv(list2, 0x0003, 1)) { aim_tlv_t *iptlv; iptlv = aim_gettlv(list2, 0x0003, 1); g_snprintf(clientip2, sizeof(clientip2), "%d.%d.%d.%d", aimutil_get8(iptlv->value+0), aimutil_get8(iptlv->value+1), aimutil_get8(iptlv->value+2), aimutil_get8(iptlv->value+3)); } /* * Verified IP address (from the perspective of Oscar). * * This is added by the server. */ if (aim_gettlv(list2, 0x0004, 1)) { aim_tlv_t *iptlv; iptlv = aim_gettlv(list2, 0x0004, 1); g_snprintf(verifiedip, sizeof(verifiedip), "%d.%d.%d.%d", aimutil_get8(iptlv->value+0), aimutil_get8(iptlv->value+1), aimutil_get8(iptlv->value+2), aimutil_get8(iptlv->value+3)); } /* * Port number for something. */ if (aim_gettlv(list2, 0x0005, 1)) args.port = aim_gettlv16(list2, 0x0005, 1); /* * Error code. */ if (aim_gettlv(list2, 0x000b, 1)) args.errorcode = aim_gettlv16(list2, 0x000b, 1); /* * Invitation message / chat description. */ if (aim_gettlv(list2, 0x000c, 1)) args.msg = aim_gettlv_str(list2, 0x000c, 1); /* * Character set. */ if (aim_gettlv(list2, 0x000d, 1)) args.encoding = aim_gettlv_str(list2, 0x000d, 1); /* * Language. */ if (aim_gettlv(list2, 0x000e, 1)) args.language = aim_gettlv_str(list2, 0x000e, 1); /* Unknown -- two bytes = 0x0001 */ if (aim_gettlv(list2, 0x000a, 1)) ; /* Unknown -- no value */ if (aim_gettlv(list2, 0x000f, 1)) ; if (strlen(clientip1)) args.clientip = (char *)clientip1; if (strlen(clientip2)) args.clientip2 = (char *)clientip2; if (strlen(verifiedip)) args.verifiedip = (char *)verifiedip; /* * This is must be present in PROPOSALs, but will probably not * exist in CANCELs and ACCEPTs. * * Service Data blocks are module-specific in format. */ if ((servdatatlv = aim_gettlv(list2, 0x2711 /* 10001 */, 1))) { aim_bstream_init(&sdbs, servdatatlv->value, servdatatlv->length); sdbsptr = &sdbs; } if (args.reqclass & AIM_CAPS_ICQSERVERRELAY) incomingim_ch2_icqserverrelay(sess, mod, rx, snac, userinfo, &args, sdbsptr); else if (args.reqclass & AIM_CAPS_CHAT) incomingim_ch2_chat(sess, mod, rx, snac, userinfo, &args, sdbsptr); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, channel, userinfo, &args); if (args.destructor) ((ch2_args_destructor_t)args.destructor)(sess, &args); g_free((char *)args.msg); g_free((char *)args.encoding); g_free((char *)args.language); aim_freetlvchain(&list2); return ret; } static int incomingim_ch4(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, aim_tlvlist_t *tlvlist, guint8 *cookie) { aim_bstream_t meat; aim_rxcallback_t userfunc; aim_tlv_t *block; struct aim_incomingim_ch4_args args; int ret = 0; /* * Make a bstream for the meaty part. Yum. Meat. */ if (!(block = aim_gettlv(tlvlist, 0x0005, 1))) return -1; aim_bstream_init(&meat, block->value, block->length); args.uin = aimbs_getle32(&meat); args.type = aimbs_getle16(&meat); args.msg = (char *)aimbs_getraw(&meat, aimbs_getle16(&meat)); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, channel, userinfo, &args); g_free(args.msg); return ret; } /* * It can easily be said that parsing ICBMs is THE single * most difficult thing to do in the in AIM protocol. In * fact, I think I just did say that. * * Below is the best damned solution I've come up with * over the past sixteen months of battling with it. This * can parse both away and normal messages from every client * I have access to. Its not fast, its not clean. But it works. * */ static int incomingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int i, ret = 0; guint8 cookie[8]; guint16 channel; aim_userinfo_t userinfo; memset(&userinfo, 0x00, sizeof(aim_userinfo_t)); /* * Read ICBM Cookie. And throw away. */ for (i = 0; i < 8; i++) cookie[i] = aimbs_get8(bs); /* * Channel ID. * * Channel 0x0001 is the message channel. There are * other channels for things called "rendevous" * which represent chat and some of the other new * features of AIM2/3/3.5. * * Channel 0x0002 is the Rendevous channel, which * is where Chat Invitiations and various client-client * connection negotiations come from. * * Channel 0x0004 is used for ICQ authorization, or * possibly any system notice. * */ channel = aimbs_get16(bs); /* * Extract the standard user info block. * * Note that although this contains TLVs that appear contiguous * with the TLVs read below, they are two different pieces. The * userinfo block contains the number of TLVs that contain user * information, the rest are not even though there is no seperation. * aim_extractuserinfo() returns the number of bytes used by the * userinfo tlvs, so you can start reading the rest of them right * afterward. * * That also means that TLV types can be duplicated between the * userinfo block and the rest of the message, however there should * never be two TLVs of the same type in one block. * */ aim_extractuserinfo(sess, bs, &userinfo); /* * From here on, its depends on what channel we're on. * * Technically all channels have a TLV list have this, however, * for the common channel 1 case, in-place parsing is used for * performance reasons (less memory allocation). */ if (channel == 1) { ret = incomingim_ch1(sess, mod, rx, snac, channel, &userinfo, bs, cookie); } else if (channel == 2) { aim_tlvlist_t *tlvlist; /* * Read block of TLVs (not including the userinfo data). All * further data is derived from what is parsed here. */ tlvlist = aim_readtlvchain(bs); ret = incomingim_ch2(sess, mod, rx, snac, channel, &userinfo, tlvlist, cookie); aim_freetlvchain(&tlvlist); } else if (channel == 4) { aim_tlvlist_t *tlvlist; tlvlist = aim_readtlvchain(bs); ret = incomingim_ch4(sess, mod, rx, snac, channel, &userinfo, tlvlist, cookie); aim_freetlvchain(&tlvlist); } else { imcb_error(sess->aux_data, "ICBM received on an unsupported channel. Ignoring."); return 0; } return ret; } /* * aim_reqicbmparaminfo() * * Request ICBM parameter information. * */ int aim_reqicbmparams(aim_session_t *sess) { aim_conn_t *conn; if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) return -EINVAL; return aim_genericreq_n(sess, conn, 0x0004, 0x0004); } /* * * I definitly recommend sending this. If you don't, you'll be stuck * with the rather unreasonable defaults. You don't want those. Send this. * */ int aim_seticbmparam(aim_session_t *sess, struct aim_icbmparameters *params) { aim_conn_t *conn; aim_frame_t *fr; aim_snacid_t snacid; if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) return -EINVAL; if (!params) return -EINVAL; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+16))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0004, 0x0002, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x0004, 0x0002, 0x0000, snacid); /* This is read-only (see Parameter Reply). Must be set to zero here. */ aimbs_put16(&fr->data, 0x0000); /* These are all read-write */ aimbs_put32(&fr->data, params->flags); aimbs_put16(&fr->data, params->maxmsglen); aimbs_put16(&fr->data, params->maxsenderwarn); aimbs_put16(&fr->data, params->maxrecverwarn); aimbs_put32(&fr->data, params->minmsginterval); aim_tx_enqueue(sess, fr); return 0; } static int paraminfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { struct aim_icbmparameters params; aim_rxcallback_t userfunc; params.maxchan = aimbs_get16(bs); params.flags = aimbs_get32(bs); params.maxmsglen = aimbs_get16(bs); params.maxsenderwarn = aimbs_get16(bs); params.maxrecverwarn = aimbs_get16(bs); params.minmsginterval = aimbs_get32(bs); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) return userfunc(sess, rx, ¶ms); return 0; } static int missedcall(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; aim_rxcallback_t userfunc; guint16 channel, nummissed, reason; aim_userinfo_t userinfo; while (aim_bstream_empty(bs)) { channel = aimbs_get16(bs); aim_extractuserinfo(sess, bs, &userinfo); nummissed = aimbs_get16(bs); reason = aimbs_get16(bs); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, channel, &userinfo, nummissed, reason); } return ret; } /* * Receive the response from an ICQ status message request. This contains the * ICQ status message. Go figure. */ static int clientautoresp(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; aim_rxcallback_t userfunc; guint16 channel, reason; char *sn; guint8 *ck, snlen; ck = aimbs_getraw(bs, 8); channel = aimbs_get16(bs); snlen = aimbs_get8(bs); sn = aimbs_getstr(bs, snlen); reason = aimbs_get16(bs); switch (reason) { case 0x0003: { /* ICQ status message. Maybe other stuff too, you never know with these people. */ guint8 statusmsgtype, *msg; guint16 len; guint32 state; len = aimbs_getle16(bs); /* Should be 0x001b */ aim_bstream_advance(bs, len); /* Unknown */ len = aimbs_getle16(bs); /* Should be 0x000e */ aim_bstream_advance(bs, len); /* Unknown */ statusmsgtype = aimbs_getle8(bs); switch (statusmsgtype) { case 0xe8: state = AIM_ICQ_STATE_AWAY; break; case 0xe9: state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_BUSY; break; case 0xea: state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_OUT; break; case 0xeb: state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_DND | AIM_ICQ_STATE_BUSY; break; case 0xec: state = AIM_ICQ_STATE_CHAT; break; default: state = 0; break; } aimbs_getle8(bs); /* Unknown - 0x03 Maybe this means this is an auto-reply */ aimbs_getle16(bs); /* Unknown - 0x0000 */ aimbs_getle16(bs); /* Unknown - 0x0000 */ len = aimbs_getle16(bs); msg = aimbs_getraw(bs, len); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, channel, sn, reason, state, msg); g_free(msg); } break; default: { if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, channel, sn, reason); } break; } /* end switch */ g_free(ck); g_free(sn); return ret; } static int msgack(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_rxcallback_t userfunc; guint16 type; guint8 snlen, *ck; char *sn; int ret = 0; ck = aimbs_getraw(bs, 8); type = aimbs_get16(bs); snlen = aimbs_get8(bs); sn = aimbs_getstr(bs, snlen); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, type, sn); g_free(sn); g_free(ck); return ret; } /* * Subtype 0x0014 - Send a mini typing notification (mtn) packet. * * This is supported by winaim5 and newer, MacAIM bleh and newer, iChat bleh and newer, * and Gaim 0.60 and newer. * */ int aim_im_sendmtn(aim_session_t *sess, guint16 type1, const char *sn, guint16 type2) { aim_conn_t *conn; aim_frame_t *fr; aim_snacid_t snacid; if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0002))) return -EINVAL; if (!sn) return -EINVAL; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+11+strlen(sn)+2))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0004, 0x0014, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x0004, 0x0014, 0x0000, snacid); /* * 8 days of light * Er, that is to say, 8 bytes of 0's */ aimbs_put16(&fr->data, 0x0000); aimbs_put16(&fr->data, 0x0000); aimbs_put16(&fr->data, 0x0000); aimbs_put16(&fr->data, 0x0000); /* * Type 1 (should be 0x0001 for mtn) */ aimbs_put16(&fr->data, type1); /* * Dest sn */ aimbs_put8(&fr->data, strlen(sn)); aimbs_putraw(&fr->data, (const guint8*)sn, strlen(sn)); /* * Type 2 (should be 0x0000, 0x0001, or 0x0002 for mtn) */ aimbs_put16(&fr->data, type2); aim_tx_enqueue(sess, fr); return 0; } /* * Subtype 0x0014 - Receive a mini typing notification (mtn) packet. * * This is supported by winaim5 and newer, MacAIM bleh and newer, iChat bleh and newer, * and Gaim 0.60 and newer. * */ static int mtn_receive(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; aim_rxcallback_t userfunc; char *sn; guint8 snlen; guint16 type1, type2; aim_bstream_advance(bs, 8); /* Unknown - All 0's */ type1 = aimbs_get16(bs); snlen = aimbs_get8(bs); sn = aimbs_getstr(bs, snlen); type2 = aimbs_get16(bs); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, type1, sn, type2); g_free(sn); return ret; } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if (snac->subtype == 0x0005) return paraminfo(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0006) return outgoingim(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0007) return incomingim(sess, mod, rx, snac, bs); else if (snac->subtype == 0x000a) return missedcall(sess, mod, rx, snac, bs); else if (snac->subtype == 0x000b) return clientautoresp(sess, mod, rx, snac, bs); else if (snac->subtype == 0x000c) return msgack(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0014) return mtn_receive(sess, mod, rx, snac, bs); return 0; } int msg_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = 0x0004; mod->version = 0x0001; mod->toolid = 0x0110; mod->toolversion = 0x0629; mod->flags = 0; strncpy(mod->name, "messaging", sizeof(mod->name)); mod->snachandler = snachandler; return 0; } bitlbee-3.2.1/protocols/oscar/snac.c0000644000175000017500000000613612245474076016747 0ustar wilmerwilmer/* * * Various SNAC-related dodads... * * outstanding_snacs is a list of aim_snac_t structs. A SNAC should be added * whenever a new SNAC is sent and it should remain in the list until the * response for it has been receieved. * * cleansnacs() should be called periodically by the client in order * to facilitate the aging out of unreplied-to SNACs. This can and does * happen, so it should be handled. * */ #include static aim_snacid_t aim_newsnac(aim_session_t *sess, aim_snac_t *newsnac); /* * Called from aim_session_init() to initialize the hash. */ void aim_initsnachash(aim_session_t *sess) { int i; for (i = 0; i < AIM_SNAC_HASH_SIZE; i++) sess->snac_hash[i] = NULL; return; } aim_snacid_t aim_cachesnac(aim_session_t *sess, const guint16 family, const guint16 type, const guint16 flags, const void *data, const int datalen) { aim_snac_t snac; snac.id = sess->snacid_next++; snac.family = family; snac.type = type; snac.flags = flags; if (datalen) { if (!(snac.data = g_malloc(datalen))) return 0; /* er... */ memcpy(snac.data, data, datalen); } else snac.data = NULL; return aim_newsnac(sess, &snac); } /* * Clones the passed snac structure and caches it in the * list/hash. */ static aim_snacid_t aim_newsnac(aim_session_t *sess, aim_snac_t *newsnac) { aim_snac_t *snac; int index; if (!newsnac) return 0; if (!(snac = g_malloc(sizeof(aim_snac_t)))) return 0; memcpy(snac, newsnac, sizeof(aim_snac_t)); snac->issuetime = time(NULL); index = snac->id % AIM_SNAC_HASH_SIZE; snac->next = (aim_snac_t *)sess->snac_hash[index]; sess->snac_hash[index] = (void *)snac; return snac->id; } /* * Finds a snac structure with the passed SNAC ID, * removes it from the list/hash, and returns a pointer to it. * * The returned structure must be freed by the caller. * */ aim_snac_t *aim_remsnac(aim_session_t *sess, aim_snacid_t id) { aim_snac_t *cur, **prev; int index; index = id % AIM_SNAC_HASH_SIZE; for (prev = (aim_snac_t **)&sess->snac_hash[index]; (cur = *prev); ) { if (cur->id == id) { *prev = cur->next; return cur; } else prev = &cur->next; } return cur; } /* * This is for cleaning up old SNACs that either don't get replies or * a reply was never received for. Garabage collection. Plain and simple. * * maxage is the _minimum_ age in seconds to keep SNACs. * */ void aim_cleansnacs(aim_session_t *sess, int maxage) { int i; for (i = 0; i < AIM_SNAC_HASH_SIZE; i++) { aim_snac_t *cur, **prev; time_t curtime; if (!sess->snac_hash[i]) continue; curtime = time(NULL); /* done here in case we waited for the lock */ for (prev = (aim_snac_t **)&sess->snac_hash[i]; (cur = *prev); ) { if ((curtime - cur->issuetime) > maxage) { *prev = cur->next; /* XXX should we have destructors here? */ g_free(cur->data); g_free(cur); } else prev = &cur->next; } } return; } int aim_putsnac(aim_bstream_t *bs, guint16 family, guint16 subtype, guint16 flags, aim_snacid_t snacid) { aimbs_put16(bs, family); aimbs_put16(bs, subtype); aimbs_put16(bs, flags); aimbs_put32(bs, snacid); return 10; } bitlbee-3.2.1/protocols/oscar/search.c0000644000175000017500000000423312245474076017264 0ustar wilmerwilmer /* * aim_search.c * * TODO: Add aim_usersearch_name() * */ #include /* XXX can this be integrated with the rest of the error handling? */ static int error(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; aim_rxcallback_t userfunc; aim_snac_t *snac2; /* XXX the modules interface should have already retrieved this for us */ if (!(snac2 = aim_remsnac(sess, snac->id))) { imcb_error(sess->aux_data, "couldn't get snac"); return 0; } if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, snac2->data /* address */); /* XXX freesnac()? */ if (snac2) g_free(snac2->data); g_free(snac2); return ret; } static int reply(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int j = 0, m, ret = 0; aim_tlvlist_t *tlvlist; char *cur = NULL, *buf = NULL; aim_rxcallback_t userfunc; aim_snac_t *snac2; char *searchaddr = NULL; if ((snac2 = aim_remsnac(sess, snac->id))) searchaddr = (char *)snac2->data; tlvlist = aim_readtlvchain(bs); m = aim_counttlvchain(&tlvlist); /* XXX uhm. */ while ((cur = aim_gettlv_str(tlvlist, 0x0001, j+1)) && j < m) { buf = g_realloc(buf, (j+1) * (MAXSNLEN+1)); strncpy(&buf[j * (MAXSNLEN+1)], cur, MAXSNLEN); g_free(cur); j++; } aim_freetlvchain(&tlvlist); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, searchaddr, j, buf); /* XXX freesnac()? */ if (snac2) g_free(snac2->data); g_free(snac2); g_free(buf); return ret; } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if (snac->subtype == 0x0001) return error(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0003) return reply(sess, mod, rx, snac, bs); return 0; } int search_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = 0x000a; mod->version = 0x0001; mod->toolid = 0x0110; mod->toolversion = 0x0629; mod->flags = 0; strncpy(mod->name, "search", sizeof(mod->name)); mod->snachandler = snachandler; return 0; } bitlbee-3.2.1/protocols/oscar/txqueue.c0000644000175000017500000001675612245474076017534 0ustar wilmerwilmer/* * aim_txqueue.c * * Herein lies all the mangement routines for the transmit (Tx) queue. * */ #include #include "im.h" #ifndef _WIN32 #include #endif /* * Allocate a new tx frame. * * This is more for looks than anything else. * * Right now, that is. If/when we implement a pool of transmit * frames, this will become the request-an-unused-frame part. * * framing = AIM_FRAMETYPE_OFT/FLAP * chan = channel for FLAP, hdrtype for OFT * */ aim_frame_t *aim_tx_new(aim_session_t *sess, aim_conn_t *conn, guint8 framing, guint8 chan, int datalen) { aim_frame_t *fr; if (!conn) { imcb_error(sess->aux_data, "no connection specified"); return NULL; } if (!(fr = (aim_frame_t *)g_new0(aim_frame_t,1))) return NULL; fr->conn = conn; fr->hdrtype = framing; if (fr->hdrtype == AIM_FRAMETYPE_FLAP) { fr->hdr.flap.type = chan; } else imcb_error(sess->aux_data, "unknown framing"); if (datalen > 0) { guint8 *data; if (!(data = (unsigned char *)g_malloc(datalen))) { aim_frame_destroy(fr); return NULL; } aim_bstream_init(&fr->data, data, datalen); } return fr; } /* * aim_tx_enqeue__queuebased() * * The overall purpose here is to enqueue the passed in command struct * into the outgoing (tx) queue. Basically... * 1) Make a scope-irrelevent copy of the struct * 3) Mark as not-sent-yet * 4) Enqueue the struct into the list * 6) Return * * Note that this is only used when doing queue-based transmitting; * that is, when sess->tx_enqueue is set to &aim_tx_enqueue__queuebased. * */ static int aim_tx_enqueue__queuebased(aim_session_t *sess, aim_frame_t *fr) { if (!fr->conn) { imcb_error(sess->aux_data, "Warning: enqueueing packet with no connection"); fr->conn = aim_getconn_type(sess, AIM_CONN_TYPE_BOS); } if (fr->hdrtype == AIM_FRAMETYPE_FLAP) { /* assign seqnum -- XXX should really not assign until hardxmit */ fr->hdr.flap.seqnum = aim_get_next_txseqnum(fr->conn); } fr->handled = 0; /* not sent yet */ /* see overhead note in aim_rxqueue counterpart */ if (!sess->queue_outgoing) sess->queue_outgoing = fr; else { aim_frame_t *cur; for (cur = sess->queue_outgoing; cur->next; cur = cur->next) ; cur->next = fr; } return 0; } /* * aim_tx_enqueue__immediate() * * Parallel to aim_tx_enqueue__queuebased, however, this bypasses * the whole queue mess when you want immediate writes to happen. * * Basically the same as its __queuebased couterpart, however * instead of doing a list append, it just calls aim_tx_sendframe() * right here. * */ static int aim_tx_enqueue__immediate(aim_session_t *sess, aim_frame_t *fr) { if (!fr->conn) { imcb_error(sess->aux_data, "packet has no connection"); aim_frame_destroy(fr); return 0; } if (fr->hdrtype == AIM_FRAMETYPE_FLAP) fr->hdr.flap.seqnum = aim_get_next_txseqnum(fr->conn); fr->handled = 0; /* not sent yet */ aim_tx_sendframe(sess, fr); aim_frame_destroy(fr); return 0; } int aim_tx_setenqueue(aim_session_t *sess, int what, int (*func)(aim_session_t *, aim_frame_t *)) { if (what == AIM_TX_QUEUED) sess->tx_enqueue = &aim_tx_enqueue__queuebased; else if (what == AIM_TX_IMMEDIATE) sess->tx_enqueue = &aim_tx_enqueue__immediate; else if (what == AIM_TX_USER) { if (!func) return -EINVAL; sess->tx_enqueue = func; } else return -EINVAL; /* unknown action */ return 0; } int aim_tx_enqueue(aim_session_t *sess, aim_frame_t *fr) { /* * If we want to send a connection thats inprogress, we have to force * them to use the queue based version. Otherwise, use whatever they * want. */ if (fr && fr->conn && (fr->conn->status & AIM_CONN_STATUS_INPROGRESS)) { return aim_tx_enqueue__queuebased(sess, fr); } return (*sess->tx_enqueue)(sess, fr); } /* * aim_get_next_txseqnum() * * This increments the tx command count, and returns the seqnum * that should be stamped on the next FLAP packet sent. This is * normally called during the final step of packet preparation * before enqueuement (in aim_tx_enqueue()). * */ flap_seqnum_t aim_get_next_txseqnum(aim_conn_t *conn) { flap_seqnum_t ret; ret = ++conn->seqnum; return ret; } static int aim_send(int fd, const void *buf, size_t count) { int left, cur; for (cur = 0, left = count; left; ) { int ret; ret = send(fd, ((unsigned char *)buf)+cur, left, 0); if (ret == -1) return -1; else if (ret == 0) return cur; cur += ret; left -= ret; } return cur; } static int aim_bstream_send(aim_bstream_t *bs, aim_conn_t *conn, size_t count) { int wrote = 0; if (!bs || !conn || (count < 0)) return -EINVAL; if (count > aim_bstream_empty(bs)) count = aim_bstream_empty(bs); /* truncate to remaining space */ if (count) { if (count - wrote) { wrote = wrote + aim_send(conn->fd, bs->data + bs->offset + wrote, count - wrote); } } bs->offset += wrote; return wrote; } static int sendframe_flap(aim_session_t *sess, aim_frame_t *fr) { aim_bstream_t obs; guint8 *obs_raw; int payloadlen, err = 0, obslen; payloadlen = aim_bstream_curpos(&fr->data); if (!(obs_raw = g_malloc(6 + payloadlen))) return -ENOMEM; aim_bstream_init(&obs, obs_raw, 6 + payloadlen); /* FLAP header */ aimbs_put8(&obs, 0x2a); aimbs_put8(&obs, fr->hdr.flap.type); aimbs_put16(&obs, fr->hdr.flap.seqnum); aimbs_put16(&obs, payloadlen); /* payload */ aim_bstream_rewind(&fr->data); aimbs_putbs(&obs, &fr->data, payloadlen); obslen = aim_bstream_curpos(&obs); aim_bstream_rewind(&obs); if (aim_bstream_send(&obs, fr->conn, obslen) != obslen) err = -errno; g_free(obs_raw); /* XXX aim_bstream_free */ fr->handled = 1; fr->conn->lastactivity = time(NULL); return err; } int aim_tx_sendframe(aim_session_t *sess, aim_frame_t *fr) { if (fr->hdrtype == AIM_FRAMETYPE_FLAP) return sendframe_flap(sess, fr); return -1; } int aim_tx_flushqueue(aim_session_t *sess) { aim_frame_t *cur; for (cur = sess->queue_outgoing; cur; cur = cur->next) { if (cur->handled) continue; /* already been sent */ if (cur->conn && (cur->conn->status & AIM_CONN_STATUS_INPROGRESS)) continue; /* * And now for the meager attempt to force transmit * latency and avoid missed messages. */ if ((cur->conn->lastactivity + cur->conn->forcedlatency) >= time(NULL)) { /* * XXX should be a break! we dont want to block the * upper layers * * XXX or better, just do this right. * */ sleep((cur->conn->lastactivity + cur->conn->forcedlatency) - time(NULL)); } /* XXX this should call the custom "queuing" function!! */ aim_tx_sendframe(sess, cur); } /* purge sent commands from queue */ aim_tx_purgequeue(sess); return 0; } /* * aim_tx_purgequeue() * * This is responsable for removing sent commands from the transmit * queue. This is not a required operation, but it of course helps * reduce memory footprint at run time! * */ void aim_tx_purgequeue(aim_session_t *sess) { aim_frame_t *cur, **prev; for (prev = &sess->queue_outgoing; (cur = *prev); ) { if (cur->handled) { *prev = cur->next; aim_frame_destroy(cur); } else prev = &cur->next; } return; } /** * aim_tx_cleanqueue - get rid of packets waiting for tx on a dying conn * @sess: session * @conn: connection that's dying * * for now this simply marks all packets as sent and lets them * disappear without warning. * */ void aim_tx_cleanqueue(aim_session_t *sess, aim_conn_t *conn) { aim_frame_t *cur; for (cur = sess->queue_outgoing; cur; cur = cur->next) { if (cur->conn == conn) cur->handled = 1; } return; } bitlbee-3.2.1/protocols/oscar/conn.c0000644000175000017500000003465112245474076016763 0ustar wilmerwilmer /* * conn.c * * Does all this gloriously nifty connection handling stuff... * */ #include #include "sock.h" static int aim_logoff(aim_session_t *sess); /* * In OSCAR, every connection has a set of SNAC groups associated * with it. These are the groups that you can send over this connection * without being guarenteed a "Not supported" SNAC error. * * The grand theory of things says that these associations transcend * what libfaim calls "connection types" (conn->type). You can probably * see the elegance here, but since I want to revel in it for a bit, you * get to hear it all spelled out. * * So let us say that you have your core BOS connection running. One * of your modules has just given you a SNAC of the group 0x0004 to send * you. Maybe an IM destined for some twit in Greenland. So you start * at the top of your connection list, looking for a connection that * claims to support group 0x0004. You find one. Why, that neat BOS * connection of yours can do that. So you send it on its way. * * Now, say, that fellow from Greenland has friends and they all want to * meet up with you in a lame chat room. This has landed you a SNAC * in the family 0x000e and you have to admit you're a bit lost. You've * searched your connection list for someone who wants to make your life * easy and deliver this SNAC for you, but there isn't one there. * * Here comes the good bit. Without even letting anyone know, particularly * the module that decided to send this SNAC, and definitly not that twit * in Greenland, you send out a service request. In this request, you have * marked the need for a connection supporting group 0x000e. A few seconds * later, you receive a service redirect with an IP address and a cookie in * it. Great, you say. Now I have something to do. Off you go, making * that connection. One of the first things you get from this new server * is a message saying that indeed it does support the group you were looking * for. So you continue and send rate confirmation and all that. * * Then you remember you had that SNAC to send, and now you have a means to * do it, and you do, and everyone is happy. Except the Greenlander, who is * still stuck in the bitter cold. * * Oh, and this is useful for building the Migration SNACs, too. In the * future, this may help convince me to implement rate limit mitigation * for real. We'll see. * * Just to make me look better, I'll say that I've known about this great * scheme for quite some time now. But I still haven't convinced myself * to make libfaim work that way. It would take a fair amount of effort, * and probably some client API changes as well. (Whenever I don't want * to do something, I just say it would change the client API. Then I * instantly have a couple of supporters of not doing it.) * * Generally, addgroup is only called by the internal handling of the * server ready SNAC. So if you want to do something before that, you'll * have to be more creative. That is done rather early, though, so I don't * think you have to worry about it. Unless you're me. I care deeply * about such inane things. * */ void aim_conn_addgroup(aim_conn_t *conn, guint16 group) { aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; struct snacgroup *sg; if (!(sg = g_malloc(sizeof(struct snacgroup)))) return; sg->group = group; sg->next = ins->groups; ins->groups = sg; return; } aim_conn_t *aim_conn_findbygroup(aim_session_t *sess, guint16 group) { aim_conn_t *cur; for (cur = sess->connlist; cur; cur = cur->next) { aim_conn_inside_t *ins = (aim_conn_inside_t *)cur->inside; struct snacgroup *sg; for (sg = ins->groups; sg; sg = sg->next) { if (sg->group == group) return cur; } } return NULL; } static void connkill_snacgroups(struct snacgroup **head) { struct snacgroup *sg; for (sg = *head; sg; ) { struct snacgroup *tmp; tmp = sg->next; g_free(sg); sg = tmp; } *head = NULL; return; } static void connkill_rates(struct rateclass **head) { struct rateclass *rc; for (rc = *head; rc; ) { struct rateclass *tmp; struct snacpair *sp; tmp = rc->next; for (sp = rc->members; sp; ) { struct snacpair *tmpsp; tmpsp = sp->next; g_free(sp); sp = tmpsp; } g_free(rc); rc = tmp; } *head = NULL; return; } static void connkill_real(aim_session_t *sess, aim_conn_t **deadconn) { aim_rxqueue_cleanbyconn(sess, *deadconn); aim_tx_cleanqueue(sess, *deadconn); if ((*deadconn)->fd != -1) aim_conn_close(*deadconn); /* * XXX ->priv should never be touched by the library. I know * it used to be, but I'm getting rid of all that. Use * ->internal instead. */ if ((*deadconn)->priv) g_free((*deadconn)->priv); /* * This will free ->internal if it necessary... */ if ((*deadconn)->type == AIM_CONN_TYPE_CHAT) aim_conn_kill_chat(sess, *deadconn); if ((*deadconn)->inside) { aim_conn_inside_t *inside = (aim_conn_inside_t *)(*deadconn)->inside; connkill_snacgroups(&inside->groups); connkill_rates(&inside->rates); g_free(inside); } g_free(*deadconn); *deadconn = NULL; return; } /** * aim_connrst - Clears out connection list, killing remaining connections. * @sess: Session to be cleared * * Clears out the connection list and kills any connections left. * */ static void aim_connrst(aim_session_t *sess) { if (sess->connlist) { aim_conn_t *cur = sess->connlist, *tmp; while (cur) { tmp = cur->next; aim_conn_close(cur); connkill_real(sess, &cur); cur = tmp; } } sess->connlist = NULL; return; } /** * aim_conn_init - Reset a connection to default values. * @deadconn: Connection to be reset * * Initializes and/or resets a connection structure. * */ static void aim_conn_init(aim_conn_t *deadconn) { if (!deadconn) return; deadconn->fd = -1; deadconn->subtype = -1; deadconn->type = -1; deadconn->seqnum = 0; deadconn->lastactivity = 0; deadconn->forcedlatency = 0; deadconn->handlerlist = NULL; deadconn->priv = NULL; memset(deadconn->inside, 0, sizeof(aim_conn_inside_t)); return; } /** * aim_conn_getnext - Gets a new connection structure. * @sess: Session * * Allocate a new empty connection structure. * */ static aim_conn_t *aim_conn_getnext(aim_session_t *sess) { aim_conn_t *newconn; if (!(newconn = g_new0(aim_conn_t,1))) return NULL; if (!(newconn->inside = g_new0(aim_conn_inside_t,1))) { g_free(newconn); return NULL; } aim_conn_init(newconn); newconn->next = sess->connlist; sess->connlist = newconn; return newconn; } /** * aim_conn_kill - Close and free a connection. * @sess: Session for the connection * @deadconn: Connection to be freed * * Close, clear, and free a connection structure. Should never be * called from within libfaim. * */ void aim_conn_kill(aim_session_t *sess, aim_conn_t **deadconn) { aim_conn_t *cur, **prev; if (!deadconn || !*deadconn) return; for (prev = &sess->connlist; (cur = *prev); ) { if (cur == *deadconn) { *prev = cur->next; break; } prev = &cur->next; } if (!cur) return; /* oops */ connkill_real(sess, &cur); return; } /** * aim_conn_close - Close a connection * @deadconn: Connection to close * * Close (but not free) a connection. * * This leaves everything untouched except for clearing the * handler list and setting the fd to -1 (used to recognize * dead connections). It will also remove cookies if necessary. * */ void aim_conn_close(aim_conn_t *deadconn) { if (deadconn->fd >= 3) closesocket(deadconn->fd); deadconn->fd = -1; if (deadconn->handlerlist) aim_clearhandlers(deadconn); return; } /** * aim_getconn_type - Find a connection of a specific type * @sess: Session to search * @type: Type of connection to look for * * Searches for a connection of the specified type in the * specified session. Returns the first connection of that * type found. * * XXX except for RENDEZVOUS, all uses of this should be removed and * use aim_conn_findbygroup() instead. */ aim_conn_t *aim_getconn_type(aim_session_t *sess, int type) { aim_conn_t *cur; for (cur = sess->connlist; cur; cur = cur->next) { if ((cur->type == type) && !(cur->status & AIM_CONN_STATUS_INPROGRESS)) break; } return cur; } aim_conn_t *aim_getconn_type_all(aim_session_t *sess, int type) { aim_conn_t *cur; for (cur = sess->connlist; cur; cur = cur->next) { if (cur->type == type) break; } return cur; } /** * aim_newconn - Open a new connection * @sess: Session to create connection in * @type: Type of connection to create * @dest: Host to connect to (in "host:port" syntax) * * Opens a new connection to the specified dest host of specified * type, using the proxy settings if available. If @host is %NULL, * the connection is allocated and returned, but no connection * is made. * * FIXME: Return errors in a more sane way. * */ aim_conn_t *aim_newconn(aim_session_t *sess, int type, const char *dest) { aim_conn_t *connstruct; guint16 port = AIM_LOGIN_PORT; char *host; int i; if (!(connstruct = aim_conn_getnext(sess))) return NULL; connstruct->sessv = (void *)sess; connstruct->type = type; if (!dest) { /* just allocate a struct */ connstruct->fd = -1; connstruct->status = 0; return connstruct; } /* * As of 23 Jul 1999, AOL now sends the port number, preceded by a * colon, in the BOS redirect. This fatally breaks all previous * libfaims. Bad, bad AOL. * * We put this here to catch every case. * */ for(i = 0; i < (int)strlen(dest); i++) { if (dest[i] == ':') { port = atoi(&(dest[i+1])); break; } } host = (char *)g_malloc(i+1); strncpy(host, dest, i); host[i] = '\0'; connstruct->fd = proxy_connect(host, port, NULL, NULL); g_free(host); return connstruct; } /** * aim_conn_setlatency - Set a forced latency value for connection * @conn: Conn to set latency for * @newval: Number of seconds to force between transmits * * Causes @newval seconds to be spent between transmits on a connection. * * This is my lame attempt at overcoming not understanding the rate * limiting. * * XXX: This should really be replaced with something that scales and * backs off like the real rate limiting does. * */ int aim_conn_setlatency(aim_conn_t *conn, int newval) { if (!conn) return -1; conn->forcedlatency = newval; conn->lastactivity = 0; /* reset this just to make sure */ return 0; } /** * aim_session_init - Initializes a session structure * @sess: Session to initialize * @flags: Flags to use. Any of %AIM_SESS_FLAGS %OR'd together. * @debuglevel: Level of debugging output (zero is least) * * Sets up the initial values for a session. * */ void aim_session_init(aim_session_t *sess, guint32 flags, int debuglevel) { if (!sess) return; memset(sess, 0, sizeof(aim_session_t)); aim_connrst(sess); sess->queue_outgoing = NULL; sess->queue_incoming = NULL; aim_initsnachash(sess); sess->msgcookies = NULL; sess->snacid_next = 0x00000001; sess->flags = 0; sess->modlistv = NULL; sess->ssi.received_data = 0; sess->ssi.waiting_for_ack = 0; sess->ssi.holding_queue = NULL; sess->ssi.revision = 0; sess->ssi.items = NULL; sess->ssi.timestamp = (time_t)0; sess->locate.userinfo = NULL; sess->locate.torequest = NULL; sess->locate.requested = NULL; sess->locate.waiting_for_response = FALSE; sess->icq_info = NULL; sess->authinfo = NULL; sess->emailinfo = NULL; sess->oft_info = NULL; /* * Default to SNAC login unless XORLOGIN is explicitly set. */ if (!(flags & AIM_SESS_FLAGS_XORLOGIN)) sess->flags |= AIM_SESS_FLAGS_SNACLOGIN; sess->flags |= flags; /* * This must always be set. Default to the queue-based * version for back-compatibility. */ aim_tx_setenqueue(sess, AIM_TX_QUEUED, NULL); /* * Register all the modules for this session... */ aim__registermodule(sess, misc_modfirst); /* load the catch-all first */ aim__registermodule(sess, general_modfirst); aim__registermodule(sess, locate_modfirst); aim__registermodule(sess, buddylist_modfirst); aim__registermodule(sess, msg_modfirst); aim__registermodule(sess, admin_modfirst); aim__registermodule(sess, bos_modfirst); aim__registermodule(sess, search_modfirst); aim__registermodule(sess, stats_modfirst); aim__registermodule(sess, chatnav_modfirst); aim__registermodule(sess, chat_modfirst); /* missing 0x0f - 0x12 */ aim__registermodule(sess, ssi_modfirst); /* missing 0x14 */ aim__registermodule(sess, icq_modfirst); /* missing 0x16 */ aim__registermodule(sess, auth_modfirst); return; } /** * aim_session_kill - Deallocate a session * @sess: Session to kill * */ void aim_session_kill(aim_session_t *sess) { aim_cleansnacs(sess, -1); aim_logoff(sess); aim__shutdownmodules(sess); return; } /* * XXX this is nearly as ugly as proxyconnect(). */ int aim_conn_completeconnect(aim_session_t *sess, aim_conn_t *conn) { fd_set fds, wfds; struct timeval tv; int res, error = ETIMEDOUT; aim_rxcallback_t userfunc; if (!conn || (conn->fd == -1)) return -1; if (!(conn->status & AIM_CONN_STATUS_INPROGRESS)) return -1; FD_ZERO(&fds); FD_SET(conn->fd, &fds); FD_ZERO(&wfds); FD_SET(conn->fd, &wfds); tv.tv_sec = 0; tv.tv_usec = 0; if ((res = select(conn->fd+1, &fds, &wfds, NULL, &tv)) == -1) { error = errno; aim_conn_close(conn); errno = error; return -1; } else if (res == 0) { return 0; /* hasn't really completed yet... */ } if (FD_ISSET(conn->fd, &fds) || FD_ISSET(conn->fd, &wfds)) { unsigned int len = sizeof(error); if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) error = errno; } if (error) { aim_conn_close(conn); errno = error; return -1; } sock_make_blocking(conn->fd); conn->status &= ~AIM_CONN_STATUS_INPROGRESS; if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNCOMPLETE))) userfunc(sess, NULL, conn); /* Flush out the queues if there was something waiting for this conn */ aim_tx_flushqueue(sess); return 0; } aim_session_t *aim_conn_getsess(aim_conn_t *conn) { if (!conn) return NULL; return (aim_session_t *)conn->sessv; } /* * aim_logoff() * * Closes -ALL- open connections. * */ static int aim_logoff(aim_session_t *sess) { aim_connrst(sess); /* in case we want to connect again */ return 0; } /* * aim_flap_nop() * * No-op. WinAIM 4.x sends these _every minute_ to keep * the connection alive. */ int aim_flap_nop(aim_session_t *sess, aim_conn_t *conn) { aim_frame_t *fr; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x05, 0))) return -ENOMEM; aim_tx_enqueue(sess, fr); return 0; } bitlbee-3.2.1/protocols/oscar/rxhandlers.c0000644000175000017500000001651112245474076020173 0ustar wilmerwilmer/* * aim_rxhandlers.c * * This file contains most all of the incoming packet handlers, along * with aim_rxdispatch(), the Rx dispatcher. Queue/list management is * actually done in aim_rxqueue.c. * */ #include struct aim_rxcblist_s { guint16 family; guint16 type; aim_rxcallback_t handler; u_short flags; struct aim_rxcblist_s *next; }; aim_module_t *aim__findmodulebygroup(aim_session_t *sess, guint16 group) { aim_module_t *cur; for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) { if (cur->family == group) return cur; } return NULL; } static aim_module_t *aim__findmodule(aim_session_t *sess, const char *name) { aim_module_t *cur; for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) { if (strcmp(name, cur->name) == 0) return cur; } return NULL; } int aim__registermodule(aim_session_t *sess, int (*modfirst)(aim_session_t *, aim_module_t *)) { aim_module_t *mod; if (!sess || !modfirst) return -1; if (!(mod = g_new0(aim_module_t,1))) return -1; if (modfirst(sess, mod) == -1) { g_free(mod); return -1; } if (aim__findmodule(sess, mod->name)) { if (mod->shutdown) mod->shutdown(sess, mod); g_free(mod); return -1; } mod->next = (aim_module_t *)sess->modlistv; sess->modlistv = mod; return 0; } void aim__shutdownmodules(aim_session_t *sess) { aim_module_t *cur; for (cur = (aim_module_t *)sess->modlistv; cur; ) { aim_module_t *tmp; tmp = cur->next; if (cur->shutdown) cur->shutdown(sess, cur); g_free(cur); cur = tmp; } sess->modlistv = NULL; return; } static int consumesnac(aim_session_t *sess, aim_frame_t *rx) { aim_module_t *cur; aim_modsnac_t snac; if (aim_bstream_empty(&rx->data) < 10) return 0; snac.family = aimbs_get16(&rx->data); snac.subtype = aimbs_get16(&rx->data); snac.flags = aimbs_get16(&rx->data); snac.id = aimbs_get32(&rx->data); /* Contains TLV(s) in the FNAC header */ if(snac.flags & 0x8000) { aim_bstream_advance(&rx->data, aimbs_get16(&rx->data)); } else if(snac.flags & 0x0001) { /* Following SNAC will be related */ } for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) { if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) && (cur->family != snac.family)) continue; if (cur->snachandler(sess, cur, rx, &snac, &rx->data)) return 1; } return 0; } static int consumenonsnac(aim_session_t *sess, aim_frame_t *rx, guint16 family, guint16 subtype) { aim_module_t *cur; aim_modsnac_t snac; snac.family = family; snac.subtype = subtype; snac.flags = snac.id = 0; for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) { if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) && (cur->family != snac.family)) continue; if (cur->snachandler(sess, cur, rx, &snac, &rx->data)) return 1; } return 0; } static int negchan_middle(aim_session_t *sess, aim_frame_t *fr) { aim_tlvlist_t *tlvlist; char *msg = NULL; guint16 code = 0; aim_rxcallback_t userfunc; int ret = 1; if (aim_bstream_empty(&fr->data) == 0) { /* XXX should do something with this */ return 1; } /* Used only by the older login protocol */ /* XXX remove this special case? */ if (fr->conn->type == AIM_CONN_TYPE_AUTH) return consumenonsnac(sess, fr, 0x0017, 0x0003); tlvlist = aim_readtlvchain(&fr->data); if (aim_gettlv(tlvlist, 0x0009, 1)) code = aim_gettlv16(tlvlist, 0x0009, 1); if (aim_gettlv(tlvlist, 0x000b, 1)) msg = aim_gettlv_str(tlvlist, 0x000b, 1); if ((userfunc = aim_callhandler(sess, fr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNERR))) ret = userfunc(sess, fr, code, msg); aim_freetlvchain(&tlvlist); g_free(msg); return ret; } /* * Some SNACs we do not allow to be hooked, for good reason. */ static int checkdisallowed(guint16 group, guint16 type) { static const struct { guint16 group; guint16 type; } dontuse[] = { {0x0001, 0x0002}, {0x0001, 0x0003}, {0x0001, 0x0006}, {0x0001, 0x0007}, {0x0001, 0x0008}, {0x0001, 0x0017}, {0x0001, 0x0018}, {0x0000, 0x0000} }; int i; for (i = 0; dontuse[i].group != 0x0000; i++) { if ((dontuse[i].group == group) && (dontuse[i].type == type)) return 1; } return 0; } int aim_conn_addhandler(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 type, aim_rxcallback_t newhandler, guint16 flags) { struct aim_rxcblist_s *newcb; if (!conn) return -1; if (checkdisallowed(family, type)) { g_assert(0); return -1; } if (!(newcb = (struct aim_rxcblist_s *)g_new0(struct aim_rxcblist_s, 1))) return -1; newcb->family = family; newcb->type = type; newcb->flags = flags; newcb->handler = newhandler; newcb->next = NULL; if (!conn->handlerlist) conn->handlerlist = (void *)newcb; else { struct aim_rxcblist_s *cur; for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur->next; cur = cur->next) ; cur->next = newcb; } return 0; } int aim_clearhandlers(aim_conn_t *conn) { struct aim_rxcblist_s *cur; if (!conn) return -1; for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur; ) { struct aim_rxcblist_s *tmp; tmp = cur->next; g_free(cur); cur = tmp; } conn->handlerlist = NULL; return 0; } aim_rxcallback_t aim_callhandler(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 type) { struct aim_rxcblist_s *cur; if (!conn) return NULL; for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur; cur = cur->next) { if ((cur->family == family) && (cur->type == type)) return cur->handler; } if (type == AIM_CB_SPECIAL_DEFAULT) { return NULL; /* prevent infinite recursion */ } return aim_callhandler(sess, conn, family, AIM_CB_SPECIAL_DEFAULT); } static int aim_callhandler_noparam(aim_session_t *sess, aim_conn_t *conn,guint16 family, guint16 type, aim_frame_t *ptr) { aim_rxcallback_t userfunc; if ((userfunc = aim_callhandler(sess, conn, family, type))) return userfunc(sess, ptr); return 1; /* XXX */ } /* * aim_rxdispatch() * * Basically, heres what this should do: * 1) Determine correct packet handler for this packet * 2) Mark the packet handled (so it can be dequeued in purge_queue()) * 3) Send the packet to the packet handler * 4) Go to next packet in the queue and start over * 5) When done, run purge_queue() to purge handled commands * * TODO: Clean up. * TODO: More support for mid-level handlers. * TODO: Allow for NULL handlers. * */ void aim_rxdispatch(aim_session_t *sess) { int i; aim_frame_t *cur; for (cur = sess->queue_incoming, i = 0; cur; cur = cur->next, i++) { /* * XXX: This is still fairly ugly. */ if (cur->handled) continue; if (cur->hdr.flap.type == 0x01) { cur->handled = aim_callhandler_noparam(sess, cur->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_FLAPVER, cur); /* XXX use consumenonsnac */ continue; } else if (cur->hdr.flap.type == 0x02) { if ((cur->handled = consumesnac(sess, cur))) continue; } else if (cur->hdr.flap.type == 0x04) { cur->handled = negchan_middle(sess, cur); continue; } else if (cur->hdr.flap.type == 0x05) ; if (!cur->handled) { consumenonsnac(sess, cur, 0xffff, 0xffff); /* last chance! */ cur->handled = 1; } } /* * This doesn't have to be called here. It could easily be done * by a seperate thread or something. It's an administrative operation, * and can take a while. Though the less you call it the less memory * you'll have :) */ aim_purge_rxqueue(sess); return; } bitlbee-3.2.1/protocols/oscar/bos.h0000644000175000017500000000044212245474076016605 0ustar wilmerwilmer#ifndef __OSCAR_BOS_H__ #define __OSCAR_BOS_H__ #define AIM_CB_FAM_BOS 0x0009 /* * SNAC Family: Misc BOS Services. */ #define AIM_CB_BOS_ERROR 0x0001 #define AIM_CB_BOS_RIGHTSQUERY 0x0002 #define AIM_CB_BOS_RIGHTS 0x0003 #define AIM_CB_BOS_DEFAULT 0xffff #endif /* __OSCAR_BOS_H__ */ bitlbee-3.2.1/protocols/oscar/chatnav.c0000644000175000017500000002503212245474076017443 0ustar wilmerwilmer/* * Handle ChatNav. * * [The ChatNav(igation) service does various things to keep chat * alive. It provides room information, room searching and creating, * as well as giving users the right ("permission") to use chat.] * */ #include #include "chatnav.h" /* * conn must be a chatnav connection! */ int aim_chatnav_reqrights(aim_session_t *sess, aim_conn_t *conn) { return aim_genericreq_n_snacid(sess, conn, 0x000d, 0x0002); } int aim_chatnav_createroom(aim_session_t *sess, aim_conn_t *conn, const char *name, guint16 exchange) { static const char ck[] = {"create"}; static const char lang[] = {"en"}; static const char charset[] = {"us-ascii"}; aim_frame_t *fr; aim_snacid_t snacid; aim_tlvlist_t *tl = NULL; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x000d, 0x0008, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x000d, 0x0008, 0x0000, snacid); /* exchange */ aimbs_put16(&fr->data, exchange); /* * This looks to be a big hack. You'll note that this entire * SNAC is just a room info structure, but the hard room name, * here, is set to "create". * * Either this goes on the "list of questions concerning * why-the-hell-did-you-do-that", or this value is completly * ignored. Without experimental evidence, but a good knowledge of * AOL style, I'm going to guess that it is the latter, and that * the value of the room name in create requests is ignored. */ aimbs_put8(&fr->data, strlen(ck)); aimbs_putraw(&fr->data, (guint8 *)ck, strlen(ck)); /* * instance * * Setting this to 0xffff apparently assigns the last instance. * */ aimbs_put16(&fr->data, 0xffff); /* detail level */ aimbs_put8(&fr->data, 0x01); aim_addtlvtochain_raw(&tl, 0x00d3, strlen(name), (guint8 *)name); aim_addtlvtochain_raw(&tl, 0x00d6, strlen(charset), (guint8 *)charset); aim_addtlvtochain_raw(&tl, 0x00d7, strlen(lang), (guint8 *)lang); /* tlvcount */ aimbs_put16(&fr->data, aim_counttlvchain(&tl)); aim_writetlvchain(&fr->data, &tl); aim_freetlvchain(&tl); aim_tx_enqueue(sess, fr); return 0; } static int parseinfo_perms(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs, aim_snac_t *snac2) { aim_rxcallback_t userfunc; int ret = 0; struct aim_chat_exchangeinfo *exchanges = NULL; int curexchange; aim_tlv_t *exchangetlv; guint8 maxrooms = 0; aim_tlvlist_t *tlvlist, *innerlist; tlvlist = aim_readtlvchain(bs); /* * Type 0x0002: Maximum concurrent rooms. */ if (aim_gettlv(tlvlist, 0x0002, 1)) maxrooms = aim_gettlv8(tlvlist, 0x0002, 1); /* * Type 0x0003: Exchange information * * There can be any number of these, each one * representing another exchange. * */ for (curexchange = 0; ((exchangetlv = aim_gettlv(tlvlist, 0x0003, curexchange+1))); ) { aim_bstream_t tbs; aim_bstream_init(&tbs, exchangetlv->value, exchangetlv->length); curexchange++; exchanges = g_realloc(exchanges, curexchange * sizeof(struct aim_chat_exchangeinfo)); /* exchange number */ exchanges[curexchange-1].number = aimbs_get16(&tbs); innerlist = aim_readtlvchain(&tbs); /* * Type 0x000a: Unknown. * * Usually three bytes: 0x0114 (exchange 1) or 0x010f (others). * */ if (aim_gettlv(innerlist, 0x000a, 1)) ; /* * Type 0x000d: Unknown. */ if (aim_gettlv(innerlist, 0x000d, 1)) ; /* * Type 0x0004: Unknown */ if (aim_gettlv(innerlist, 0x0004, 1)) ; /* * Type 0x0002: Unknown */ if (aim_gettlv(innerlist, 0x0002, 1)) ; /* * Type 0x00c9: Flags * * 1 Evilable * 2 Nav Only * 4 Instancing Allowed * 8 Occupant Peek Allowed * */ if (aim_gettlv(innerlist, 0x00c9, 1)) exchanges[curexchange-1].flags = aim_gettlv16(innerlist, 0x00c9, 1); /* * Type 0x00ca: Creation Date */ if (aim_gettlv(innerlist, 0x00ca, 1)) ; /* * Type 0x00d0: Mandatory Channels? */ if (aim_gettlv(innerlist, 0x00d0, 1)) ; /* * Type 0x00d1: Maximum Message length */ if (aim_gettlv(innerlist, 0x00d1, 1)) ; /* * Type 0x00d2: Maximum Occupancy? */ if (aim_gettlv(innerlist, 0x00d2, 1)) ; /* * Type 0x00d3: Exchange Description */ if (aim_gettlv(innerlist, 0x00d3, 1)) exchanges[curexchange-1].name = aim_gettlv_str(innerlist, 0x00d3, 1); else exchanges[curexchange-1].name = NULL; /* * Type 0x00d4: Exchange Description URL */ if (aim_gettlv(innerlist, 0x00d4, 1)) ; /* * Type 0x00d5: Creation Permissions * * 0 Creation not allowed * 1 Room creation allowed * 2 Exchange creation allowed * */ if (aim_gettlv(innerlist, 0x00d5, 1)) { aim_gettlv8(innerlist, 0x00d5, 1); /* createperms */ } /* * Type 0x00d6: Character Set (First Time) */ if (aim_gettlv(innerlist, 0x00d6, 1)) exchanges[curexchange-1].charset1 = aim_gettlv_str(innerlist, 0x00d6, 1); else exchanges[curexchange-1].charset1 = NULL; /* * Type 0x00d7: Language (First Time) */ if (aim_gettlv(innerlist, 0x00d7, 1)) exchanges[curexchange-1].lang1 = aim_gettlv_str(innerlist, 0x00d7, 1); else exchanges[curexchange-1].lang1 = NULL; /* * Type 0x00d8: Character Set (Second Time) */ if (aim_gettlv(innerlist, 0x00d8, 1)) exchanges[curexchange-1].charset2 = aim_gettlv_str(innerlist, 0x00d8, 1); else exchanges[curexchange-1].charset2 = NULL; /* * Type 0x00d9: Language (Second Time) */ if (aim_gettlv(innerlist, 0x00d9, 1)) exchanges[curexchange-1].lang2 = aim_gettlv_str(innerlist, 0x00d9, 1); else exchanges[curexchange-1].lang2 = NULL; /* * Type 0x00da: Unknown */ if (aim_gettlv(innerlist, 0x00da, 1)) ; aim_freetlvchain(&innerlist); } /* * Call client. */ if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, snac2->type, maxrooms, curexchange, exchanges); for (curexchange--; curexchange >= 0; curexchange--) { g_free(exchanges[curexchange].name); g_free(exchanges[curexchange].charset1); g_free(exchanges[curexchange].lang1); g_free(exchanges[curexchange].charset2); g_free(exchanges[curexchange].lang2); } g_free(exchanges); aim_freetlvchain(&tlvlist); return ret; } static int parseinfo_create(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs, aim_snac_t *snac2) { aim_rxcallback_t userfunc; aim_tlvlist_t *tlvlist, *innerlist; char *ck = NULL, *fqcn = NULL, *name = NULL; guint16 exchange = 0, instance = 0, unknown = 0, flags = 0, maxmsglen = 0, maxoccupancy = 0; guint32 createtime = 0; guint8 createperms = 0, detaillevel; int cklen; aim_tlv_t *bigblock; int ret = 0; aim_bstream_t bbbs; tlvlist = aim_readtlvchain(bs); if (!(bigblock = aim_gettlv(tlvlist, 0x0004, 1))) { imcb_error(sess->aux_data, "no bigblock in top tlv in create room response"); aim_freetlvchain(&tlvlist); return 0; } aim_bstream_init(&bbbs, bigblock->value, bigblock->length); exchange = aimbs_get16(&bbbs); cklen = aimbs_get8(&bbbs); ck = aimbs_getstr(&bbbs, cklen); instance = aimbs_get16(&bbbs); detaillevel = aimbs_get8(&bbbs); if (detaillevel != 0x02) { imcb_error(sess->aux_data, "unknown detaillevel in create room response"); aim_freetlvchain(&tlvlist); g_free(ck); return 0; } unknown = aimbs_get16(&bbbs); innerlist = aim_readtlvchain(&bbbs); if (aim_gettlv(innerlist, 0x006a, 1)) fqcn = aim_gettlv_str(innerlist, 0x006a, 1); if (aim_gettlv(innerlist, 0x00c9, 1)) flags = aim_gettlv16(innerlist, 0x00c9, 1); if (aim_gettlv(innerlist, 0x00ca, 1)) createtime = aim_gettlv32(innerlist, 0x00ca, 1); if (aim_gettlv(innerlist, 0x00d1, 1)) maxmsglen = aim_gettlv16(innerlist, 0x00d1, 1); if (aim_gettlv(innerlist, 0x00d2, 1)) maxoccupancy = aim_gettlv16(innerlist, 0x00d2, 1); if (aim_gettlv(innerlist, 0x00d3, 1)) name = aim_gettlv_str(innerlist, 0x00d3, 1); if (aim_gettlv(innerlist, 0x00d5, 1)) createperms = aim_gettlv8(innerlist, 0x00d5, 1); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) { ret = userfunc(sess, rx, snac2->type, fqcn, instance, exchange, flags, createtime, maxmsglen, maxoccupancy, createperms, unknown, name, ck); } g_free(ck); g_free(name); g_free(fqcn); aim_freetlvchain(&innerlist); aim_freetlvchain(&tlvlist); return ret; } /* * Since multiple things can trigger this callback, we must lookup the * snacid to determine the original snac subtype that was called. * * XXX This isn't really how this works. But this is: Every d/9 response * has a 16bit value at the beginning. That matches to: * Short Desc = 1 * Full Desc = 2 * Instance Info = 4 * Nav Short Desc = 8 * Nav Instance Info = 16 * And then everything is really asynchronous. There is no specific * attachment of a response to a create room request, for example. Creating * the room yields no different a response than requesting the room's info. * */ static int parseinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_snac_t *snac2; int ret = 0; if (!(snac2 = aim_remsnac(sess, snac->id))) { imcb_error(sess->aux_data, "received response to unknown request!"); return 0; } if (snac2->family != 0x000d) { imcb_error(sess->aux_data, "received response that maps to corrupt request!"); return 0; } /* * We now know what the original SNAC subtype was. */ if (snac2->type == 0x0002) /* request chat rights */ ret = parseinfo_perms(sess, mod, rx, snac, bs, snac2); else if (snac2->type == 0x0003) {} /* request exchange info */ else if (snac2->type == 0x0004) {} /* request room info */ else if (snac2->type == 0x0005) {} /* request more room info */ else if (snac2->type == 0x0006) {} /* request occupant list */ else if (snac2->type == 0x0007) {} /* search for a room */ else if (snac2->type == 0x0008) /* create room */ ret = parseinfo_create(sess, mod, rx, snac, bs, snac2); else imcb_error(sess->aux_data, "unknown request subtype"); if (snac2) g_free(snac2->data); g_free(snac2); return ret; } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if (snac->subtype == 0x0009) return parseinfo(sess, mod, rx, snac, bs); return 0; } int chatnav_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = 0x000d; mod->version = 0x0003; mod->toolid = 0x0010; mod->toolversion = 0x0629; mod->flags = 0; strncpy(mod->name, "chatnav", sizeof(mod->name)); mod->snachandler = snachandler; return 0; } bitlbee-3.2.1/protocols/oscar/rxqueue.c0000644000175000017500000002234112245474076017515 0ustar wilmerwilmer/* * aim_rxqueue.c * * This file contains the management routines for the receive * (incoming packet) queue. The actual packet handlers are in * aim_rxhandlers.c. */ #include #ifndef _WIN32 #include #endif /* * */ int aim_recv(int fd, void *buf, size_t count) { int left, cur; for (cur = 0, left = count; left; ) { int ret; ret = recv(fd, ((unsigned char *)buf)+cur, left, 0); /* Of course EOF is an error, only morons disagree with that. */ if (ret <= 0) return -1; cur += ret; left -= ret; } return cur; } /* * Read into a byte stream. Will not read more than count, but may read * less if there is not enough room in the stream buffer. */ static int aim_bstream_recv(aim_bstream_t *bs, int fd, size_t count) { int red = 0; if (!bs || (fd < 0) || (count < 0)) return -1; if (count > (bs->len - bs->offset)) count = bs->len - bs->offset; /* truncate to remaining space */ if (count) { red = aim_recv(fd, bs->data + bs->offset, count); if (red <= 0) return -1; } bs->offset += red; return red; } int aim_bstream_init(aim_bstream_t *bs, guint8 *data, int len) { if (!bs) return -1; bs->data = data; bs->len = len; bs->offset = 0; return 0; } int aim_bstream_empty(aim_bstream_t *bs) { return bs->len - bs->offset; } int aim_bstream_curpos(aim_bstream_t *bs) { return bs->offset; } int aim_bstream_setpos(aim_bstream_t *bs, int off) { if (off > bs->len) return -1; bs->offset = off; return off; } void aim_bstream_rewind(aim_bstream_t *bs) { aim_bstream_setpos(bs, 0); return; } int aim_bstream_advance(aim_bstream_t *bs, int n) { if (aim_bstream_empty(bs) < n) return 0; /* XXX throw an exception */ bs->offset += n; return n; } guint8 aimbs_get8(aim_bstream_t *bs) { if (aim_bstream_empty(bs) < 1) return 0; /* XXX throw an exception */ bs->offset++; return aimutil_get8(bs->data + bs->offset - 1); } guint16 aimbs_get16(aim_bstream_t *bs) { if (aim_bstream_empty(bs) < 2) return 0; /* XXX throw an exception */ bs->offset += 2; return aimutil_get16(bs->data + bs->offset - 2); } guint32 aimbs_get32(aim_bstream_t *bs) { if (aim_bstream_empty(bs) < 4) return 0; /* XXX throw an exception */ bs->offset += 4; return aimutil_get32(bs->data + bs->offset - 4); } guint8 aimbs_getle8(aim_bstream_t *bs) { if (aim_bstream_empty(bs) < 1) return 0; /* XXX throw an exception */ bs->offset++; return aimutil_getle8(bs->data + bs->offset - 1); } guint16 aimbs_getle16(aim_bstream_t *bs) { if (aim_bstream_empty(bs) < 2) return 0; /* XXX throw an exception */ bs->offset += 2; return aimutil_getle16(bs->data + bs->offset - 2); } guint32 aimbs_getle32(aim_bstream_t *bs) { if (aim_bstream_empty(bs) < 4) return 0; /* XXX throw an exception */ bs->offset += 4; return aimutil_getle32(bs->data + bs->offset - 4); } int aimbs_put8(aim_bstream_t *bs, guint8 v) { if (aim_bstream_empty(bs) < 1) return 0; /* XXX throw an exception */ bs->offset += aimutil_put8(bs->data + bs->offset, v); return 1; } int aimbs_put16(aim_bstream_t *bs, guint16 v) { if (aim_bstream_empty(bs) < 2) return 0; /* XXX throw an exception */ bs->offset += aimutil_put16(bs->data + bs->offset, v); return 2; } int aimbs_put32(aim_bstream_t *bs, guint32 v) { if (aim_bstream_empty(bs) < 4) return 0; /* XXX throw an exception */ bs->offset += aimutil_put32(bs->data + bs->offset, v); return 1; } int aimbs_putle8(aim_bstream_t *bs, guint8 v) { if (aim_bstream_empty(bs) < 1) return 0; /* XXX throw an exception */ bs->offset += aimutil_putle8(bs->data + bs->offset, v); return 1; } int aimbs_putle16(aim_bstream_t *bs, guint16 v) { if (aim_bstream_empty(bs) < 2) return 0; /* XXX throw an exception */ bs->offset += aimutil_putle16(bs->data + bs->offset, v); return 2; } int aimbs_putle32(aim_bstream_t *bs, guint32 v) { if (aim_bstream_empty(bs) < 4) return 0; /* XXX throw an exception */ bs->offset += aimutil_putle32(bs->data + bs->offset, v); return 1; } int aimbs_getrawbuf(aim_bstream_t *bs, guint8 *buf, int len) { if (aim_bstream_empty(bs) < len) return 0; memcpy(buf, bs->data + bs->offset, len); bs->offset += len; return len; } guint8 *aimbs_getraw(aim_bstream_t *bs, int len) { guint8 *ob; if (!(ob = g_malloc(len))) return NULL; if (aimbs_getrawbuf(bs, ob, len) < len) { g_free(ob); return NULL; } return ob; } char *aimbs_getstr(aim_bstream_t *bs, int len) { guint8 *ob; if (!(ob = g_malloc(len+1))) return NULL; if (aimbs_getrawbuf(bs, ob, len) < len) { g_free(ob); return NULL; } ob[len] = '\0'; return (char *)ob; } int aimbs_putraw(aim_bstream_t *bs, const guint8 *v, int len) { if (aim_bstream_empty(bs) < len) return 0; /* XXX throw an exception */ memcpy(bs->data + bs->offset, v, len); bs->offset += len; return len; } int aimbs_putbs(aim_bstream_t *bs, aim_bstream_t *srcbs, int len) { if (aim_bstream_empty(srcbs) < len) return 0; /* XXX throw exception (underrun) */ if (aim_bstream_empty(bs) < len) return 0; /* XXX throw exception (overflow) */ memcpy(bs->data + bs->offset, srcbs->data + srcbs->offset, len); bs->offset += len; srcbs->offset += len; return len; } /** * aim_frame_destroy - free aim_frame_t * @frame: the frame to free * * returns -1 on error; 0 on success. * */ void aim_frame_destroy(aim_frame_t *frame) { g_free(frame->data.data); /* XXX aim_bstream_free */ g_free(frame); } /* * Grab a single command sequence off the socket, and enqueue * it in the incoming event queue in a seperate struct. */ int aim_get_command(aim_session_t *sess, aim_conn_t *conn) { guint8 flaphdr_raw[6]; aim_bstream_t flaphdr; aim_frame_t *newrx; guint16 payloadlen; if (!sess || !conn) return 0; if (conn->fd == -1) return -1; /* its a aim_conn_close()'d connection */ /* KIDS, THIS IS WHAT HAPPENS IF YOU USE CODE WRITTEN FOR GUIS IN A DAEMON! And wouldn't it make sense to return something that prevents this function from being called again IMMEDIATELY (and making the program suck up all CPU time)?... if (conn->fd < 3) return 0; */ if (conn->status & AIM_CONN_STATUS_INPROGRESS) return aim_conn_completeconnect(sess, conn); aim_bstream_init(&flaphdr, flaphdr_raw, sizeof(flaphdr_raw)); /* * Read FLAP header. Six bytes: * * 0 char -- Always 0x2a * 1 char -- Channel ID. Usually 2 -- 1 and 4 are used during login. * 2 short -- Sequence number * 4 short -- Number of data bytes that follow. */ if (aim_bstream_recv(&flaphdr, conn->fd, 6) < 6) { aim_conn_close(conn); return -1; } aim_bstream_rewind(&flaphdr); /* * This shouldn't happen unless the socket breaks, the server breaks, * or we break. We must handle it just in case. */ if (aimbs_get8(&flaphdr) != 0x2a) { aim_bstream_rewind(&flaphdr); aimbs_get8(&flaphdr); imcb_error(sess->aux_data, "FLAP framing disrupted"); aim_conn_close(conn); return -1; } /* allocate a new struct */ if (!(newrx = (aim_frame_t *)g_new0(aim_frame_t,1))) return -1; /* we're doing FLAP if we're here */ newrx->hdrtype = AIM_FRAMETYPE_FLAP; newrx->hdr.flap.type = aimbs_get8(&flaphdr); newrx->hdr.flap.seqnum = aimbs_get16(&flaphdr); payloadlen = aimbs_get16(&flaphdr); newrx->nofree = 0; /* free by default */ if (payloadlen) { guint8 *payload = NULL; if (!(payload = (guint8 *) g_malloc(payloadlen))) { aim_frame_destroy(newrx); return -1; } aim_bstream_init(&newrx->data, payload, payloadlen); /* read the payload */ if (aim_bstream_recv(&newrx->data, conn->fd, payloadlen) < payloadlen) { aim_frame_destroy(newrx); /* free's payload */ aim_conn_close(conn); return -1; } } else aim_bstream_init(&newrx->data, NULL, 0); aim_bstream_rewind(&newrx->data); newrx->conn = conn; newrx->next = NULL; /* this will always be at the bottom */ if (!sess->queue_incoming) sess->queue_incoming = newrx; else { aim_frame_t *cur; for (cur = sess->queue_incoming; cur->next; cur = cur->next) ; cur->next = newrx; } newrx->conn->lastactivity = time(NULL); return 0; } /* * Purge recieve queue of all handled commands (->handled==1). Also * allows for selective freeing using ->nofree so that the client can * keep the data for various purposes. * * If ->nofree is nonzero, the frame will be delinked from the global list, * but will not be free'ed. The client _must_ keep a pointer to the * data -- libfaim will not! If the client marks ->nofree but * does not keep a pointer, it's lost forever. * */ void aim_purge_rxqueue(aim_session_t *sess) { aim_frame_t *cur, **prev; for (prev = &sess->queue_incoming; (cur = *prev); ) { if (cur->handled) { *prev = cur->next; if (!cur->nofree) aim_frame_destroy(cur); } else prev = &cur->next; } return; } /* * Since aim_get_command will aim_conn_kill dead connections, we need * to clean up the rxqueue of unprocessed connections on that socket. * * XXX: this is something that was handled better in the old connection * handling method, but eh. */ void aim_rxqueue_cleanbyconn(aim_session_t *sess, aim_conn_t *conn) { aim_frame_t *currx; for (currx = sess->queue_incoming; currx; currx = currx->next) { if ((!currx->handled) && (currx->conn == conn)) currx->handled = 1; } return; } bitlbee-3.2.1/protocols/oscar/misc.c0000644000175000017500000001252612245474076016756 0ustar wilmerwilmer /* * aim_misc.c * * TODO: Seperate a lot of this into an aim_bos.c. * * Other things... * * - Idle setting * * */ #include /* * aim_bos_setprofile(profile) * * Gives BOS your profile. * */ int aim_bos_setprofile(aim_session_t *sess, aim_conn_t *conn, const char *profile, const char *awaymsg, guint32 caps) { static const char defencoding[] = {"text/aolrtf; charset=\"utf-8\""}; aim_frame_t *fr; aim_tlvlist_t *tl = NULL; aim_snacid_t snacid; /* Build to packet first to get real length */ if (profile) { aim_addtlvtochain_raw(&tl, 0x0001, strlen(defencoding), (guint8 *)defencoding); aim_addtlvtochain_raw(&tl, 0x0002, strlen(profile), (guint8 *)profile); } /* * So here's how this works: * - You are away when you have a non-zero-length type 4 TLV stored. * - You become unaway when you clear the TLV with a zero-length * type 4 TLV. * - If you do not send the type 4 TLV, your status does not change * (that is, if you were away, you'll remain away). */ if (awaymsg) { if (strlen(awaymsg)) { aim_addtlvtochain_raw(&tl, 0x0003, strlen(defencoding), (guint8 *)defencoding); aim_addtlvtochain_raw(&tl, 0x0004, strlen(awaymsg), (guint8 *)awaymsg); } else aim_addtlvtochain_noval(&tl, 0x0004); } aim_addtlvtochain_caps(&tl, 0x0005, caps); if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + aim_sizetlvchain(&tl)))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0002, 0x0004, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x0002, 0x004, 0x0000, snacid); aim_writetlvchain(&fr->data, &tl); aim_freetlvchain(&tl); aim_tx_enqueue(sess, fr); return 0; } /* * aim_bos_reqbuddyrights() * * Request Buddy List rights. * */ int aim_bos_reqbuddyrights(aim_session_t *sess, aim_conn_t *conn) { return aim_genericreq_n(sess, conn, 0x0003, 0x0002); } /* * Generic routine for sending commands. * * * I know I can do this in a smarter way...but I'm not thinking straight * right now... * * I had one big function that handled all three cases, but then it broke * and I split it up into three. But then I fixed it. I just never went * back to the single. I don't see any advantage to doing it either way. * */ int aim_genericreq_n(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype) { aim_frame_t *fr; aim_snacid_t snacid = 0x00000000; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10))) return -ENOMEM; aim_putsnac(&fr->data, family, subtype, 0x0000, snacid); aim_tx_enqueue(sess, fr); return 0; } int aim_genericreq_n_snacid(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype) { aim_frame_t *fr; aim_snacid_t snacid; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10))) return -ENOMEM; snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0); aim_putsnac(&fr->data, family, subtype, 0x0000, snacid); aim_tx_enqueue(sess, fr); return 0; } int aim_genericreq_l(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype, guint32 *longdata) { aim_frame_t *fr; aim_snacid_t snacid; if (!longdata) return aim_genericreq_n(sess, conn, family, subtype); if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4))) return -ENOMEM; snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0); aim_putsnac(&fr->data, family, subtype, 0x0000, snacid); aimbs_put32(&fr->data, *longdata); aim_tx_enqueue(sess, fr); return 0; } int aim_genericreq_s(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype, guint16 *shortdata) { aim_frame_t *fr; aim_snacid_t snacid; if (!shortdata) return aim_genericreq_n(sess, conn, family, subtype); if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2))) return -ENOMEM; snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0); aim_putsnac(&fr->data, family, subtype, 0x0000, snacid); aimbs_put16(&fr->data, *shortdata); aim_tx_enqueue(sess, fr); return 0; } /* * aim_bos_reqlocaterights() * * Request Location services rights. * */ int aim_bos_reqlocaterights(aim_session_t *sess, aim_conn_t *conn) { return aim_genericreq_n(sess, conn, 0x0002, 0x0002); } /* * Should be generic enough to handle the errors for all groups. * */ static int generror(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; int error = 0; aim_rxcallback_t userfunc; aim_snac_t *snac2; snac2 = aim_remsnac(sess, snac->id); if (aim_bstream_empty(bs)) error = aimbs_get16(bs); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, error, snac2 ? snac2->data : NULL); if (snac2) g_free(snac2->data); g_free(snac2); return ret; } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if (snac->subtype == 0x0001) return generror(sess, mod, rx, snac, bs); else if ((snac->family == 0xffff) && (snac->subtype == 0xffff)) { aim_rxcallback_t userfunc; if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) return userfunc(sess, rx); } return 0; } int misc_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = 0xffff; mod->version = 0x0000; mod->flags = AIM_MODFLAG_MULTIFAMILY; strncpy(mod->name, "misc", sizeof(mod->name)); mod->snachandler = snachandler; return 0; } bitlbee-3.2.1/protocols/oscar/bos.c0000644000175000017500000000360412245474076016603 0ustar wilmerwilmer#include #include "bos.h" /* Request BOS rights (group 9, type 2) */ int aim_bos_reqrights(aim_session_t *sess, aim_conn_t *conn) { return aim_genericreq_n(sess, conn, 0x0009, 0x0002); } /* BOS Rights (group 9, type 3) */ static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_rxcallback_t userfunc; aim_tlvlist_t *tlvlist; guint16 maxpermits = 0, maxdenies = 0; int ret = 0; /* * TLVs follow */ tlvlist = aim_readtlvchain(bs); /* * TLV type 0x0001: Maximum number of buddies on permit list. */ if (aim_gettlv(tlvlist, 0x0001, 1)) maxpermits = aim_gettlv16(tlvlist, 0x0001, 1); /* * TLV type 0x0002: Maximum number of buddies on deny list. */ if (aim_gettlv(tlvlist, 0x0002, 1)) maxdenies = aim_gettlv16(tlvlist, 0x0002, 1); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, maxpermits, maxdenies); aim_freetlvchain(&tlvlist); return ret; } /* * Set group permisson mask (group 9, type 4) * * Normally 0x1f (all classes). * * The group permission mask allows you to keep users of a certain * class or classes from talking to you. The mask should be * a bitwise OR of all the user classes you want to see you. * */ int aim_bos_setgroupperm(aim_session_t *sess, aim_conn_t *conn, guint32 mask) { return aim_genericreq_l(sess, conn, 0x0009, 0x0004, &mask); } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if (snac->subtype == 0x0003) return rights(sess, mod, rx, snac, bs); return 0; } int bos_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = 0x0009; mod->version = 0x0001; mod->toolid = 0x0110; mod->toolversion = 0x0629; mod->flags = 0; strncpy(mod->name, "bos", sizeof(mod->name)); mod->snachandler = snachandler; return 0; } bitlbee-3.2.1/protocols/oscar/chat.c0000644000175000017500000003365412245474076016747 0ustar wilmerwilmer/* * aim_chat.c * * Routines for the Chat service. * */ #include #include #include "info.h" /* Stored in the ->priv of chat connections */ struct chatconnpriv { guint16 exchange; char *name; guint16 instance; }; void aim_conn_kill_chat(aim_session_t *sess, aim_conn_t *conn) { struct chatconnpriv *ccp = (struct chatconnpriv *)conn->priv; if (ccp) g_free(ccp->name); g_free(ccp); return; } /* * Send a Chat Message. * * Possible flags: * AIM_CHATFLAGS_NOREFLECT -- Unset the flag that requests messages * should be sent to their sender. * AIM_CHATFLAGS_AWAY -- Mark the message as an autoresponse * (Note that WinAIM does not honor this, * and displays the message as normal.) * * XXX convert this to use tlvchains */ int aim_chat_send_im(aim_session_t *sess, aim_conn_t *conn, guint16 flags, const char *msg, int msglen) { int i; aim_frame_t *fr; aim_msgcookie_t *cookie; aim_snacid_t snacid; guint8 ckstr[8]; aim_tlvlist_t *otl = NULL, *itl = NULL; if (!sess || !conn || !msg || (msglen <= 0)) return 0; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x000e, 0x0005, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x000e, 0x0005, 0x0000, snacid); /* * Generate a random message cookie. * * XXX mkcookie should generate the cookie and cache it in one * operation to preserve uniqueness. * */ for (i = 0; i < sizeof(ckstr); i++) aimutil_put8(ckstr+i, (guint8) rand()); cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_CHAT, NULL); cookie->data = NULL; /* XXX store something useful here */ aim_cachecookie(sess, cookie); for (i = 0; i < sizeof(ckstr); i++) aimbs_put8(&fr->data, ckstr[i]); /* * Channel ID. */ aimbs_put16(&fr->data, 0x0003); /* * Type 1: Flag meaning this message is destined to the room. */ aim_addtlvtochain_noval(&otl, 0x0001); /* * Type 6: Reflect */ if (!(flags & AIM_CHATFLAGS_NOREFLECT)) aim_addtlvtochain_noval(&otl, 0x0006); /* * Type 7: Autoresponse */ if (flags & AIM_CHATFLAGS_AWAY) aim_addtlvtochain_noval(&otl, 0x0007); /* [WvG] This wasn't there originally, but we really should send the right charset flags, as we also do with normal messages. Hope this will work. :-) */ /* if (flags & AIM_CHATFLAGS_UNICODE) aimbs_put16(&fr->data, 0x0002); else if (flags & AIM_CHATFLAGS_ISO_8859_1) aimbs_put16(&fr->data, 0x0003); else aimbs_put16(&fr->data, 0x0000); aimbs_put16(&fr->data, 0x0000); */ /* * SubTLV: Type 1: Message */ aim_addtlvtochain_raw(&itl, 0x0001, strlen(msg), (guint8 *)msg); /* * Type 5: Message block. Contains more TLVs. * * This could include other information... We just * put in a message TLV however. * */ aim_addtlvtochain_frozentlvlist(&otl, 0x0005, &itl); aim_writetlvchain(&fr->data, &otl); aim_freetlvchain(&itl); aim_freetlvchain(&otl); aim_tx_enqueue(sess, fr); return 0; } /* * Join a room of name roomname. This is the first step to joining an * already created room. It's basically a Service Request for * family 0x000e, with a little added on to specify the exchange and room * name. */ int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance) { aim_frame_t *fr; aim_snacid_t snacid; aim_tlvlist_t *tl = NULL; struct chatsnacinfo csi; if (!sess || !conn || !roomname || !strlen(roomname)) return -EINVAL; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512))) return -ENOMEM; memset(&csi, 0, sizeof(csi)); csi.exchange = exchange; strncpy(csi.name, roomname, sizeof(csi.name)); csi.instance = instance; snacid = aim_cachesnac(sess, 0x0001, 0x0004, 0x0000, &csi, sizeof(csi)); aim_putsnac(&fr->data, 0x0001, 0x0004, 0x0000, snacid); /* * Requesting service chat (0x000e) */ aimbs_put16(&fr->data, 0x000e); aim_addtlvtochain_chatroom(&tl, 0x0001, exchange, roomname, instance); aim_writetlvchain(&fr->data, &tl); aim_freetlvchain(&tl); aim_tx_enqueue(sess, fr); return 0; } int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo) { int namelen; if (!bs || !outinfo) return 0; outinfo->exchange = aimbs_get16(bs); namelen = aimbs_get8(bs); outinfo->name = aimbs_getstr(bs, namelen); outinfo->instance = aimbs_get16(bs); return 0; } /* * conn must be a BOS connection! */ int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, guint16 exchange, const char *roomname, guint16 instance) { int i; aim_frame_t *fr; aim_msgcookie_t *cookie; struct aim_invite_priv *priv; guint8 ckstr[8]; aim_snacid_t snacid; aim_tlvlist_t *otl = NULL, *itl = NULL; guint8 *hdr; int hdrlen; aim_bstream_t hdrbs; if (!sess || !conn || !sn || !msg || !roomname) return -EINVAL; if (conn->type != AIM_CONN_TYPE_BOS) return -EINVAL; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152+strlen(sn)+strlen(roomname)+strlen(msg)))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, sn, strlen(sn)+1); aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); /* * Cookie */ for (i = 0; i < sizeof(ckstr); i++) aimutil_put8(ckstr, (guint8) rand()); /* XXX should be uncached by an unwritten 'invite accept' handler */ if ((priv = g_malloc(sizeof(struct aim_invite_priv)))) { priv->sn = g_strdup(sn); priv->roomname = g_strdup(roomname); priv->exchange = exchange; priv->instance = instance; } if ((cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_INVITE, priv))) aim_cachecookie(sess, cookie); else g_free(priv); for (i = 0; i < sizeof(ckstr); i++) aimbs_put8(&fr->data, ckstr[i]); /* * Channel (2) */ aimbs_put16(&fr->data, 0x0002); /* * Dest sn */ aimbs_put8(&fr->data, strlen(sn)); aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); /* * TLV t(0005) * * Everything else is inside this TLV. * * Sigh. AOL was rather inconsistent right here. So we have * to play some minor tricks. Right inside the type 5 is some * raw data, followed by a series of TLVs. * */ hdrlen = 2+8+16+6+4+4+strlen(msg)+4+2+1+strlen(roomname)+2; hdr = g_malloc(hdrlen); aim_bstream_init(&hdrbs, hdr, hdrlen); aimbs_put16(&hdrbs, 0x0000); /* Unknown! */ aimbs_putraw(&hdrbs, ckstr, sizeof(ckstr)); /* I think... */ aim_putcap(&hdrbs, AIM_CAPS_CHAT); aim_addtlvtochain16(&itl, 0x000a, 0x0001); aim_addtlvtochain_noval(&itl, 0x000f); aim_addtlvtochain_raw(&itl, 0x000c, strlen(msg), (guint8 *)msg); aim_addtlvtochain_chatroom(&itl, 0x2711, exchange, roomname, instance); aim_writetlvchain(&hdrbs, &itl); aim_addtlvtochain_raw(&otl, 0x0005, aim_bstream_curpos(&hdrbs), hdr); aim_writetlvchain(&fr->data, &otl); g_free(hdr); aim_freetlvchain(&itl); aim_freetlvchain(&otl); aim_tx_enqueue(sess, fr); return 0; } /* * General room information. Lots of stuff. * * Values I know are in here but I havent attached * them to any of the 'Unknown's: * - Language (English) * * SNAC 000e/0002 */ static int infoupdate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_userinfo_t *userinfo = NULL; aim_rxcallback_t userfunc; int ret = 0; int usercount = 0; guint8 detaillevel = 0; char *roomname = NULL; struct aim_chat_roominfo roominfo; aim_tlvlist_t *tlvlist; char *roomdesc = NULL; guint16 flags = 0; guint32 creationtime = 0; guint16 maxmsglen = 0, maxvisiblemsglen = 0; guint16 unknown_d2 = 0, unknown_d5 = 0; aim_chat_readroominfo(bs, &roominfo); detaillevel = aimbs_get8(bs); if (detaillevel != 0x02) { imcb_error(sess->aux_data, "Only detaillevel 0x2 is support at the moment"); return 1; } aimbs_get16(bs); /* tlv count */ /* * Everything else are TLVs. */ tlvlist = aim_readtlvchain(bs); /* * TLV type 0x006a is the room name in Human Readable Form. */ if (aim_gettlv(tlvlist, 0x006a, 1)) roomname = aim_gettlv_str(tlvlist, 0x006a, 1); /* * Type 0x006f: Number of occupants. */ if (aim_gettlv(tlvlist, 0x006f, 1)) usercount = aim_gettlv16(tlvlist, 0x006f, 1); /* * Type 0x0073: Occupant list. */ if (aim_gettlv(tlvlist, 0x0073, 1)) { int curoccupant = 0; aim_tlv_t *tmptlv; aim_bstream_t occbs; tmptlv = aim_gettlv(tlvlist, 0x0073, 1); /* Allocate enough userinfo structs for all occupants */ userinfo = g_new0(aim_userinfo_t, usercount); aim_bstream_init(&occbs, tmptlv->value, tmptlv->length); while (curoccupant < usercount) aim_extractuserinfo(sess, &occbs, &userinfo[curoccupant++]); } /* * Type 0x00c9: Flags. (AIM_CHATROOM_FLAG) */ if (aim_gettlv(tlvlist, 0x00c9, 1)) flags = aim_gettlv16(tlvlist, 0x00c9, 1); /* * Type 0x00ca: Creation time (4 bytes) */ if (aim_gettlv(tlvlist, 0x00ca, 1)) creationtime = aim_gettlv32(tlvlist, 0x00ca, 1); /* * Type 0x00d1: Maximum Message Length */ if (aim_gettlv(tlvlist, 0x00d1, 1)) maxmsglen = aim_gettlv16(tlvlist, 0x00d1, 1); /* * Type 0x00d2: Unknown. (2 bytes) */ if (aim_gettlv(tlvlist, 0x00d2, 1)) unknown_d2 = aim_gettlv16(tlvlist, 0x00d2, 1); /* * Type 0x00d3: Room Description */ if (aim_gettlv(tlvlist, 0x00d3, 1)) roomdesc = aim_gettlv_str(tlvlist, 0x00d3, 1); /* * Type 0x000d4: Unknown (flag only) */ if (aim_gettlv(tlvlist, 0x000d4, 1)) ; /* * Type 0x00d5: Unknown. (1 byte) */ if (aim_gettlv(tlvlist, 0x00d5, 1)) unknown_d5 = aim_gettlv8(tlvlist, 0x00d5, 1); /* * Type 0x00d6: Encoding 1 ("us-ascii") */ if (aim_gettlv(tlvlist, 0x000d6, 1)) ; /* * Type 0x00d7: Language 1 ("en") */ if (aim_gettlv(tlvlist, 0x000d7, 1)) ; /* * Type 0x00d8: Encoding 2 ("us-ascii") */ if (aim_gettlv(tlvlist, 0x000d8, 1)) ; /* * Type 0x00d9: Language 2 ("en") */ if (aim_gettlv(tlvlist, 0x000d9, 1)) ; /* * Type 0x00da: Maximum visible message length */ if (aim_gettlv(tlvlist, 0x000da, 1)) maxvisiblemsglen = aim_gettlv16(tlvlist, 0x00da, 1); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) { ret = userfunc(sess, rx, &roominfo, roomname, usercount, userinfo, roomdesc, flags, creationtime, maxmsglen, unknown_d2, unknown_d5, maxvisiblemsglen); } g_free(roominfo.name); g_free(userinfo); g_free(roomname); g_free(roomdesc); aim_freetlvchain(&tlvlist); return ret; } static int userlistchange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_userinfo_t *userinfo = NULL; aim_rxcallback_t userfunc; int curcount = 0, ret = 0; while (aim_bstream_empty(bs)) { curcount++; userinfo = g_realloc(userinfo, curcount * sizeof(aim_userinfo_t)); aim_extractuserinfo(sess, bs, &userinfo[curcount-1]); } if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, curcount, userinfo); g_free(userinfo); return ret; } /* * We could probably include this in the normal ICBM parsing * code as channel 0x0003, however, since only the start * would be the same, we might as well do it here. * * General outline of this SNAC: * snac * cookie * channel id * tlvlist * unknown * source user info * name * evility * userinfo tlvs * online time * etc * message metatlv * message tlv * message string * possibly others * */ static int incomingmsg(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_userinfo_t userinfo; aim_rxcallback_t userfunc; int ret = 0; guint8 *cookie; guint16 channel; aim_tlvlist_t *otl; char *msg = NULL; aim_msgcookie_t *ck; memset(&userinfo, 0, sizeof(aim_userinfo_t)); /* * ICBM Cookie. Uncache it. */ cookie = aimbs_getraw(bs, 8); if ((ck = aim_uncachecookie(sess, cookie, AIM_COOKIETYPE_CHAT))) { g_free(ck->data); g_free(ck); } /* * Channel ID * * Channels 1 and 2 are implemented in the normal ICBM * parser. * * We only do channel 3 here. * */ channel = aimbs_get16(bs); if (channel != 0x0003) { imcb_error(sess->aux_data, "unknown channel!"); return 0; } /* * Start parsing TLVs right away. */ otl = aim_readtlvchain(bs); /* * Type 0x0003: Source User Information */ if (aim_gettlv(otl, 0x0003, 1)) { aim_tlv_t *userinfotlv; aim_bstream_t tbs; userinfotlv = aim_gettlv(otl, 0x0003, 1); aim_bstream_init(&tbs, userinfotlv->value, userinfotlv->length); aim_extractuserinfo(sess, &tbs, &userinfo); } /* * Type 0x0001: If present, it means it was a message to the * room (as opposed to a whisper). */ if (aim_gettlv(otl, 0x0001, 1)) ; /* * Type 0x0005: Message Block. Conains more TLVs. */ if (aim_gettlv(otl, 0x0005, 1)) { aim_tlvlist_t *itl; aim_tlv_t *msgblock; aim_bstream_t tbs; msgblock = aim_gettlv(otl, 0x0005, 1); aim_bstream_init(&tbs, msgblock->value, msgblock->length); itl = aim_readtlvchain(&tbs); /* * Type 0x0001: Message. */ if (aim_gettlv(itl, 0x0001, 1)) msg = aim_gettlv_str(itl, 0x0001, 1); aim_freetlvchain(&itl); } if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, &userinfo, msg); g_free(cookie); g_free(msg); aim_freetlvchain(&otl); return ret; } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if (snac->subtype == 0x0002) return infoupdate(sess, mod, rx, snac, bs); else if ((snac->subtype == 0x0003) || (snac->subtype == 0x0004)) return userlistchange(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0006) return incomingmsg(sess, mod, rx, snac, bs); return 0; } int chat_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = 0x000e; mod->version = 0x0001; mod->toolid = 0x0010; mod->toolversion = 0x0629; mod->flags = 0; strncpy(mod->name, "chat", sizeof(mod->name)); mod->snachandler = snachandler; return 0; } bitlbee-3.2.1/protocols/oscar/auth.c0000644000175000017500000003567412245474076016775 0ustar wilmerwilmer/* * Deals with the authorizer (group 0x0017=23, and old-style non-SNAC login). * */ #include #include "md5.h" static int aim_encode_password(const char *password, unsigned char *encoded); /* * This just pushes the passed cookie onto the passed connection, without * the SNAC header or any of that. * * Very commonly used, as every connection except auth will require this to * be the first thing you send. * */ int aim_sendcookie(aim_session_t *sess, aim_conn_t *conn, const guint8 *chipsahoy) { aim_frame_t *fr; aim_tlvlist_t *tl = NULL; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x0001, 4+2+2+AIM_COOKIELEN))) return -ENOMEM; aimbs_put32(&fr->data, 0x00000001); aim_addtlvtochain_raw(&tl, 0x0006, AIM_COOKIELEN, chipsahoy); aim_writetlvchain(&fr->data, &tl); aim_freetlvchain(&tl); aim_tx_enqueue(sess, fr); return 0; } /* * Normally the FLAP version is sent as the first few bytes of the cookie, * meaning you generally never call this. * * But there are times when something might want it seperate. Specifically, * libfaim sends this internally when doing SNAC login. * */ int aim_sendflapver(aim_session_t *sess, aim_conn_t *conn) { aim_frame_t *fr; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x01, 4))) return -ENOMEM; aimbs_put32(&fr->data, 0x00000001); aim_tx_enqueue(sess, fr); return 0; } /* * This is a bit confusing. * * Normal SNAC login goes like this: * - connect * - server sends flap version * - client sends flap version * - client sends screen name (17/6) * - server sends hash key (17/7) * - client sends auth request (17/2 -- aim_send_login) * - server yells * * XOR login (for ICQ) goes like this: * - connect * - server sends flap version * - client sends auth request which contains flap version (aim_send_login) * - server yells * * For the client API, we make them implement the most complicated version, * and for the simpler version, we fake it and make it look like the more * complicated process. * * This is done by giving the client a faked key, just so we can convince * them to call aim_send_login right away, which will detect the session * flag that says this is XOR login and ignore the key, sending an ICQ * login request instead of the normal SNAC one. * * As soon as AOL makes ICQ log in the same way as AIM, this is /gone/. * * XXX This may cause problems if the client relies on callbacks only * being called from the context of aim_rxdispatch()... * */ static int goddamnicq(aim_session_t *sess, aim_conn_t *conn, const char *sn) { aim_frame_t fr; aim_rxcallback_t userfunc; sess->flags &= ~AIM_SESS_FLAGS_SNACLOGIN; sess->flags |= AIM_SESS_FLAGS_XORLOGIN; fr.conn = conn; if ((userfunc = aim_callhandler(sess, conn, 0x0017, 0x0007))) userfunc(sess, &fr, ""); return 0; } /* * In AIM 3.5 protocol, the first stage of login is to request login from the * Authorizer, passing it the screen name for verification. If the name is * invalid, a 0017/0003 is spit back, with the standard error contents. If * valid, a 0017/0007 comes back, which is the signal to send it the main * login command (0017/0002). * */ int aim_request_login(aim_session_t *sess, aim_conn_t *conn, const char *sn) { aim_frame_t *fr; aim_snacid_t snacid; aim_tlvlist_t *tl = NULL; struct im_connection *ic = sess->aux_data; if (!sess || !conn || !sn) return -EINVAL; if (isdigit(sn[0]) && set_getbool(&ic->acc->set, "old_icq_auth")) return goddamnicq(sess, conn, sn); sess->flags |= AIM_SESS_FLAGS_SNACLOGIN; aim_sendflapver(sess, conn); if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(sn)))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0017, 0x0006, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x0017, 0x0006, 0x0000, snacid); aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), (guint8 *)sn); aim_writetlvchain(&fr->data, &tl); aim_freetlvchain(&tl); aim_tx_enqueue(sess, fr); return 0; } /* * Part two of the ICQ hack. Note the ignoring of the key and clientinfo. */ static int goddamnicq2(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *password) { static const char clientstr[] = {"ICQ Inc. - Product of ICQ (TM) 2001b.5.17.1.3642.85"}; static const char lang[] = {"en"}; static const char country[] = {"us"}; aim_frame_t *fr; aim_tlvlist_t *tl = NULL; guint8 *password_encoded; if (!(password_encoded = (guint8 *) g_malloc(strlen(password)))) return -ENOMEM; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x01, 1152))) { g_free(password_encoded); return -ENOMEM; } aim_encode_password(password, password_encoded); aimbs_put32(&fr->data, 0x00000001); aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), (guint8 *)sn); aim_addtlvtochain_raw(&tl, 0x0002, strlen(password), password_encoded); aim_addtlvtochain_raw(&tl, 0x0003, strlen(clientstr), (guint8 *)clientstr); aim_addtlvtochain16(&tl, 0x0016, 0x010a); /* cliend ID */ aim_addtlvtochain16(&tl, 0x0017, 0x0005); /* major version */ aim_addtlvtochain16(&tl, 0x0018, 0x0011); /* minor version */ aim_addtlvtochain16(&tl, 0x0019, 0x0001); /* point version */ aim_addtlvtochain16(&tl, 0x001a, 0x0e3a); /* build */ aim_addtlvtochain32(&tl, 0x0014, 0x00000055); /* distribution chan */ aim_addtlvtochain_raw(&tl, 0x000f, strlen(lang), (guint8 *)lang); aim_addtlvtochain_raw(&tl, 0x000e, strlen(country), (guint8 *)country); aim_writetlvchain(&fr->data, &tl); g_free(password_encoded); aim_freetlvchain(&tl); aim_tx_enqueue(sess, fr); return 0; } /* * send_login(int socket, char *sn, char *password) * * This is the initial login request packet. * * NOTE!! If you want/need to make use of the aim_sendmemblock() function, * then the client information you send here must exactly match the * executable that you're pulling the data from. * * WinAIM 4.8.2540 * clientstring = "AOL Instant Messenger (SM), version 4.8.2540/WIN32" * clientid = 0x0109 * major = 0x0004 * minor = 0x0008 * point = 0x0000 * build = 0x09ec * t(0x0014) = 0x000000af * t(0x004a) = 0x01 * * WinAIM 4.3.2188: * clientstring = "AOL Instant Messenger (SM), version 4.3.2188/WIN32" * clientid = 0x0109 * major = 0x0400 * minor = 0x0003 * point = 0x0000 * build = 0x088c * unknown = 0x00000086 * lang = "en" * country = "us" * unknown4a = 0x01 * * Latest WinAIM that libfaim can emulate without server-side buddylists: * clientstring = "AOL Instant Messenger (SM), version 4.1.2010/WIN32" * clientid = 0x0004 * major = 0x0004 * minor = 0x0001 * point = 0x0000 * build = 0x07da * unknown= 0x0000004b * * WinAIM 3.5.1670: * clientstring = "AOL Instant Messenger (SM), version 3.5.1670/WIN32" * clientid = 0x0004 * major = 0x0003 * minor = 0x0005 * point = 0x0000 * build = 0x0686 * unknown =0x0000002a * * Java AIM 1.1.19: * clientstring = "AOL Instant Messenger (TM) version 1.1.19 for Java built 03/24/98, freeMem 215871 totalMem 1048567, i686, Linus, #2 SMP Sun Feb 11 03:41:17 UTC 2001 2.4.1-ac9, IBM Corporation, 1.1.8, 45.3, Tue Mar 27 12:09:17 PST 2001" * clientid = 0x0001 * major = 0x0001 * minor = 0x0001 * point = (not sent) * build = 0x0013 * unknown= (not sent) * * AIM for Linux 1.1.112: * clientstring = "AOL Instant Messenger (SM)" * clientid = 0x1d09 * major = 0x0001 * minor = 0x0001 * point = 0x0001 * build = 0x0070 * unknown= 0x0000008b * serverstore = 0x01 * */ int aim_send_login(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *password, struct client_info_s *ci, const char *key) { aim_frame_t *fr; aim_tlvlist_t *tl = NULL; guint8 digest[16]; aim_snacid_t snacid; if (!ci || !sn || !password) return -EINVAL; /* * What the XORLOGIN flag _really_ means is that its an ICQ login, * which is really stupid and painful, so its not done here. * */ if (sess->flags & AIM_SESS_FLAGS_XORLOGIN) return goddamnicq2(sess, conn, sn, password); if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) return -ENOMEM; snacid = aim_cachesnac(sess, 0x0017, 0x0002, 0x0000, NULL, 0); aim_putsnac(&fr->data, 0x0017, 0x0002, 0x0000, snacid); aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), (guint8 *)sn); aim_encode_password_md5(password, key, digest); aim_addtlvtochain_raw(&tl, 0x0025, 16, digest); /* * Newer versions of winaim have an empty type x004c TLV here. */ if (ci->clientstring) aim_addtlvtochain_raw(&tl, 0x0003, strlen(ci->clientstring), (guint8 *)ci->clientstring); aim_addtlvtochain16(&tl, 0x0016, (guint16)ci->clientid); aim_addtlvtochain16(&tl, 0x0017, (guint16)ci->major); aim_addtlvtochain16(&tl, 0x0018, (guint16)ci->minor); aim_addtlvtochain16(&tl, 0x0019, (guint16)ci->point); aim_addtlvtochain16(&tl, 0x001a, (guint16)ci->build); aim_addtlvtochain_raw(&tl, 0x000e, strlen(ci->country), (guint8 *)ci->country); aim_addtlvtochain_raw(&tl, 0x000f, strlen(ci->lang), (guint8 *)ci->lang); /* * If set, old-fashioned buddy lists will not work. You will need * to use SSI. */ aim_addtlvtochain8(&tl, 0x004a, 0x01); aim_writetlvchain(&fr->data, &tl); aim_freetlvchain(&tl); aim_tx_enqueue(sess, fr); return 0; } int aim_encode_password_md5(const char *password, const char *key, guint8 *digest) { md5_state_t state; md5_init(&state); md5_append(&state, (const md5_byte_t *)key, strlen(key)); md5_append(&state, (const md5_byte_t *)password, strlen(password)); md5_append(&state, (const md5_byte_t *)AIM_MD5_STRING, strlen(AIM_MD5_STRING)); md5_finish(&state, (md5_byte_t *)digest); return 0; } /** * aim_encode_password - Encode a password using old XOR method * @password: incoming password * @encoded: buffer to put encoded password * * This takes a const pointer to a (null terminated) string * containing the unencoded password. It also gets passed * an already allocated buffer to store the encoded password. * This buffer should be the exact length of the password without * the null. The encoded password buffer /is not %NULL terminated/. * * The encoding_table seems to be a fixed set of values. We'll * hope it doesn't change over time! * * This is only used for the XOR method, not the better MD5 method. * */ static int aim_encode_password(const char *password, guint8 *encoded) { guint8 encoding_table[] = { /* v2.1 table, also works for ICQ */ 0xf3, 0x26, 0x81, 0xc4, 0x39, 0x86, 0xdb, 0x92, 0x71, 0xa3, 0xb9, 0xe6, 0x53, 0x7a, 0x95, 0x7c }; int i; for (i = 0; i < strlen(password); i++) encoded[i] = (password[i] ^ encoding_table[i]); return 0; } /* * This is sent back as a general response to the login command. * It can be either an error or a success, depending on the * precense of certain TLVs. * * The client should check the value passed as errorcode. If * its nonzero, there was an error. * */ static int parse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_tlvlist_t *tlvlist; aim_rxcallback_t userfunc; struct aim_authresp_info info; int ret = 0; memset(&info, 0, sizeof(info)); /* * Read block of TLVs. All further data is derived * from what is parsed here. */ tlvlist = aim_readtlvchain(bs); /* * No matter what, we should have a screen name. */ memset(sess->sn, 0, sizeof(sess->sn)); if (aim_gettlv(tlvlist, 0x0001, 1)) { info.sn = aim_gettlv_str(tlvlist, 0x0001, 1); strncpy(sess->sn, info.sn, sizeof(sess->sn)); } /* * Check for an error code. If so, we should also * have an error url. */ if (aim_gettlv(tlvlist, 0x0008, 1)) info.errorcode = aim_gettlv16(tlvlist, 0x0008, 1); if (aim_gettlv(tlvlist, 0x0004, 1)) info.errorurl = aim_gettlv_str(tlvlist, 0x0004, 1); /* * BOS server address. */ if (aim_gettlv(tlvlist, 0x0005, 1)) info.bosip = aim_gettlv_str(tlvlist, 0x0005, 1); /* * Authorization cookie. */ if (aim_gettlv(tlvlist, 0x0006, 1)) { aim_tlv_t *tmptlv; tmptlv = aim_gettlv(tlvlist, 0x0006, 1); info.cookie = tmptlv->value; } /* * The email address attached to this account * Not available for ICQ logins. */ if (aim_gettlv(tlvlist, 0x0011, 1)) info.email = aim_gettlv_str(tlvlist, 0x0011, 1); /* * The registration status. (Not real sure what it means.) * Not available for ICQ logins. * * 1 = No disclosure * 2 = Limited disclosure * 3 = Full disclosure * * This has to do with whether your email address is available * to other users or not. AFAIK, this feature is no longer used. * */ if (aim_gettlv(tlvlist, 0x0013, 1)) info.regstatus = aim_gettlv16(tlvlist, 0x0013, 1); if (aim_gettlv(tlvlist, 0x0040, 1)) info.latestbeta.build = aim_gettlv32(tlvlist, 0x0040, 1); if (aim_gettlv(tlvlist, 0x0041, 1)) info.latestbeta.url = aim_gettlv_str(tlvlist, 0x0041, 1); if (aim_gettlv(tlvlist, 0x0042, 1)) info.latestbeta.info = aim_gettlv_str(tlvlist, 0x0042, 1); if (aim_gettlv(tlvlist, 0x0043, 1)) info.latestbeta.name = aim_gettlv_str(tlvlist, 0x0043, 1); if (aim_gettlv(tlvlist, 0x0048, 1)) ; /* no idea what this is */ if (aim_gettlv(tlvlist, 0x0044, 1)) info.latestrelease.build = aim_gettlv32(tlvlist, 0x0044, 1); if (aim_gettlv(tlvlist, 0x0045, 1)) info.latestrelease.url = aim_gettlv_str(tlvlist, 0x0045, 1); if (aim_gettlv(tlvlist, 0x0046, 1)) info.latestrelease.info = aim_gettlv_str(tlvlist, 0x0046, 1); if (aim_gettlv(tlvlist, 0x0047, 1)) info.latestrelease.name = aim_gettlv_str(tlvlist, 0x0047, 1); if (aim_gettlv(tlvlist, 0x0049, 1)) ; /* no idea what this is */ if ((userfunc = aim_callhandler(sess, rx->conn, snac ? snac->family : 0x0017, snac ? snac->subtype : 0x0003))) ret = userfunc(sess, rx, &info); g_free(info.sn); g_free(info.bosip); g_free(info.errorurl); g_free(info.email); g_free(info.latestrelease.name); g_free(info.latestrelease.url); g_free(info.latestrelease.info); g_free(info.latestbeta.name); g_free(info.latestbeta.url); g_free(info.latestbeta.info); aim_freetlvchain(&tlvlist); return ret; } /* * Middle handler for 0017/0007 SNACs. Contains the auth key prefixed * by only its length in a two byte word. * * Calls the client, which should then use the value to call aim_send_login. * */ static int keyparse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int keylen, ret = 1; aim_rxcallback_t userfunc; char *keystr; keylen = aimbs_get16(bs); keystr = aimbs_getstr(bs, keylen); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, keystr); g_free(keystr); return ret; } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if (snac->subtype == 0x0003) return parse(sess, mod, rx, snac, bs); else if (snac->subtype == 0x0007) return keyparse(sess, mod, rx, snac, bs); return 0; } int auth_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = 0x0017; mod->version = 0x0000; mod->flags = 0; strncpy(mod->name, "auth", sizeof(mod->name)); mod->snachandler = snachandler; return 0; } bitlbee-3.2.1/protocols/oscar/ssi.c0000644000175000017500000010611112245474076016613 0ustar wilmerwilmer/* * Server-Side/Stored Information. * * Relatively new facility that allows storing of certain types of information, * such as a users buddy list, permit/deny list, and permit/deny preferences, * to be stored on the server, so that they can be accessed from any client. * * We keep a copy of the ssi data in sess->ssi, because the data needs to be * accessed for various reasons. So all the "aim_ssi_itemlist_bleh" functions * near the top just manage the local data. * * The SNAC sending and receiving functions are lower down in the file, and * they're simpler. They are in the order of the subtypes they deal with, * starting with the request rights function (subtype 0x0002), then parse * rights (subtype 0x0003), then--well, you get the idea. * * This is entirely too complicated. * You don't know the half of it. * * XXX - Test for memory leaks * XXX - Better parsing of rights, and use the rights info to limit adds * */ #include #include "ssi.h" /** * Locally add a new item to the given item list. * * @param list A pointer to a pointer to the current list of items. * @param parent A pointer to the parent group, or NULL if the item should have no * parent group (ie. the group ID# should be 0). * @param name A null terminated string of the name of the new item, or NULL if the * item should have no name. * @param type The type of the item, 0x0001 for a contact, 0x0002 for a group, etc. * @return The newly created item. */ static struct aim_ssi_item *aim_ssi_itemlist_add(struct aim_ssi_item **list, struct aim_ssi_item *parent, char *name, guint16 type) { int i; struct aim_ssi_item *cur, *newitem; if (!(newitem = g_new0(struct aim_ssi_item, 1))) return NULL; /* Set the name */ if (name) { if (!(newitem->name = (char *)g_malloc((strlen(name)+1)*sizeof(char)))) { g_free(newitem); return NULL; } strcpy(newitem->name, name); } else newitem->name = NULL; /* Set the group ID# and the buddy ID# */ newitem->gid = 0x0000; newitem->bid = 0x0000; if (type == AIM_SSI_TYPE_GROUP) { if (name) do { newitem->gid += 0x0001; for (cur=*list, i=0; ((cur) && (!i)); cur=cur->next) if ((cur->gid == newitem->gid) && (cur->gid == newitem->gid)) i=1; } while (i); } else { if (parent) newitem->gid = parent->gid; do { newitem->bid += 0x0001; for (cur=*list, i=0; ((cur) && (!i)); cur=cur->next) if ((cur->bid == newitem->bid) && (cur->gid == newitem->gid)) i=1; } while (i); } /* Set the rest */ newitem->type = type; newitem->data = NULL; newitem->next = *list; *list = newitem; return newitem; } /** * Locally rebuild the 0x00c8 TLV in the additional data of the given group. * * @param list A pointer to a pointer to the current list of items. * @param parentgroup A pointer to the group who's additional data you want to rebuild. * @return Return 0 if no errors, otherwise return the error number. */ static int aim_ssi_itemlist_rebuildgroup(struct aim_ssi_item **list, struct aim_ssi_item *parentgroup) { int newlen; //, i; struct aim_ssi_item *cur; /* Free the old additional data */ if (parentgroup->data) { aim_freetlvchain((aim_tlvlist_t **)&parentgroup->data); parentgroup->data = NULL; } /* Find the length for the new additional data */ newlen = 0; if (parentgroup->gid == 0x0000) { for (cur=*list; cur; cur=cur->next) if ((cur->gid != 0x0000) && (cur->type == AIM_SSI_TYPE_GROUP)) newlen += 2; } else { for (cur=*list; cur; cur=cur->next) if ((cur->gid == parentgroup->gid) && (cur->type == AIM_SSI_TYPE_BUDDY)) newlen += 2; } /* Rebuild the additional data */ if (newlen>0) { guint8 *newdata; if (!(newdata = (guint8 *)g_malloc((newlen)*sizeof(guint8)))) return -ENOMEM; newlen = 0; if (parentgroup->gid == 0x0000) { for (cur=*list; cur; cur=cur->next) if ((cur->gid != 0x0000) && (cur->type == AIM_SSI_TYPE_GROUP)) newlen += aimutil_put16(newdata+newlen, cur->gid); } else { for (cur=*list; cur; cur=cur->next) if ((cur->gid == parentgroup->gid) && (cur->type == AIM_SSI_TYPE_BUDDY)) newlen += aimutil_put16(newdata+newlen, cur->bid); } aim_addtlvtochain_raw((aim_tlvlist_t **)&(parentgroup->data), 0x00c8, newlen, newdata); g_free(newdata); } return 0; } /** * Locally free all of the stored buddy list information. * * @param sess The oscar session. * @return Return 0 if no errors, otherwise return the error number. */ static int aim_ssi_freelist(aim_session_t *sess) { struct aim_ssi_item *cur, *delitem; cur = sess->ssi.items; while (cur) { if (cur->name) g_free(cur->name); if (cur->data) aim_freetlvchain((aim_tlvlist_t **)&cur->data); delitem = cur; cur = cur->next; g_free(delitem); } sess->ssi.items = NULL; sess->ssi.revision = 0; sess->ssi.timestamp = (time_t)0; return 0; } /** * Locally find an item given a group ID# and a buddy ID#. * * @param list A pointer to the current list of items. * @param gid The group ID# of the desired item. * @param bid The buddy ID# of the desired item. * @return Return a pointer to the item if found, else return NULL; */ struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_item *list, guint16 gid, guint16 bid) { struct aim_ssi_item *cur; for (cur=list; cur; cur=cur->next) if ((cur->gid == gid) && (cur->bid == bid)) return cur; return NULL; } /** * Locally find an item given a group name, screen name, and type. If group name * and screen name are null, then just return the first item of the given type. * * @param list A pointer to the current list of items. * @param gn The group name of the desired item. * @param bn The buddy name of the desired item. * @param type The type of the desired item. * @return Return a pointer to the item if found, else return NULL; */ struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_item *list, char *gn, char *sn, guint16 type) { struct aim_ssi_item *cur; if (!list) return NULL; if (gn && sn) { /* For finding buddies in groups */ for (cur=list; cur; cur=cur->next) if ((cur->type == type) && (cur->name) && !(aim_sncmp(cur->name, sn))) { struct aim_ssi_item *curg; for (curg=list; curg; curg=curg->next) if ((curg->type == AIM_SSI_TYPE_GROUP) && (curg->gid == cur->gid) && (curg->name) && !(aim_sncmp(curg->name, gn))) return cur; } } else if (sn) { /* For finding groups, permits, denies, and ignores */ for (cur=list; cur; cur=cur->next) if ((cur->type == type) && (cur->name) && !(aim_sncmp(cur->name, sn))) return cur; /* For stuff without names--permit deny setting, visibility mask, etc. */ } else for (cur=list; cur; cur=cur->next) { if (cur->type == type) return cur; } return NULL; } /** * Locally find the parent item of the given buddy name. * * @param list A pointer to the current list of items. * @param bn The buddy name of the desired item. * @return Return a pointer to the item if found, else return NULL; */ struct aim_ssi_item *aim_ssi_itemlist_findparent(struct aim_ssi_item *list, char *sn) { struct aim_ssi_item *cur, *curg; if (!list || !sn) return NULL; if (!(cur = aim_ssi_itemlist_finditem(list, NULL, sn, AIM_SSI_TYPE_BUDDY))) return NULL; for (curg=list; curg; curg=curg->next) if ((curg->type == AIM_SSI_TYPE_GROUP) && (curg->gid == cur->gid)) return curg; return NULL; } /** * Locally find the permit/deny setting item, and return the setting. * * @param list A pointer to the current list of items. * @return Return the current SSI permit deny setting, or 0 if no setting was found. */ int aim_ssi_getpermdeny(struct aim_ssi_item *list) { struct aim_ssi_item *cur = aim_ssi_itemlist_finditem(list, NULL, NULL, AIM_SSI_TYPE_PDINFO); if (cur) { aim_tlvlist_t *tlvlist = cur->data; if (tlvlist) { aim_tlv_t *tlv = aim_gettlv(tlvlist, 0x00ca, 1); if (tlv && tlv->value) return aimutil_get8(tlv->value); } } return 0; } /** * Add the given packet to the holding queue. We totally need to send SSI SNACs one at * a time, so we have a local queue where packets get put before they are sent, and * then we send stuff one at a time, nice and orderly-like. * * @param sess The oscar session. * @param conn The bos connection for this session. * @param fr The newly created SNAC that you want to send. * @return Return 0 if no errors, otherwise return the error number. */ static int aim_ssi_enqueue(aim_session_t *sess, aim_conn_t *conn, aim_frame_t *fr) { aim_frame_t *cur; if (!sess || !conn || !fr) return -EINVAL; fr->next = NULL; if (sess->ssi.holding_queue == NULL) { sess->ssi.holding_queue = fr; if (!sess->ssi.waiting_for_ack) aim_ssi_modbegin(sess, conn); } else { for (cur = sess->ssi.holding_queue; cur->next; cur = cur->next) ; cur->next = fr; } return 0; } /** * Send the next SNAC from the holding queue. This is called * automatically when an ack from an add, mod, or del is received. * If the queue is empty, it sends the modend SNAC. * * @param sess The oscar session. * @param conn The bos connection for this session. * @return Return 0 if no errors, otherwise return the error number. */ static int aim_ssi_dispatch(aim_session_t *sess, aim_conn_t *conn) { aim_frame_t *cur; if (!sess || !conn) return -EINVAL; if (!sess->ssi.waiting_for_ack) { if (sess->ssi.holding_queue) { sess->ssi.waiting_for_ack = 1; cur = sess->ssi.holding_queue->next; sess->ssi.holding_queue->next = NULL; aim_tx_enqueue(sess, sess->ssi.holding_queue); sess->ssi.holding_queue = cur; } else aim_ssi_modend(sess, conn); } return 0; } /** * Add an array of screen names to the given group. * * @param sess The oscar session. * @param conn The bos connection for this session. * @param gn The name of the group to which you want to add these names. * @param sn An array of null terminated strings of the names you want to add. * @param num The number of screen names you are adding (size of the sn array). * @param flags 1 - Add with TLV(0x66) * @return Return 0 if no errors, otherwise return the error number. */ int aim_ssi_addbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num, unsigned int flags) { struct aim_ssi_item *parentgroup, **newitems; guint16 i; if (!sess || !conn || !gn || !sn || !num) return -EINVAL; /* Look up the parent group */ if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP))) { aim_ssi_addgroups(sess, conn, &gn, 1); if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP))) return -ENOMEM; } /* Allocate an array of pointers to each of the new items */ if (!(newitems = g_new0(struct aim_ssi_item *, num))) return -ENOMEM; /* Add items to the local list, and index them in the array */ for (i=0; issi.items, parentgroup, sn[i], AIM_SSI_TYPE_BUDDY))) { g_free(newitems); return -ENOMEM; } else if (flags & 1) { aim_tlvlist_t *tl = NULL; aim_addtlvtochain_noval(&tl, 0x66); newitems[i]->data = tl; } /* Send the add item SNAC */ if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) { g_free(newitems); return -i; } /* Free the array of pointers to each of the new items */ g_free(newitems); /* Rebuild the additional data in the parent group */ if ((i = aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup))) return i; /* Send the mod item SNAC */ if ((i = aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD ))) return i; /* Begin sending SSI SNACs */ if (!(i = aim_ssi_dispatch(sess, conn))) return i; return 0; } /** * Add the master group (the group containing all groups). This is called by * aim_ssi_addgroups, if necessary. * * @param sess The oscar session. * @param conn The bos connection for this session. * @return Return 0 if no errors, otherwise return the error number. */ int aim_ssi_addmastergroup(aim_session_t *sess, aim_conn_t *conn) { struct aim_ssi_item *newitem; if (!sess || !conn) return -EINVAL; /* Add the item to the local list, and keep a pointer to it */ if (!(newitem = aim_ssi_itemlist_add(&sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_GROUP))) return -ENOMEM; /* If there are any existing groups (technically there shouldn't be, but */ /* just in case) then add their group ID#'s to the additional data */ aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, newitem); /* Send the add item SNAC */ aim_ssi_addmoddel(sess, conn, &newitem, 1, AIM_CB_SSI_ADD); /* Begin sending SSI SNACs */ aim_ssi_dispatch(sess, conn); return 0; } /** * Add an array of groups to the list. * * @param sess The oscar session. * @param conn The bos connection for this session. * @param gn An array of null terminated strings of the names you want to add. * @param num The number of groups names you are adding (size of the sn array). * @return Return 0 if no errors, otherwise return the error number. */ int aim_ssi_addgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num) { struct aim_ssi_item *parentgroup, **newitems; guint16 i; if (!sess || !conn || !gn || !num) return -EINVAL; /* Look up the parent group */ if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) { aim_ssi_addmastergroup(sess, conn); if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) return -ENOMEM; } /* Allocate an array of pointers to each of the new items */ if (!(newitems = g_new0(struct aim_ssi_item *, num))) return -ENOMEM; /* Add items to the local list, and index them in the array */ for (i=0; issi.items, parentgroup, gn[i], AIM_SSI_TYPE_GROUP))) { g_free(newitems); return -ENOMEM; } /* Send the add item SNAC */ if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) { g_free(newitems); return -i; } /* Free the array of pointers to each of the new items */ g_free(newitems); /* Rebuild the additional data in the parent group */ if ((i = aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup))) return i; /* Send the mod item SNAC */ if ((i = aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD))) return i; /* Begin sending SSI SNACs */ if (!(i = aim_ssi_dispatch(sess, conn))) return i; return 0; } /** * Add an array of a certain type of item to the list. This can be used for * permit buddies, deny buddies, ICQ's ignore buddies, and probably other * types, also. * * @param sess The oscar session. * @param conn The bos connection for this session. * @param sn An array of null terminated strings of the names you want to add. * @param num The number of groups names you are adding (size of the sn array). * @param type The type of item you want to add. See the AIM_SSI_TYPE_BLEH * #defines in aim.h. * @return Return 0 if no errors, otherwise return the error number. */ int aim_ssi_addpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type) { struct aim_ssi_item **newitems; guint16 i; if (!sess || !conn || !sn || !num) return -EINVAL; /* Allocate an array of pointers to each of the new items */ if (!(newitems = g_new0(struct aim_ssi_item *, num))) return -ENOMEM; /* Add items to the local list, and index them in the array */ for (i=0; issi.items, NULL, sn[i], type))) { g_free(newitems); return -ENOMEM; } /* Send the add item SNAC */ if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) { g_free(newitems); return -i; } /* Free the array of pointers to each of the new items */ g_free(newitems); /* Begin sending SSI SNACs */ if (!(i = aim_ssi_dispatch(sess, conn))) return i; return 0; } /** * Move a buddy from one group to another group. This basically just deletes the * buddy and re-adds it. * * @param sess The oscar session. * @param conn The bos connection for this session. * @param oldgn The group that the buddy is currently in. * @param newgn The group that the buddy should be moved in to. * @param sn The name of the buddy to be moved. * @return Return 0 if no errors, otherwise return the error number. */ int aim_ssi_movebuddy(aim_session_t *sess, aim_conn_t *conn, char *oldgn, char *newgn, char *sn) { struct aim_ssi_item **groups, *buddy, *cur; guint16 i; if (!sess || !conn || !oldgn || !newgn || !sn) return -EINVAL; /* Look up the buddy */ if (!(buddy = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, sn, AIM_SSI_TYPE_BUDDY))) return -ENOMEM; /* Allocate an array of pointers to the two groups */ if (!(groups = g_new0(struct aim_ssi_item *, 2))) return -ENOMEM; /* Look up the old parent group */ if (!(groups[0] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, oldgn, AIM_SSI_TYPE_GROUP))) { g_free(groups); return -ENOMEM; } /* Look up the new parent group */ if (!(groups[1] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, newgn, AIM_SSI_TYPE_GROUP))) { g_free(groups); return -ENOMEM; } /* Send the delete item SNAC */ aim_ssi_addmoddel(sess, conn, &buddy, 1, AIM_CB_SSI_DEL); /* Put the buddy in the new group */ buddy->gid = groups[1]->gid; /* Assign a new buddy ID#, because the new group might already have a buddy with this ID# */ buddy->bid = 0; do { buddy->bid += 0x0001; for (cur=sess->ssi.items, i=0; ((cur) && (!i)); cur=cur->next) if ((cur->bid == buddy->bid) && (cur->gid == buddy->gid) && (cur->type == AIM_SSI_TYPE_BUDDY) && (cur->name) && aim_sncmp(cur->name, buddy->name)) i=1; } while (i); /* Rebuild the additional data in the two parent groups */ aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, groups[0]); aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, groups[1]); /* Send the add item SNAC */ aim_ssi_addmoddel(sess, conn, &buddy, 1, AIM_CB_SSI_ADD); /* Send the mod item SNAC */ aim_ssi_addmoddel(sess, conn, groups, 2, AIM_CB_SSI_MOD); /* Free the temporary array */ g_free(groups); /* Begin sending SSI SNACs */ aim_ssi_dispatch(sess, conn); return 0; } /** * Delete an array of screen names from the given group. * * @param sess The oscar session. * @param conn The bos connection for this session. * @param gn The name of the group from which you want to delete these names. * @param sn An array of null terminated strings of the names you want to delete. * @param num The number of screen names you are deleting (size of the sn array). * @return Return 0 if no errors, otherwise return the error number. */ int aim_ssi_delbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num) { struct aim_ssi_item *cur, *parentgroup, **delitems; int i; if (!sess || !conn || !gn || !sn || !num) return -EINVAL; /* Look up the parent group */ if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP))) return -EINVAL; /* Allocate an array of pointers to each of the items to be deleted */ delitems = g_new0(struct aim_ssi_item *, num); /* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */ for (i=0; issi.items, NULL, sn[i], AIM_SSI_TYPE_BUDDY))) { g_free(delitems); return -EINVAL; } /* Remove the delitems from the item list */ if (sess->ssi.items == delitems[i]) { sess->ssi.items = sess->ssi.items->next; } else { for (cur=sess->ssi.items; (cur->next && (cur->next!=delitems[i])); cur=cur->next); if (cur->next) cur->next = cur->next->next; } } /* Send the del item SNAC */ aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL); /* Free the items */ for (i=0; iname) g_free(delitems[i]->name); if (delitems[i]->data) aim_freetlvchain((aim_tlvlist_t **)&delitems[i]->data); g_free(delitems[i]); } g_free(delitems); /* Rebuild the additional data in the parent group */ aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup); /* Send the mod item SNAC */ aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD); /* Delete the group, but only if it's empty */ if (!parentgroup->data) aim_ssi_delgroups(sess, conn, &parentgroup->name, 1); /* Begin sending SSI SNACs */ aim_ssi_dispatch(sess, conn); return 0; } /** * Delete the master group from the item list. There can be only one. * Er, so just find the one master group and delete it. * * @param sess The oscar session. * @param conn The bos connection for this session. * @return Return 0 if no errors, otherwise return the error number. */ int aim_ssi_delmastergroup(aim_session_t *sess, aim_conn_t *conn) { struct aim_ssi_item *cur, *delitem; if (!sess || !conn) return -EINVAL; /* Make delitem a pointer to the aim_ssi_item to be deleted */ if (!(delitem = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) return -EINVAL; /* Remove delitem from the item list */ if (sess->ssi.items == delitem) { sess->ssi.items = sess->ssi.items->next; } else { for (cur=sess->ssi.items; (cur->next && (cur->next!=delitem)); cur=cur->next); if (cur->next) cur->next = cur->next->next; } /* Send the del item SNAC */ aim_ssi_addmoddel(sess, conn, &delitem, 1, AIM_CB_SSI_DEL); /* Free the item */ if (delitem->name) g_free(delitem->name); if (delitem->data) aim_freetlvchain((aim_tlvlist_t **)&delitem->data); g_free(delitem); /* Begin sending SSI SNACs */ aim_ssi_dispatch(sess, conn); return 0; } /** * Delete an array of groups. * * @param sess The oscar session. * @param conn The bos connection for this session. * @param gn An array of null terminated strings of the groups you want to delete. * @param num The number of groups you are deleting (size of the gn array). * @return Return 0 if no errors, otherwise return the error number. */ int aim_ssi_delgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num) { struct aim_ssi_item *cur, *parentgroup, **delitems; int i; if (!sess || !conn || !gn || !num) return -EINVAL; /* Look up the parent group */ if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) return -EINVAL; /* Allocate an array of pointers to each of the items to be deleted */ delitems = g_new0(struct aim_ssi_item *, num); /* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */ for (i=0; issi.items, NULL, gn[i], AIM_SSI_TYPE_GROUP))) { g_free(delitems); return -EINVAL; } /* Remove the delitems from the item list */ if (sess->ssi.items == delitems[i]) { sess->ssi.items = sess->ssi.items->next; } else { for (cur=sess->ssi.items; (cur->next && (cur->next!=delitems[i])); cur=cur->next); if (cur->next) cur->next = cur->next->next; } } /* Send the del item SNAC */ aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL); /* Free the items */ for (i=0; iname) g_free(delitems[i]->name); if (delitems[i]->data) aim_freetlvchain((aim_tlvlist_t **)&delitems[i]->data); g_free(delitems[i]); } g_free(delitems); /* Rebuild the additional data in the parent group */ aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup); /* Send the mod item SNAC */ aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD); /* Delete the group, but only if it's empty */ if (!parentgroup->data) aim_ssi_delmastergroup(sess, conn); /* Begin sending SSI SNACs */ aim_ssi_dispatch(sess, conn); return 0; } /** * Delete an array of a certain type of item from the list. This can be * used for permit buddies, deny buddies, ICQ's ignore buddies, and * probably other types, also. * * @param sess The oscar session. * @param conn The bos connection for this session. * @param sn An array of null terminated strings of the items you want to delete. * @param num The number of items you are deleting (size of the sn array). * @return Return 0 if no errors, otherwise return the error number. */ int aim_ssi_delpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type) { struct aim_ssi_item *cur, **delitems; int i; if (!sess || !conn || !sn || !num || (type!=AIM_SSI_TYPE_PERMIT && type!=AIM_SSI_TYPE_DENY)) return -EINVAL; /* Allocate an array of pointers to each of the items to be deleted */ delitems = g_new0(struct aim_ssi_item *, num); /* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */ for (i=0; issi.items, NULL, sn[i], type))) { g_free(delitems); return -EINVAL; } /* Remove the delitems from the item list */ if (sess->ssi.items == delitems[i]) { sess->ssi.items = sess->ssi.items->next; } else { for (cur=sess->ssi.items; (cur->next && (cur->next!=delitems[i])); cur=cur->next); if (cur->next) cur->next = cur->next->next; } } /* Send the del item SNAC */ aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL); /* Free the items */ for (i=0; iname) g_free(delitems[i]->name); if (delitems[i]->data) aim_freetlvchain((aim_tlvlist_t **)&delitems[i]->data); g_free(delitems[i]); } g_free(delitems); /* Begin sending SSI SNACs */ aim_ssi_dispatch(sess, conn); return 0; } /* * Request SSI Rights. */ int aim_ssi_reqrights(aim_session_t *sess, aim_conn_t *conn) { return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_REQRIGHTS); } /* * SSI Rights Information. */ static int parserights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; aim_rxcallback_t userfunc; if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx); return ret; } int aim_ssi_reqalldata(aim_session_t *sess, aim_conn_t *conn) { aim_frame_t *fr; aim_snacid_t snacid; if (!sess || !conn) return -EINVAL; if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10))) return -ENOMEM; snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, AIM_CB_SSI_REQFULLLIST, 0x0000, NULL, 0); aim_putsnac(&fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_REQFULLLIST, 0x0000, snacid); aim_tx_enqueue(sess, fr); return 0; } /* * SSI Data. */ static int parsedata(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; aim_rxcallback_t userfunc; struct aim_ssi_item *cur = NULL; guint8 fmtver; /* guess */ guint16 revision; guint32 timestamp; /* When you set the version for the SSI family to 2-4, the beginning of this changes. * Instead of the version and then the revision, there is "0x0006" and then a type * 0x0001 TLV containing the 2 byte SSI family version that you sent earlier. Also, * the SNAC flags go from 0x0000 to 0x8000. I guess the 0x0006 is the length of the * TLV(s) that follow. The rights SNAC does the same thing, with the differing flag * and everything. */ fmtver = aimbs_get8(bs); /* Version of ssi data. Should be 0x00 */ revision = aimbs_get16(bs); /* # of times ssi data has been modified */ if (revision != 0) sess->ssi.revision = revision; for (cur = sess->ssi.items; cur && cur->next; cur=cur->next) ; while (aim_bstream_empty(bs) > 4) { /* last four bytes are stamp */ guint16 namelen, tbslen; if (!sess->ssi.items) { if (!(sess->ssi.items = g_new0(struct aim_ssi_item, 1))) return -ENOMEM; cur = sess->ssi.items; } else { if (!(cur->next = g_new0(struct aim_ssi_item, 1))) return -ENOMEM; cur = cur->next; } if ((namelen = aimbs_get16(bs))) cur->name = aimbs_getstr(bs, namelen); cur->gid = aimbs_get16(bs); cur->bid = aimbs_get16(bs); cur->type = aimbs_get16(bs); if ((tbslen = aimbs_get16(bs))) { aim_bstream_t tbs; aim_bstream_init(&tbs, bs->data + bs->offset /* XXX */, tbslen); cur->data = (void *)aim_readtlvchain(&tbs); aim_bstream_advance(bs, tbslen); } } timestamp = aimbs_get32(bs); if (timestamp != 0) sess->ssi.timestamp = timestamp; sess->ssi.received_data = 1; if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, fmtver, sess->ssi.revision, sess->ssi.timestamp, sess->ssi.items); return ret; } /* * SSI Data Enable Presence. * * Should be sent after receiving 13/6 or 13/f to tell the server you * are ready to begin using the list. It will promptly give you the * presence information for everyone in your list and put your permit/deny * settings into effect. * */ int aim_ssi_enable(aim_session_t *sess, aim_conn_t *conn) { return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, 0x0007); } /* * Stuff for SSI authorizations. The code used to work with the old im_ch4 * messages, but those are supposed to be obsolete. This is probably * ICQ-specific. */ /** * Request authorization to add someone to the server-side buddy list. * * @param sess The oscar session. * @param conn The bos connection for this session. * @param uin The contact's ICQ UIN. * @param reason The reason string to send with the request. * @return Return 0 if no errors, otherwise return the error number. */ int aim_ssi_auth_request( aim_session_t *sess, aim_conn_t *conn, char *uin, char *reason ) { aim_frame_t *fr; aim_snacid_t snacid; int snaclen; snaclen = 10 + 1 + strlen( uin ) + 2 + strlen( reason ) + 2; if( !( fr = aim_tx_new( sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen ) ) ) return -ENOMEM; snacid = aim_cachesnac( sess, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREQ, 0x0000, NULL, 0 ); aim_putsnac( &fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREQ, 0x0000, snacid ); aimbs_put8( &fr->data, strlen( uin ) ); aimbs_putraw( &fr->data, (guint8 *)uin, strlen( uin ) ); aimbs_put16( &fr->data, strlen( reason ) ); aimbs_putraw( &fr->data, (guint8 *)reason, strlen( reason ) ); aimbs_put16( &fr->data, 0 ); aim_tx_enqueue( sess, fr ); return( 0 ); } /** * Reply to an authorization request to add someone to the server-side buddy list. * * @param sess The oscar session. * @param conn The bos connection for this session. * @param uin The contact's ICQ UIN. * @param yesno 1 == Permit, 0 == Deny * @param reason The reason string to send with the request. * @return Return 0 if no errors, otherwise return the error number. */ int aim_ssi_auth_reply( aim_session_t *sess, aim_conn_t *conn, char *uin, int yesno, char *reason ) { aim_frame_t *fr; aim_snacid_t snacid; int snaclen; snaclen = 10 + 1 + strlen( uin ) + 3 + strlen( reason ); if( !( fr = aim_tx_new( sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen ) ) ) return -ENOMEM; snacid = aim_cachesnac( sess, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREP, 0x0000, NULL, 0 ); aim_putsnac( &fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREP, 0x0000, snacid ); aimbs_put8( &fr->data, strlen( uin ) ); aimbs_putraw( &fr->data, (guint8 *)uin, strlen( uin ) ); aimbs_put8( &fr->data, yesno ); aimbs_put16( &fr->data, strlen( reason ) ); aimbs_putraw( &fr->data, (guint8 *)reason, strlen( reason ) ); aim_tx_enqueue( sess, fr ); return( 0 ); } /* * SSI Add/Mod/Del Item(s). * * Sends the SNAC to add, modify, or delete an item from the server-stored * information. These 3 SNACs all have an identical structure. The only * difference is the subtype that is set for the SNAC. * */ int aim_ssi_addmoddel(aim_session_t *sess, aim_conn_t *conn, struct aim_ssi_item **items, unsigned int num, guint16 subtype) { aim_frame_t *fr; aim_snacid_t snacid; int i, snaclen, listlen; char *list = NULL; if (!sess || !conn || !items || !num) return -EINVAL; snaclen = 10; /* For family, subtype, flags, and SNAC ID */ listlen = 0; for (i=0; iname) { snaclen += strlen(items[i]->name); if (subtype == AIM_CB_SSI_ADD) { list = g_realloc(list, listlen + strlen(items[i]->name) + 1); strcpy(list + listlen, items[i]->name); listlen += strlen(items[i]->name) + 1; } } else { if (subtype == AIM_CB_SSI_ADD) { list = g_realloc(list, listlen + 1); list[listlen] = '\0'; listlen ++; } } if (items[i]->data) snaclen += aim_sizetlvchain((aim_tlvlist_t **)&items[i]->data); } if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen))) return -ENOMEM; snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, subtype, 0x0000, list, list ? listlen : 0); aim_putsnac(&fr->data, AIM_CB_FAM_SSI, subtype, 0x0000, snacid); g_free(list); for (i=0; idata, items[i]->name ? strlen(items[i]->name) : 0); if (items[i]->name) aimbs_putraw(&fr->data, (guint8 *)items[i]->name, strlen(items[i]->name)); aimbs_put16(&fr->data, items[i]->gid); aimbs_put16(&fr->data, items[i]->bid); aimbs_put16(&fr->data, items[i]->type); aimbs_put16(&fr->data, items[i]->data ? aim_sizetlvchain((aim_tlvlist_t **)&items[i]->data) : 0); if (items[i]->data) aim_writetlvchain(&fr->data, (aim_tlvlist_t **)&items[i]->data); } aim_ssi_enqueue(sess, conn, fr); return 0; } /* * SSI Add/Mod/Del Ack. * * Response to add, modify, or delete SNAC (sent with aim_ssi_addmoddel). * */ static int parseack(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; aim_rxcallback_t userfunc; aim_snac_t *origsnac; sess->ssi.waiting_for_ack = 0; aim_ssi_dispatch(sess, rx->conn); origsnac = aim_remsnac(sess, snac->id); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, origsnac); if (origsnac) { g_free(origsnac->data); g_free(origsnac); } return ret; } /* * SSI Begin Data Modification. * * Tells the server you're going to start modifying data. * */ int aim_ssi_modbegin(aim_session_t *sess, aim_conn_t *conn) { return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_EDITSTART); } /* * SSI End Data Modification. * * Tells the server you're done modifying data. * */ int aim_ssi_modend(aim_session_t *sess, aim_conn_t *conn) { return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_EDITSTOP); } /* * SSI Data Unchanged. * * Response to aim_ssi_reqdata() if the server-side data is not newer than * posted local stamp/revision. * */ static int parsedataunchanged(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; aim_rxcallback_t userfunc; sess->ssi.received_data = 1; if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx); return ret; } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if (snac->subtype == AIM_CB_SSI_RIGHTSINFO) return parserights(sess, mod, rx, snac, bs); else if (snac->subtype == AIM_CB_SSI_LIST) return parsedata(sess, mod, rx, snac, bs); else if (snac->subtype == AIM_CB_SSI_SRVACK) return parseack(sess, mod, rx, snac, bs); else if (snac->subtype == AIM_CB_SSI_NOLIST) return parsedataunchanged(sess, mod, rx, snac, bs); return 0; } static void ssi_shutdown(aim_session_t *sess, aim_module_t *mod) { aim_ssi_freelist(sess); return; } int ssi_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = AIM_CB_FAM_SSI; mod->version = 0x0003; mod->toolid = 0x0110; mod->toolversion = 0x0629; mod->flags = 0; strncpy(mod->name, "ssi", sizeof(mod->name)); mod->snachandler = snachandler; mod->shutdown = ssi_shutdown; return 0; } bitlbee-3.2.1/protocols/oscar/stats.c0000644000175000017500000000151712245474076017157 0ustar wilmerwilmer #include static int reportinterval(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { guint16 interval; aim_rxcallback_t userfunc; interval = aimbs_get16(bs); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) return userfunc(sess, rx, interval); return 0; } static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { if (snac->subtype == 0x0002) return reportinterval(sess, mod, rx, snac, bs); return 0; } int stats_modfirst(aim_session_t *sess, aim_module_t *mod) { mod->family = 0x000b; mod->version = 0x0001; mod->toolid = 0x0104; mod->toolversion = 0x0001; mod->flags = 0; strncpy(mod->name, "stats", sizeof(mod->name)); mod->snachandler = snachandler; return 0; } bitlbee-3.2.1/protocols/oscar/msgcookie.c0000644000175000017500000001005612245474076017777 0ustar wilmerwilmer/* * Cookie Caching stuff. Adam wrote this, apparently just some * derivatives of n's SNAC work. I cleaned it up, added comments. * */ /* * I'm assuming that cookies are type-specific. that is, we can have * "1234578" for type 1 and type 2 concurrently. if i'm wrong, then we * lose some error checking. if we assume cookies are not type-specific and are * wrong, we get quirky behavior when cookies step on each others' toes. */ #include #include "info.h" /** * aim_cachecookie - appends a cookie to the cookie list * @sess: session to add to * @cookie: pointer to struct to append * * if cookie->cookie for type cookie->type is found, updates the * ->addtime of the found structure; otherwise adds the given cookie * to the cache * * returns -1 on error, 0 on append, 1 on update. the cookie you pass * in may be free'd, so don't count on its value after calling this! * */ int aim_cachecookie(aim_session_t *sess, aim_msgcookie_t *cookie) { aim_msgcookie_t *newcook; if (!sess || !cookie) return -EINVAL; newcook = aim_checkcookie(sess, cookie->cookie, cookie->type); if (newcook == cookie) { newcook->addtime = time(NULL); return 1; } else if (newcook) aim_cookie_free(sess, newcook); cookie->addtime = time(NULL); cookie->next = sess->msgcookies; sess->msgcookies = cookie; return 0; } /** * aim_uncachecookie - grabs a cookie from the cookie cache (removes it from the list) * @sess: session to grab cookie from * @cookie: cookie string to look for * @type: cookie type to look for * * takes a cookie string and a cookie type and finds the cookie struct associated with that duple, removing it from the cookie list ikn the process. * * if found, returns the struct; if none found (or on error), returns NULL: */ aim_msgcookie_t *aim_uncachecookie(aim_session_t *sess, guint8 *cookie, int type) { aim_msgcookie_t *cur, **prev; if (!cookie || !sess->msgcookies) return NULL; for (prev = &sess->msgcookies; (cur = *prev); ) { if ((cur->type == type) && (memcmp(cur->cookie, cookie, 8) == 0)) { *prev = cur->next; return cur; } prev = &cur->next; } return NULL; } /** * aim_mkcookie - generate an aim_msgcookie_t *struct from a cookie string, a type, and a data pointer. * @c: pointer to the cookie string array * @type: cookie type to use * @data: data to be cached with the cookie * * returns NULL on error, a pointer to the newly-allocated cookie on * success. * */ aim_msgcookie_t *aim_mkcookie(guint8 *c, int type, void *data) { aim_msgcookie_t *cookie; if (!c) return NULL; if (!(cookie = g_new0(aim_msgcookie_t,1))) return NULL; cookie->data = data; cookie->type = type; memcpy(cookie->cookie, c, 8); return cookie; } /** * aim_checkcookie - check to see if a cookietuple has been cached * @sess: session to check for the cookie in * @cookie: pointer to the cookie string array * @type: type of the cookie to look for * * this returns a pointer to the cookie struct (still in the list) on * success; returns NULL on error/not found * */ aim_msgcookie_t *aim_checkcookie(aim_session_t *sess, const guint8 *cookie, int type) { aim_msgcookie_t *cur; for (cur = sess->msgcookies; cur; cur = cur->next) { if ((cur->type == type) && (memcmp(cur->cookie, cookie, 8) == 0)) return cur; } return NULL; } /** * aim_cookie_free - free an aim_msgcookie_t struct * @sess: session to remove the cookie from * @cookiep: the address of a pointer to the cookie struct to remove * * this function removes the cookie *cookie from teh list of cookies * in sess, and then frees all memory associated with it. including * its data! if you want to use the private data after calling this, * make sure you copy it first. * * returns -1 on error, 0 on success. * */ int aim_cookie_free(aim_session_t *sess, aim_msgcookie_t *cookie) { aim_msgcookie_t *cur, **prev; if (!sess || !cookie) return -EINVAL; for (prev = &sess->msgcookies; (cur = *prev); ) { if (cur == cookie) *prev = cur->next; else prev = &cur->next; } g_free(cookie->data); g_free(cookie); return 0; } bitlbee-3.2.1/protocols/skype/0000755000175000017500000000000012245477444015677 5ustar wilmerwilmerbitlbee-3.2.1/protocols/skype/Makefile0000644000175000017500000000140412245474076017334 0ustar wilmerwilmer-include ../../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)protocols/skype/ endif DATE := $(shell date +%Y-%m-%d) INSTALL = install ASCIIDOC = yes ifeq ($(ASCIIDOC),yes) MANPAGES = skyped.1 else MANPAGES = endif all: $(MANPAGES) clean: rm -f $(MANPAGES) # take this from the kernel check: perl checkpatch.pl --show-types --ignore LONG_LINE,CAMELCASE --no-tree --file skype.c test: all ./test.py doc: $(MANPAGES) install-doc: doc ifeq ($(ASCIIDOC),yes) $(INSTALL) -d $(DESTDIR)$(MANDIR)/man1 $(INSTALL) -m644 $(MANPAGES) $(DESTDIR)$(MANDIR)/man1 endif uninstall-doc: rm -f $(DESTDIR)$(MANDIR)/man1/skyped.1* %.1: $(_SRCDIR_)%.txt $(_SRCDIR_)asciidoc.conf a2x --asciidoc-opts="-f $(_SRCDIR_)asciidoc.conf" -a bee_date=$(DATE) -f manpage -D . $< bitlbee-3.2.1/protocols/skype/client.sh0000644000175000017500000000006612245474076017511 0ustar wilmerwilmeropenssl s_client -host localhost -port 2727 -verify 0 bitlbee-3.2.1/protocols/skype/skyped.cnf0000644000175000017500000000222612245474076017666 0ustar wilmerwilmer# create RSA certs - Server RANDFILE = skyped.rnd [ req ] default_bits = 1024 encrypt_key = yes distinguished_name = req_dn x509_extensions = cert_type [ req_dn ] countryName = Country Name (2 letter code) countryName_default = HU countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Some-State localityName = Locality Name (eg, city) 0.organizationName = Organization Name (eg, company) 0.organizationName_default = Stunnel Developers Ltd organizationalUnitName = Organizational Unit Name (eg, section) #organizationalUnitName_default = 0.commonName = Common Name (FQDN of your server) 0.commonName_default = localhost # To create a certificate for more than one name uncomment: # 1.commonName = DNS alias of your server # 2.commonName = DNS alias of your server # ... # See http://home.netscape.com/eng/security/ssl_2.0_certificate.html # to see how Netscape understands commonName. [ cert_type ] nsCertType = server bitlbee-3.2.1/protocols/skype/skyped.py0000644000175000017500000003665612245474076017566 0ustar wilmerwilmer#!/usr/bin/env python2.7 # # skyped.py # # Copyright (c) 2007-2013 by Miklos Vajna # # 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 # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, # USA. # import sys import os import signal import time import socket import Skype4Py import hashlib from ConfigParser import ConfigParser, NoOptionError from traceback import print_exception from fcntl import fcntl, F_SETFD, FD_CLOEXEC import ssl __version__ = "0.1.1" try: import gobject hasgobject = True except ImportError: import select import threading hasgobject = False def eh(type, value, tb): global options if type != KeyboardInterrupt: print_exception(type, value, tb) if hasgobject: gobject.MainLoop().quit() if options.conn: options.conn.close() if not options.dont_start_skype: # shut down client if it's running try: skype.skype.Client.Shutdown() except NameError: pass sys.exit("Exiting.") sys.excepthook = eh def wait_for_lock(lock, timeout_to_print, timeout, msg): start = time.time() locked = lock.acquire(0) while not(locked): time.sleep(0.5) if timeout_to_print and (time.time() - timeout_to_print > start): dprint("%s: Waited %f seconds" % \ (msg, time.time() - start)) timeout_to_print = False if timeout and (time.time() - timeout > start): dprint("%s: Waited %f seconds, giving up" % \ (msg, time.time() - start)) return False locked = lock.acquire(0) return True def input_handler(fd, io_condition = None): global options global skype if options.buf: for i in options.buf: skype.send(i.strip()) options.buf = None if not hasgobject: return True else: if not hasgobject: close_socket = False if wait_for_lock(options.lock, 3, 10, "input_handler"): try: input = fd.recv(1024) options.lock.release() except Exception, s: dprint("Warning, receiving 1024 bytes failed (%s)." % s) fd.close() options.conn = False options.lock.release() return False for i in input.split("\n"): if i.strip() == "SET USERSTATUS OFFLINE": close_socket = True skype.send(i.strip()) return not(close_socket) try: input = fd.recv(1024) except Exception, s: dprint("Warning, receiving 1024 bytes failed (%s)." % s) fd.close() return False for i in input.split("\n"): skype.send(i.strip()) return True def skype_idle_handler(skype): try: c = skype.skype.Command("PING", Block=True) skype.skype.SendCommand(c) except (Skype4Py.SkypeAPIError, AttributeError), s: dprint("Warning, pinging Skype failed (%s)." % (s)) time.sleep(1) return True def send(sock, txt, tries=10): global options if hasgobject: if not options.conn: return try: done = sock.sendall(txt) except socket.error as s: dprint("Warning, sending '%s' failed (%s)." % (txt, s)) options.conn.close() options.conn = False else: for attempt in xrange(1, tries+1): if not options.conn: break if wait_for_lock(options.lock, 3, 10, "socket send"): try: if options.conn: done = sock.sendall(txt) options.lock.release() except socket.error as s: options.lock.release() dprint("Warning, sending '%s' failed (%s). count=%d" % (txt, s, count)) time.sleep(1) else: break else: if options.conn: options.conn.close() options.conn = False return done def bitlbee_idle_handler(skype): global options done = False if options.conn: try: e = "PING" done = send(options.conn, "%s\n" % e) except Exception, s: dprint("Warning, sending '%s' failed (%s)." % (e, s)) if hasgobject: options.conn.close() else: if options.conn: options.conn.close() options.conn = False done = False if hasgobject: return True else: return done return True def server(host, port, skype = None): global options if ":" in host: sock = socket.socket(socket.AF_INET6) else: sock = socket.socket() sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) fcntl(sock, F_SETFD, FD_CLOEXEC); sock.bind((host, port)) sock.listen(1) if hasgobject: gobject.io_add_watch(sock, gobject.IO_IN, listener) else: dprint("Waiting for connection...") listener(sock, skype) def listener(sock, skype): global options if not hasgobject: if not(wait_for_lock(options.lock, 3, 10, "listener")): return False rawsock, addr = sock.accept() try: options.conn = ssl.wrap_socket(rawsock, server_side=True, certfile=options.config.sslcert, keyfile=options.config.sslkey, ssl_version=ssl.PROTOCOL_TLSv1) except (ssl.SSLError, socket.error) as err: if isinstance(err, ssl.SSLError): dprint("Warning, SSL init failed, did you create your certificate?") return False else: dprint('Warning, SSL init failed') return True if hasattr(options.conn, 'handshake'): try: options.conn.handshake() except Exception: if not hasgobject: options.lock.release() dprint("Warning, handshake failed, closing connection.") return False ret = 0 try: line = options.conn.recv(1024) if line.startswith("USERNAME") and line.split(' ')[1].strip() == options.config.username: ret += 1 line = options.conn.recv(1024) if line.startswith("PASSWORD") and hashlib.sha1(line.split(' ')[1].strip()).hexdigest() == options.config.password: ret += 1 except Exception, s: dprint("Warning, receiving 1024 bytes failed (%s)." % s) options.conn.close() if not hasgobject: options.conn = False options.lock.release() return False if ret == 2: dprint("Username and password OK.") options.conn.send("PASSWORD OK\n") if hasgobject: gobject.io_add_watch(options.conn, gobject.IO_IN, input_handler) else: options.lock.release() serverloop(options, skype) return True else: dprint("Username and/or password WRONG.") options.conn.send("PASSWORD KO\n") if not hasgobject: options.conn.close() options.conn = False options.lock.release() return False def dprint(msg): from time import strftime global options if options.debug: import inspect prefix = strftime("[%Y-%m-%d %H:%M:%S]") + " %s:%d" % inspect.stack()[1][1:3] sanitized = msg try: print prefix + ": " + msg except Exception, s: try: sanitized = msg.encode("ascii", "backslashreplace") except Error, s: try: sanitized = "hex [" + msg.encode("hex") + "]" except Error, s: sanitized = "[unable to print debug message]" print prefix + "~=" + sanitized if options.log: sock = open(options.log, "a") sock.write("%s: %s\n" % (prefix, sanitized)) sock.close() sys.stdout.flush() class MockedSkype: """Mock class for Skype4Py.Skype(), in case the -m option is used.""" def __init__(self, mock): sock = open(mock) self.lines = sock.readlines() def SendCommand(self, c): pass def Command(self, msg, Block): if msg == "PING": return ["PONG"] line = self.lines[0].strip() if not line.startswith(">> "): raise Exception("Corrupted mock input") line = line[3:] if line != msg: raise Exception("'%s' != '%s'" % (line, msg)) self.lines = self.lines[1:] # drop the expected incoming line ret = [] while True: # and now send back all the following lines, up to the next expected incoming line if len(self.lines) == 0: break if self.lines[0].startswith(">> "): break if not self.lines[0].startswith("<< "): raise Exception("Corrupted mock input") ret.append(self.lines[0][3:].strip()) self.lines = self.lines[1:] return ret class SkypeApi: def __init__(self, mock): global options if not mock: self.skype = Skype4Py.Skype() self.skype.OnNotify = self.recv if not options.dont_start_skype: self.skype.Client.Start() else: self.skype = MockedSkype(mock) def recv(self, msg_text): global options if msg_text == "PONG": return if "\n" in msg_text: # crappy skype prefixes only the first line for # multiline messages so we need to do so for the other # lines, too. this is something like: # 'CHATMESSAGE id BODY first line\nsecond line' -> # 'CHATMESSAGE id BODY first line\nCHATMESSAGE id BODY second line' prefix = " ".join(msg_text.split(" ")[:3]) msg_text = ["%s %s" % (prefix, i) for i in " ".join(msg_text.split(" ")[3:]).split("\n")] else: msg_text = [msg_text] for i in msg_text: try: # Internally, BitlBee always uses UTF-8 and encodes/decodes as # necessary to communicate with the IRC client; thus send the # UTF-8 it expects e = i.encode('UTF-8') except: # Should never happen, but it's better to send difficult to # read data than crash because some message couldn't be encoded e = i.encode('ascii', 'backslashreplace') if options.conn: dprint('<< ' + e) try: send(options.conn, e + "\n") except Exception, s: dprint("Warning, sending '%s' failed (%s)." % (e, s)) if options.conn: options.conn.close() options.conn = False else: dprint('-- ' + e) def send(self, msg_text): if not len(msg_text) or msg_text == "PONG": if msg_text == "PONG": options.last_bitlbee_pong = time.time() return try: # Internally, BitlBee always uses UTF-8 and encodes/decodes as # necessary to communicate with the IRC client; thus decode the # UTF-8 it sent us e = msg_text.decode('UTF-8') except: # Should never happen, but it's better to send difficult to read # data to Skype than to crash e = msg_text.decode('ascii', 'backslashreplace') dprint('>> ' + e) try: c = self.skype.Command(e, Block=True) self.skype.SendCommand(c) if hasattr(c, "Reply"): self.recv(c.Reply) # Skype4Py answer else: for i in c: # mock may return multiple iterable answers self.recv(i) except Skype4Py.SkypeError: pass except Skype4Py.SkypeAPIError, s: dprint("Warning, sending '%s' failed (%s)." % (e, s)) def serverloop(options, skype): timeout = 1; # in seconds skype_ping_period = 5 bitlbee_ping_period = 10 bitlbee_pong_timeout = 30 now = time.time() skype_ping_start_time = now bitlbee_ping_start_time = now options.last_bitlbee_pong = now in_error = [] handler_ok = True while (len(in_error) == 0) and handler_ok and options.conn: ready_to_read, ready_to_write, in_error = \ select.select([options.conn], [], [options.conn], \ timeout) now = time.time() handler_ok = len(in_error) == 0 if (len(ready_to_read) == 1) and handler_ok: handler_ok = input_handler(ready_to_read.pop()) # don't ping bitlbee/skype if they already received data now = time.time() # allow for the input_handler to take some time bitlbee_ping_start_time = now skype_ping_start_time = now options.last_bitlbee_pong = now if (now - skype_ping_period > skype_ping_start_time) and handler_ok: handler_ok = skype_idle_handler(skype) skype_ping_start_time = now if now - bitlbee_ping_period > bitlbee_ping_start_time: handler_ok = bitlbee_idle_handler(skype) bitlbee_ping_start_time = now if options.last_bitlbee_pong: if (now - options.last_bitlbee_pong) > bitlbee_pong_timeout: dprint("Bitlbee pong timeout") # TODO is following line necessary? Should there be a options.conn.unwrap() somewhere? # options.conn.shutdown() if options.conn: options.conn.close() options.conn = False else: options.last_bitlbee_pong = now def main(args=None): global options global skype cfgpath = os.path.join(os.environ['HOME'], ".skyped", "skyped.conf") syscfgpath = "/usr/local/etc/skyped/skyped.conf" if not os.path.exists(cfgpath) and os.path.exists(syscfgpath): cfgpath = syscfgpath # fall back to system-wide settings port = 2727 import argparse parser = argparse.ArgumentParser() parser.add_argument('-c', '--config', metavar='path', default=cfgpath, help='path to configuration file (default: %(default)s)') parser.add_argument('-H', '--host', default='0.0.0.0', help='set the tcp host, supports IPv4 and IPv6 (default: %(default)s)') parser.add_argument('-p', '--port', type=int, help='set the tcp port (default: %(default)s)') parser.add_argument('-l', '--log', metavar='path', help='set the log file in background mode (default: none)') parser.add_argument('-v', '--version', action='store_true', help='display version information') parser.add_argument('-n', '--nofork', action='store_true', help="don't run as daemon in the background") parser.add_argument('-s', '--dont-start-skype', action='store_true', help="assume that skype is running independently, don't try to start/stop it") parser.add_argument('-m', '--mock', help='fake interactions with skype (only useful for tests)') parser.add_argument('-d', '--debug', action='store_true', help='enable debug messages') options = parser.parse_args(sys.argv[1:] if args is None else args) if options.version: print "skyped %s" % __version__ sys.exit(0) # well, this is a bit hackish. we store the socket of the last connected client # here and notify it. maybe later notify all connected clients? options.conn = None # this will be read first by the input handler options.buf = None if not os.path.exists(options.config): parser.error(( "Can't find configuration file at '%s'. " "Use the -c option to specify an alternate one." )% options.config) cfgpath = options.config options.config = ConfigParser() options.config.read(cfgpath) options.config.username = options.config.get('skyped', 'username').split('#', 1)[0] options.config.password = options.config.get('skyped', 'password').split('#', 1)[0] options.config.sslkey = os.path.expanduser(options.config.get('skyped', 'key').split('#', 1)[0]) options.config.sslcert = os.path.expanduser(options.config.get('skyped', 'cert').split('#', 1)[0]) # hack: we have to parse the parameters first to locate the # config file but the -p option should overwrite the value from # the config file try: options.config.port = int(options.config.get('skyped', 'port').split('#', 1)[0]) if not options.port: options.port = options.config.port except NoOptionError: pass if not options.port: options.port = port dprint("Parsing config file '%s' done, username is '%s'." % (cfgpath, options.config.username)) if not options.nofork: pid = os.fork() if pid == 0: nullin = file(os.devnull, 'r') nullout = file(os.devnull, 'w') os.dup2(nullin.fileno(), sys.stdin.fileno()) os.dup2(nullout.fileno(), sys.stdout.fileno()) os.dup2(nullout.fileno(), sys.stderr.fileno()) else: print 'skyped is started on port %s, pid: %d' % (options.port, pid) sys.exit(0) else: dprint('skyped is started on port %s' % options.port) if hasgobject: server(options.host, options.port) try: skype = SkypeApi(options.mock) except Skype4Py.SkypeAPIError, s: sys.exit("%s. Are you sure you have started Skype?" % s) if hasgobject: gobject.timeout_add(2000, skype_idle_handler, skype) gobject.timeout_add(60000, bitlbee_idle_handler, skype) gobject.MainLoop().run() else: while 1: options.conn = False options.lock = threading.Lock() server(options.host, options.port, skype) if __name__ == '__main__': main() bitlbee-3.2.1/protocols/skype/NEWS0000644000175000017500000001634412245474076016404 0ustar wilmerwilmerVERSION DESCRIPTION ----------------------------------------------------------------------------- 0.9.0 - merge support for building the plugin on OpenBSD - merge support for running skyped without gobject and pygnutls/pyopenssl - as a side effect this adds Windows support - add /ctcp call|hangup support (you need BitlBee from bzr to use this) - add group support (see http://wiki.bitlbee.org/UiFix) 0.8.4 - now using python2.7 directly in case python would point to python3k - merge patch to avoid a crash when failing to connect to skyped - merge support for building the plugin on NetBSD - merge Debian patches 0.8.3 - support for BitlBee 1.3dev - fixed --debug switch (-d was fine) - documentation fixes 0.8.2 - building documentation is now optional - new settings: test_join and show_moods - '~' in skyped.conf is now expanded to the user's home directory - groupchat channel names are now persistent (requires BitlBee-1.2.6) 0.8.1 - support for BitlBee 1.2.5 - support for Skype 2.1.0.81 and Skype4Py 1.0.32.0 - the plugin part now supports FreeBSD - fix for edited messages, the prefix can now be configured 0.8.0 - fix build on x86_64 (-fPIC usage) - debug messages now have a timestamp - more work on having the default config under ~/.skyped - added a manual page for skyped 0.7.2 - add --log option to skyped to allow logging while it the daemon is in the background. - prefer config files from ~/.skyped over /etc/skyped - handle the case when LANG and LC_ALL env vars are empty 0.7.1 - mostly internal changes, the monster read callback is now replaced by tiny parser functions 0.7.0 - made 'make config' more portable - add 'skypeconsole' buddy for debugging purposes - support autojoin for bookmarked groupchats - skyped: make hardwired '/dev/null' portable and fix Python-2.6 warnings 0.6.3 - various osx-specific improvements (see the new screenshot!) - added python-gnutls install instructions - bitlbee.pc is now searched under /usr/local/lib/pkgconfig by default to help LFS monkeys ;) 0.6.2 - bugfix: make install required the plugin even in case its build was disabled 0.6.1 - added keepalive traffic to avoid disconnects in bitlbee when there is no traffic for a long time - now the plugin or skyped is automatically disabled if the dependencies are not available; useful in case the plugin is to be installed on a public server, or the skyped is to be used with a public server only 0.6.0 - works with BitlBee 1.2.1 0.5.1 - configure now automatically detects the right prefix to match witl BitlBee's one - minor documentation improvements (public chats, bug reporting address) 0.5.0 - skyped now uses gnutls if possible, which seem to be more stable, compared to openssl. - skyped now tries to handle all read/write errors from/to clients, and always just warn about failures, never exit. - installation for Debian users should be more simple - improved documentation - this .0 release should be quite stable, only about 100 lines of new code 0.4.2 - skyped should be now more responsive - new skypeout_offline setting for hiding/showing SkypeOut contacts - support for SkypeOut calls - support for querying the balance from Skype - all setting should be documented now 0.4.1 - support for building the plugin on Mac OSX - tested with BitlBee 1.2 and Skype 2.0.0.63 - avoid ${prefix} (by autoconf) in the config file as we don't handle such a variable - now you can call echo123 (patch by Riskó Gergely) 0.4.0 - support for starting, accepting and rejecting calls - also updated documentation (the key is the account set skype/call command) - as usual with the .0 releases, be careful, ~200 lines of new code 0.3.2 - support for Skype 2.0.0.43 - skyped now automatically starts/shuts down skype - improved 'make prepare' to handle more automake versions - documentation improvements 0.3.1 - beautify output when skyped is interrupted using ^C - 'nick skype foo' now really sets display name, not the mood text - documentation fixups - this version should be again as stable as 0.2.6 was 0.3.0 - authentication support in skyped via ssl - ~200 lines of new code, so be careful :) - upgraders: please read the documentation about how to set up your config the ssl certificate, this was no necessary till now 0.2.6 - the server setting has a default value, 'localhost' so in most cases you no longer have to set it explicitly - support for receiving emoted messages, ie. when the user types '/me foo' - support for setting the display name (nick 0 "foo bar") - it sets the mood text 0.2.5 - now bitlbee's info command is supported (it displays full name, birthday, homepage, age, etc.) 0.2.4 - improve documentation based on feedback from people on #bitlbee - fixed for Skype4Py >= 0.9.28.4 - tested with latest Skype beta, too (the one which supports video) 0.2.3 - fixed that annoying "creating groupchat failed" warning 0.2.2 - don't change the topic if skype does not report a successful topic change - fixed for the recent bitlbee API changes 0.2.1 - topic support in group chats - bugfixes for multiline messages - this version should be again as stable as 0.1.4 was 0.2.0 - group chat support - ~300 lines of new code, so be careful :) - the version number mentions that this is not a minor change 0.1.4 - documentation: mention the version of all deps (requirements section) - fix sending / sending accents - don't use internal functions of skype4py - skyped no longer dies when skype is killed 0.1.3 - support for edited messages - ignore empty messages (skype does the same) - support for multiline messages - switch to the x11 api instead of dbus (it's much more stable) 0.1.2 - notification when a new call arrives in - more documentation (vnc) - first release which works with unpatched bitlbee 0.1.1 - skyped now runs as daemon in the background by default - skyped now automatically reconnects on Skype restarts 0.1.0 - initial release - see README for major features bitlbee-3.2.1/protocols/skype/t/0000755000175000017500000000000012245474076016140 5ustar wilmerwilmerbitlbee-3.2.1/protocols/skype/t/filetransfer-bitlbee.mock0000644000175000017500000000067612245474076023114 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee >> PRIVMSG &bitlbee :skype - The user bob offered a new file for you. >> PRIVMSG &bitlbee :skype - File transfer from user bob started, saving to /home/alice/text.odt. >> PRIVMSG &bitlbee :skype - File transfer from user bob completed. bitlbee-3.2.1/protocols/skype/t/groupchat-leave-bitlbee.mock0000644000175000017500000000070612245474076023510 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype set skypeconsole_receive true << PRIVMSG &bitlbee :account skype on >> JOIN :##cecil/$bob;4d8cc9965791 >> 353 alice = ##cecil/$bob;4d8cc9965791 :@alice bob cecil @root << PART ##cecil/$bob;4d8cc9965791 >> PRIVMSG &bitlbee :alice: CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS UNSUBSCRIBED bitlbee-3.2.1/protocols/skype/t/group-add-skyped.mock0000644000175000017500000000212712245474076022174 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 70, 71 >> SEARCH FRIENDS << USERS echo123, bob, cecil, daniel, emily >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET GROUP 70 DISPLAYNAME << GROUP 70 DISPLAYNAME Family >> GET GROUP 70 USERS << GROUP 70 USERS bob, cecil >> GET GROUP 71 DISPLAYNAME << GROUP 71 DISPLAYNAME Work >> GET GROUP 71 USERS << GROUP 71 USERS daniel, emily >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS ONLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil >> GET USER daniel ONLINESTATUS << USER daniel ONLINESTATUS ONLINE >> GET USER daniel FULLNAME << USER daniel FULLNAME Daniel >> GET USER emily ONLINESTATUS << USER emily ONLINESTATUS OFFLINE >> GET USER emily FULLNAME << USER emily FULLNAME Emily >> ALTER GROUP 70 ADDUSER daniel << ALTER GROUP 70 ADDUSER daniel bitlbee-3.2.1/protocols/skype/t/call-failed-skyped.mock0000644000175000017500000000105312245474076022444 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS OFFLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> CALL bob << CALL 216 STATUS UNPLACED << CALL 216 STATUS ROUTING << CALL 216 FAILUREREASON 3 << CALL 216 STATUS FAILED bitlbee-3.2.1/protocols/skype/t/call-skyped.mock0000644000175000017500000000131112245474076021217 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS OFFLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> CALL bob << CALL 178 STATUS UNPLACED << CALL 178 STATUS ROUTING << CALL 178 STATUS RINGING >> GET CALL 178 PARTNER_HANDLE << CALL 178 PARTNER_HANDLE bob >> SET CALL 178 STATUS FINISHED << CALL 178 STATUS CANCELLED >> GET CALL 178 PARTNER_HANDLE << CALL 178 PARTNER_HANDLE bob bitlbee-3.2.1/protocols/skype/t/called-yes-bitlbee.mock0000644000175000017500000000054512245474076022445 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - New request: The user bob is currently ringing you. << PRIVMSG &bitlbee :yes >> PRIVMSG &bitlbee :skype - Accepted: The user bob is currently ringing you. bitlbee-3.2.1/protocols/skype/t/info-bitlbee.mock0000644000175000017500000000042412245474076021352 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee << PRIVMSG &bitlbee :info bob >> PRIVMSG &bitlbee :Full Name: Bob bitlbee-3.2.1/protocols/skype/t/groupchat-leave-skyped.mock0000644000175000017500000000474012245474076023403 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob, cecil >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS OFFLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS OFFLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 NAME #cecil/$bob;4d8cc9965791c6b9 << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMEMBER 186 ROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MYROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MEMBERS bob cecil alice << CHAT #cecil/$bob;4d8cc9965791c6b9 FRIENDLYNAME bob, cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob alice << CHAT #cecil/$bob;4d8cc9965791c6b9 TIMESTAMP 1358276196 << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMESSAGE 188 STATUS RECEIVED >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHATMESSAGE 188 FROM_HANDLE << CHATMESSAGE 188 FROM_HANDLE bob >> GET CHATMESSAGE 188 BODY << CHATMESSAGE 188 BODY >> GET CHATMESSAGE 188 TYPE << CHATMESSAGE 188 TYPE ADDEDMEMBERS >> GET CHATMESSAGE 188 CHATNAME << CHATMESSAGE 188 CHATNAME #cecil/$bob;4d8cc9965791c6b9 << CHATMESSAGE 189 STATUS READ << CHATMESSAGE 189 STATUS READ << CHATMEMBER 186 IS_ACTIVE TRUE << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob cecil alice << CHATMESSAGE 190 STATUS SENT >> ALTER CHAT #cecil/$bob;4d8cc9965791c6b9 LEAVE << ALTER CHAT LEAVE << CHAT #cecil/$bob;4d8cc9965791c6b9 MEMBERS bob cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS UNSUBSCRIBED bitlbee-3.2.1/protocols/skype/t/groupchat-invited-bitlbee.mock0000644000175000017500000000042112245474076024050 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> JOIN :##cecil/$bob;4d8cc9965791 >> 353 alice = ##cecil/$bob;4d8cc9965791 :@alice bob cecil @root bitlbee-3.2.1/protocols/skype/t/ctcp-help-skyped.mock0000644000175000017500000000066112245474076022172 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob bitlbee-3.2.1/protocols/skype/t/away-set-bitlbee.mock0000644000175000017500000000053312245474076022152 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype set skypeconsole_receive true << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - Logging in: Logged in << AWAY :work >> PRIVMSG &bitlbee :alice: USERSTATUS AWAY bitlbee-3.2.1/protocols/skype/t/added-yes-skyped.mock0000644000175000017500000000137012245474076022150 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123 >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service << USER bob RECEIVEDAUTHREQUEST Please allow me to see when you are online >> SET USER bob ISAUTHORIZED TRUE << USER bob ISAUTHORIZED TRUE << USER bob RECEIVEDAUTHREQUEST << USER bob ISAUTHORIZED TRUE << USER bob ISBLOCKED FALSE << USER bob BUDDYSTATUS 3 << USER bob ONLINESTATUS OFFLINE << USER bob ONLINESTATUS ONLINE << USER bob TIMEZONE 90000 << USER bob FULLNAME Miklos V << USER bob LANGUAGE hu Hungarian << USER bob COUNTRY hu Hungary bitlbee-3.2.1/protocols/skype/t/away-set-skyped.mock0000644000175000017500000000101612245474076022040 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> SET USERSTATUS AWAY << USERSTATUS AWAY << USER alice ONLINESTATUS AWAY << USERSTATUS AWAY bitlbee-3.2.1/protocols/skype/t/msg-bitlbee.mock0000644000175000017500000000044312245474076021206 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee << PRIVMSG &bitlbee :bob: foo >> :bob!bob@skype.com PRIVMSG &bitlbee :alice: bar bitlbee-3.2.1/protocols/skype/t/info-skyped.mock0000644000175000017500000000232712245474076021247 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER bob PHONE_HOME << USER bob PHONE_HOME >> GET USER bob PHONE_OFFICE << USER bob PHONE_OFFICE >> GET USER bob PHONE_MOBILE << USER bob PHONE_MOBILE >> GET USER bob NROF_AUTHED_BUDDIES << USER bob NROF_AUTHED_BUDDIES 145 >> GET USER bob TIMEZONE << USER bob TIMEZONE 90000 >> GET USER bob LASTONLINETIMESTAMP << USER bob LASTONLINETIMESTAMP 1358023469 >> GET USER bob SEX << USER bob SEX MALE >> GET USER bob LANGUAGE << USER bob LANGUAGE hu Hungarian >> GET USER bob COUNTRY << USER bob COUNTRY hu Hungary >> GET USER bob PROVINCE << USER bob PROVINCE >> GET USER bob CITY << USER bob CITY Budapest >> GET USER bob HOMEPAGE << USER bob HOMEPAGE >> GET USER bob ABOUT << USER bob ABOUT >> GET USER bob BIRTHDAY << USER bob BIRTHDAY 19781108 bitlbee-3.2.1/protocols/skype/t/ctcp-help-bitlbee.mock0000644000175000017500000000052512245474076022300 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee << PRIVMSG bob :HELP >> :bob!bob@skype.com NOTICE alice :HELP Supported CTCPs: HANGUP (Hang up a call), CALL (Initiate a call) bitlbee-3.2.1/protocols/skype/t/groupchat-topic-bitlbee.mock0000644000175000017500000000054512245474076023533 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> JOIN :##cecil/$bob;4d8cc9965791 >> 353 alice = ##cecil/$bob;4d8cc9965791 :@alice bob cecil @root << TOPIC ##cecil/$bob;4d8cc9965791 :topic >> TOPIC ##cecil/$bob;4d8cc9965791 :topic bitlbee-3.2.1/protocols/skype/t/group-read-bitlbee.mock0000644000175000017500000000073012245474076022464 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype set read_groups true << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee >> :cecil!cecil@skype.com JOIN :&bitlbee >> :daniel!daniel@skype.com JOIN :&bitlbee << JOIN &family >> 353 alice = &family :@alice +bob +cecil @root << JOIN &work >> 353 alice = &work :@alice +daniel @root bitlbee-3.2.1/protocols/skype/t/login-skyped.mock0000644000175000017500000000066112245474076021423 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob bitlbee-3.2.1/protocols/skype/t/called-no-bitlbee.mock0000644000175000017500000000064512245474076022262 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - New request: The user bob is currently ringing you. << PRIVMSG &bitlbee :no >> PRIVMSG &bitlbee :skype - Rejected: The user bob is currently ringing you. >> PRIVMSG &bitlbee :skype - You refused the call from user bob. bitlbee-3.2.1/protocols/skype/t/groupchat-invite-bitlbee.mock0000644000175000017500000000061512245474076023711 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee << PRIVMSG &bitlbee :chat with bob >> 353 alice = ##alice/$bob;a7ab206ec780 :@alice bob @root << INVITE cecil ##alice/$bob;a7ab206ec780 >> cecil@skype.com JOIN :##alice/$bob;a7ab206ec780 bitlbee-3.2.1/protocols/skype/t/add-yes-skyped.mock0000644000175000017500000000122712245474076021640 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123 >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> SET USER bob BUDDYSTATUS 2 Please authorize me << USER bob BUDDYSTATUS 2 << USER bob ISAUTHORIZED TRUE << USER bob ISBLOCKED FALSE << USER bob BUDDYSTATUS 2 << USER bob ONLINESTATUS OFFLINE << USER bob FULLNAME skype test203 << USER bob BUDDYSTATUS 3 << USER bob ONLINESTATUS OFFLINE << USER bob ONLINESTATUS ONLINE << USER bob TIMEZONE 90000 bitlbee-3.2.1/protocols/skype/t/added-no-skyped.mock0000644000175000017500000000071212245474076021763 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123 >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service << USER bob RECEIVEDAUTHREQUEST Please allow me to see when you are online >> SET USER bob ISAUTHORIZED FALSE << USER bob ISAUTHORIZED FALSE bitlbee-3.2.1/protocols/skype/t/called-no-skyped.mock0000644000175000017500000000123112245474076022143 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob << CALL 212 CONF_ID 0 << CALL 212 STATUS RINGING >> GET CALL 212 PARTNER_HANDLE << CALL 212 PARTNER_HANDLE bob >> SET CALL 212 STATUS FINISHED << CALL 212 STATUS REFUSED >> GET CALL 212 PARTNER_HANDLE << CALL 212 PARTNER_HANDLE bob bitlbee-3.2.1/protocols/skype/t/added-no-bitlbee.mock0000644000175000017500000000046312245474076022075 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - New request: The user bob wants to add you << PRIVMSG &bitlbee :no >> PRIVMSG &bitlbee :skype - Rejected bitlbee-3.2.1/protocols/skype/t/filetransfer-skyped.mock0000644000175000017500000000233112245474076022773 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob << FILETRANSFER 208 TYPE INCOMING << FILETRANSFER 208 PARTNER_HANDLE bob << FILETRANSFER 208 PARTNER_DISPNAME bob << FILETRANSFER 208 FILENAME text.odt << FILETRANSFER 208 STATUS NEW << FILETRANSFER 208 FILESIZE 83534193 << FILETRANSFER 208 STARTTIME 1358458276 << FILETRANSFER 208 FINISHTIME 0 << FILETRANSFER 208 BYTESPERSECOND 0 << FILETRANSFER 208 BYTESTRANSFERRED 0 << FILETRANSFER 208 FILESIZE 83534193 >> GET FILETRANSFER 208 PARTNER_HANDLE << FILETRANSFER 208 PARTNER_HANDLE bob << FILETRANSFER 208 FILEPATH /home/alice/text.odt << FILETRANSFER 208 STATUS CONNECTING << FILETRANSFER 208 STATUS TRANSFERRING >> GET FILETRANSFER 208 PARTNER_HANDLE << FILETRANSFER 208 PARTNER_HANDLE bob << FILETRANSFER 208 STATUS COMPLETED >> GET FILETRANSFER 208 PARTNER_HANDLE << FILETRANSFER 208 PARTNER_HANDLE bob bitlbee-3.2.1/protocols/skype/t/msg-skyped.mock0000644000175000017500000000356412245474076021106 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> MESSAGE bob foo << CHATMESSAGE 290 STATUS SENDING << CHAT #alice/$bob;ea753190f0a3e49b NAME #alice/$bob;ea753190f0a3e49b << CHAT #alice/$bob;ea753190f0a3e49b STATUS DIALOG << CHATMEMBER 287 ROLE USER << CHAT #alice/$bob;ea753190f0a3e49b MYROLE USER << CHAT #alice/$bob;ea753190f0a3e49b MEMBERS alice bob << CHAT #alice/$bob;ea753190f0a3e49b ACTIVEMEMBERS alice << CHAT #alice/$bob;ea753190f0a3e49b STATUS DIALOG << CHAT #alice/$bob;ea753190f0a3e49b TIMESTAMP 1357987847 << CHAT #alice/$bob;ea753190f0a3e49b DIALOG_PARTNER bob << CHAT #alice/$bob;ea753190f0a3e49b MEMBERS alice bob << CHAT #alice/$bob;ea753190f0a3e49b FRIENDLYNAME bob | foo << CHAT #alice/$bob;ea753190f0a3e49b POSTERS alice << CHAT #alice/$bob;ea753190f0a3e49b ACTIVITY_TIMESTAMP 1357987847 << CHAT #alice/$bob;ea753190f0a3e49b FRIENDLYNAME bob | foo << CHATMESSAGE 289 STATUS SENDING << CHATMESSAGE 290 STATUS SENDING << CHATMESSAGE 289 STATUS SENT << CHATMESSAGE 290 STATUS SENT << CHATMEMBER 288 IS_ACTIVE TRUE << CHAT #alice/$bob;ea753190f0a3e49b ACTIVEMEMBERS alice bob << CHAT #alice/$bob;ea753190f0a3e49b POSTERS alice bob << CHAT #alice/$bob;ea753190f0a3e49b ACTIVITY_TIMESTAMP 1357987875 << CHATMESSAGE 293 STATUS RECEIVED >> GET CHATMESSAGE 293 FROM_HANDLE << CHATMESSAGE 293 FROM_HANDLE bob >> GET CHATMESSAGE 293 BODY << CHATMESSAGE 293 BODY bar >> GET CHATMESSAGE 293 TYPE << CHATMESSAGE 293 TYPE SAID >> GET CHATMESSAGE 293 CHATNAME << CHATMESSAGE 293 CHATNAME #alice/$bob;ea753190f0a3e49b bitlbee-3.2.1/protocols/skype/t/set-mood-text-skyped.mock0000644000175000017500000000057312245474076023026 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123 >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> SET PROFILE MOOD_TEXT foo bar << PROFILE MOOD_TEXT foo bar bitlbee-3.2.1/protocols/skype/t/call-bitlbee.mock0000644000175000017500000000061412245474076021333 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee << PRIVMSG bob :CALL >> PRIVMSG &bitlbee :skype - You are currently ringing the user bob. << PRIVMSG bob :HANGUP >> PRIVMSG &bitlbee :skype - You cancelled the call to the user bob. bitlbee-3.2.1/protocols/skype/t/called-yes-skyped.mock0000644000175000017500000000114012245474076022326 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob << CALL 208 CONF_ID 0 << CALL 208 STATUS RINGING >> GET CALL 208 PARTNER_HANDLE << CALL 208 PARTNER_HANDLE bob >> SET CALL 208 STATUS INPROGRESS << CALL 208 STATUS INPROGRESS bitlbee-3.2.1/protocols/skype/t/groupchat-invited-skyped.mock0000644000175000017500000000435612245474076023754 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob, cecil >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS OFFLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS OFFLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 NAME #cecil/$bob;4d8cc9965791c6b9 << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMEMBER 186 ROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MYROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MEMBERS bob cecil alice << CHAT #cecil/$bob;4d8cc9965791c6b9 FRIENDLYNAME bob, cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob alice << CHAT #cecil/$bob;4d8cc9965791c6b9 TIMESTAMP 1358276196 << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMESSAGE 188 STATUS RECEIVED >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHATMESSAGE 188 FROM_HANDLE << CHATMESSAGE 188 FROM_HANDLE bob >> GET CHATMESSAGE 188 BODY << CHATMESSAGE 188 BODY >> GET CHATMESSAGE 188 TYPE << CHATMESSAGE 188 TYPE ADDEDMEMBERS >> GET CHATMESSAGE 188 CHATNAME << CHATMESSAGE 188 CHATNAME #cecil/$bob;4d8cc9965791c6b9 << CHATMESSAGE 189 STATUS READ << CHATMESSAGE 189 STATUS READ << CHATMEMBER 186 IS_ACTIVE TRUE << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob cecil alice << CHATMESSAGE 190 STATUS SENT bitlbee-3.2.1/protocols/skype/t/call-failed-bitlbee.mock0000644000175000017500000000047012245474076022555 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - Logging in: Logged in << PRIVMSG bob :CALL >> PRIVMSG &bitlbee :skype - Error: Call failed: User is offline bitlbee-3.2.1/protocols/skype/t/groupchat-topic-skyped.mock0000644000175000017500000000453112245474076023423 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob, cecil >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS OFFLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS OFFLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 NAME #cecil/$bob;4d8cc9965791c6b9 << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMEMBER 186 ROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MYROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MEMBERS bob cecil alice << CHAT #cecil/$bob;4d8cc9965791c6b9 FRIENDLYNAME bob, cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob alice << CHAT #cecil/$bob;4d8cc9965791c6b9 TIMESTAMP 1358276196 << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMESSAGE 188 STATUS RECEIVED >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHATMESSAGE 188 FROM_HANDLE << CHATMESSAGE 188 FROM_HANDLE bob >> GET CHATMESSAGE 188 BODY << CHATMESSAGE 188 BODY >> GET CHATMESSAGE 188 TYPE << CHATMESSAGE 188 TYPE ADDEDMEMBERS >> GET CHATMESSAGE 188 CHATNAME << CHATMESSAGE 188 CHATNAME #cecil/$bob;4d8cc9965791c6b9 << CHATMESSAGE 189 STATUS READ << CHATMESSAGE 189 STATUS READ << CHATMEMBER 186 IS_ACTIVE TRUE << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob cecil alice << CHATMESSAGE 190 STATUS SENT >> ALTER CHAT #cecil/$bob;4d8cc9965791c6b9 SETTOPIC topic << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC topic bitlbee-3.2.1/protocols/skype/t/login-bitlbee.mock0000644000175000017500000000034012245474076021524 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - Logging in: Logged in bitlbee-3.2.1/protocols/skype/t/group-read-skyped.mock0000644000175000017500000000202512245474076022354 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 70, 71 >> SEARCH FRIENDS << USERS echo123, bob, cecil, daniel, emily >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET GROUP 70 DISPLAYNAME << GROUP 70 DISPLAYNAME Family >> GET GROUP 70 USERS << GROUP 70 USERS bob, cecil >> GET GROUP 71 DISPLAYNAME << GROUP 71 DISPLAYNAME Work >> GET GROUP 71 USERS << GROUP 71 USERS daniel, emily >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS ONLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil >> GET USER daniel ONLINESTATUS << USER daniel ONLINESTATUS ONLINE >> GET USER daniel FULLNAME << USER daniel FULLNAME Daniel >> GET USER emily ONLINESTATUS << USER emily ONLINESTATUS OFFLINE >> GET USER emily FULLNAME << USER emily FULLNAME Emily bitlbee-3.2.1/protocols/skype/t/group-add-bitlbee.mock0000644000175000017500000000074212245474076022304 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype set read_groups true << PRIVMSG &bitlbee :account skype on >> :bob!bob@skype.com JOIN :&bitlbee >> :cecil!cecil@skype.com JOIN :&bitlbee >> :daniel!daniel@skype.com JOIN :&bitlbee << JOIN &family >> 353 alice = &family :@alice +bob +cecil @root << INVITE daniel &family >> :daniel!daniel@skype.com JOIN :&family bitlbee-3.2.1/protocols/skype/t/added-yes-bitlbee.mock0000644000175000017500000000046312245474076022261 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on >> PRIVMSG &bitlbee :skype - New request: The user bob wants to add you << PRIVMSG &bitlbee :yes >> :bob!bob@skype.com JOIN :&bitlbee bitlbee-3.2.1/protocols/skype/t/set-mood-text-bitlbee.mock0000644000175000017500000000053712245474076023135 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype set skypeconsole_receive true << PRIVMSG &bitlbee :account skype on << PRIVMSG &bitlbee :account skype set mood_text "foo bar" >> PRIVMSG &bitlbee :alice: PROFILE MOOD_TEXT foo bar bitlbee-3.2.1/protocols/skype/t/groupchat-invite-skyped.mock0000644000175000017500000000357412245474076023611 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob, cecil >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS ONLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS ONLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil >> CHAT CREATE bob << CHAT #alice/$bob;a7ab206ec78060f1 STATUS DIALOG >> GET CHAT #alice/$bob;a7ab206ec78060f1 ADDER << CHAT #alice/$bob;a7ab206ec78060f1 ADDER << CHAT #alice/$bob;a7ab206ec78060f1 NAME #alice/$bob;a7ab206ec78060f1 >> GET CHAT #alice/$bob;a7ab206ec78060f1 TOPIC << CHAT #alice/$bob;a7ab206ec78060f1 TOPIC << CHATMESSAGE 206 STATUS SENDING << CHAT #alice/$bob;a7ab206ec78060f1 STATUS DIALOG << CHATMEMBER 204 ROLE USER << CHAT #alice/$bob;a7ab206ec78060f1 MYROLE USER << CHAT #alice/$bob;a7ab206ec78060f1 MEMBERS bob alice << CHAT #alice/$bob;a7ab206ec78060f1 ACTIVEMEMBERS alice << CHAT #alice/$bob;a7ab206ec78060f1 STATUS DIALOG << CHAT #alice/$bob;a7ab206ec78060f1 TIMESTAMP 1358344213 << CHAT #alice/$bob;a7ab206ec78060f1 DIALOG_PARTNER bob << CHAT #alice/$bob;a7ab206ec78060f1 MEMBERS bob alice << CHAT #alice/$bob;a7ab206ec78060f1 FRIENDLYNAME bob >> ALTER CHAT #alice/$bob;a7ab206ec78060f1 ADDMEMBERS cecil << ALTER CHAT ADDMEMBERS << CHAT #alice/$bob;a7ab206ec78060f1 STATUS MULTI_SUBSCRIBED << CHAT #alice/$bob;a7ab206ec78060f1 MEMBERS bob cecil alice >> GET CHAT #alice/$bob;a7ab206ec78060f1 ADDER << CHAT #alice/$bob;a7ab206ec78060f1 ADDER << CHAT #alice/$bob;a7ab206ec78060f1 FRIENDLYNAME bob, cecil >> GET CHAT #alice/$bob;a7ab206ec78060f1 TOPIC << CHAT #alice/$bob;a7ab206ec78060f1 TOPIC << CHATMESSAGE 210 STATUS SENDING bitlbee-3.2.1/protocols/skype/t/groupchat-msg-skyped.mock0000644000175000017500000000454312245474076023076 0ustar wilmerwilmer>> SEARCH GROUPS CUSTOM << GROUPS 48, 49 >> SEARCH FRIENDS << USERS echo123, bob, cecil >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> SET USERSTATUS ONLINE << USERSTATUS ONLINE >> GET USER echo123 ONLINESTATUS << USER echo123 ONLINESTATUS ONLINE >> GET USER echo123 FULLNAME << USER echo123 FULLNAME Echo / Sound Test Service >> GET USER bob ONLINESTATUS << USER bob ONLINESTATUS OFFLINE >> GET USER bob FULLNAME << USER bob FULLNAME Bob >> GET USER cecil ONLINESTATUS << USER cecil ONLINESTATUS OFFLINE >> GET USER cecil FULLNAME << USER cecil FULLNAME Cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 NAME #cecil/$bob;4d8cc9965791c6b9 << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMEMBER 186 ROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MYROLE USER << CHAT #cecil/$bob;4d8cc9965791c6b9 MEMBERS bob cecil alice << CHAT #cecil/$bob;4d8cc9965791c6b9 FRIENDLYNAME bob, cecil << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob alice << CHAT #cecil/$bob;4d8cc9965791c6b9 TIMESTAMP 1358276196 << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 STATUS MULTI_SUBSCRIBED << CHATMESSAGE 188 STATUS RECEIVED >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER << CHAT #cecil/$bob;4d8cc9965791c6b9 ADDER bob >> GET CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC << CHAT #cecil/$bob;4d8cc9965791c6b9 TOPIC >> GET CHATMESSAGE 188 FROM_HANDLE << CHATMESSAGE 188 FROM_HANDLE bob >> GET CHATMESSAGE 188 BODY << CHATMESSAGE 188 BODY >> GET CHATMESSAGE 188 TYPE << CHATMESSAGE 188 TYPE ADDEDMEMBERS >> GET CHATMESSAGE 188 CHATNAME << CHATMESSAGE 188 CHATNAME #cecil/$bob;4d8cc9965791c6b9 << CHATMESSAGE 189 STATUS READ << CHATMESSAGE 189 STATUS READ << CHATMEMBER 186 IS_ACTIVE TRUE << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVEMEMBERS bob cecil alice << CHATMESSAGE 190 STATUS SENT >> CHATMESSAGE #cecil/$bob;4d8cc9965791c6b9 hello << CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVITY_TIMESTAMP 1364652882 bitlbee-3.2.1/protocols/skype/t/add-yes-bitlbee.mock0000644000175000017500000000036512245474076021751 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype on << PRIVMSG &bitlbee :add skype bob >> :bob!bob@skype.com JOIN :&bitlbee bitlbee-3.2.1/protocols/skype/t/groupchat-msg-bitlbee.mock0000644000175000017500000000071712245474076023204 0ustar wilmerwilmer>> NOTICE AUTH << NICK alice << USER alice alice localhost :Alice >> PRIVMSG &bitlbee << PRIVMSG &bitlbee :account add skype alice foo << PRIVMSG &bitlbee :account skype set skypeconsole_receive true << PRIVMSG &bitlbee :account skype on >> JOIN :##cecil/$bob;4d8cc9965791 >> 353 alice = ##cecil/$bob;4d8cc9965791 :@alice bob cecil @root << PRIVMSG ##cecil/$bob;4d8cc9965791 :hello >> PRIVMSG &bitlbee :alice: CHAT #cecil/$bob;4d8cc9965791c6b9 ACTIVITY_TIMESTAMP bitlbee-3.2.1/protocols/skype/README0000644000175000017500000002271612245474076016565 0ustar wilmerwilmer= Skype plugin for BitlBee Miklos Vajna == Status [quote, Wilmer van der Gaast (author of BitlBee)] ____ Okay, this exists now, with lots of thanks to vmiklos for his *excellent* work!! It's not in the main BitlBee and it'll never be for various reasons, but because it's a plugin that shouldn't be a problem. ____ One day I browsed the BitlBee bugtracker and found http://bugs.bitlbee.org/bitlbee/ticket/82[this] ticket. Then after a while I returned and saw that it was still open. So I wrote it. It's pretty stable (one day I wanted to restart it because of an upgrade and just noticed it was running for 2+ months without crashing), I use it for my daily work. Being a plug-in, no patching is required, you can just install it after installing BitlBee itself. NOTE: You will see that this implementation of the Skype plug-in still requires a Skype instance to be running. This is because I'm not motivated to reverse engineer Skype's http://en.wikipedia.org/wiki/Skype_Protocol#Obfuscation_Layer[obfuscation layer]. (Not mentioning that you should ask your lawyer about if it is legal or not..) == Requirements * Skype >= 1.4.0.99. The latest version I've tested is 4.1.0.20. * BitlBee >= 3.0. The latest version I've tested is @BITLBEE_VERSION@. Use old versions (see the NEWS file about which one) if you have older BitlBee installed. * Skype4Py >= 0.9.28.7. Previous versions won't work due to API changes. The latest version I've tested is 1.0.32.0. * Python >= 2.5. Skype4Py does not work with 2.4. * OS: `bitlbee-skype` has been tested under Linux and Mac OS X. The plugin part has been tested under Free/Open/NetBSD as well. The daemon part has been tested on Windows, too. == How to set it up Before you start. The setup is the following: BitlBee can't connect directly to Skype servers (the company's ones). It needs a running Skype client to do so. In fact BitlBee will connect to `skyped` (a tcp server, provided in this package) and `skyped` will connect to to your Skype client. The benefit of this architecture is that you can run Skype and `skyped` on a machine different to the one where you run BitlBee (it can be even a public server) and/or your IRC client. NOTE: The order is important. First `skyped` starts Skype. Then `skyped` connects to Skype, finally BitlBee can connect to `skyped`. === Installing Either use your package manager to install the Skype plugin, using something like: ---- # apt-get install skyped bitlbee-plugin-skype ---- Or install http://sourceforge.net/projects/skype4py/[Skype4Py], and build BitlBee with `--skype=1`. === Configuring See the manpage of `skyped`. == Setting up Skype in a VNC server (optional) Optionally, if you want to run Skype on a server, you might want to setup up a `VNC` server as well. I used `tightvnc` but probably other `VNC` servers will work, too. First run ---- $ vncpasswd ~/.vnc/passwd ---- and create a password. You will need it at least once. Now create `~/.vnc/xstartup` with the following contents: ---- #!/bin/sh blackbox ---- Adjust the permissions: ---- $ chmod +x ~/.vnc/xstartup ---- Then start the server: ---- $ vncserver ---- Then connect to it, start an `xterm`, set up Skype (username, password, enable X11 API and allow the `Skype4Py` client), quit from Skype, and start `skyped`. If you want to watch its traffic, enable debug messages and foreground mode: ---- $ skyped -n -d ---- == Features - Download nicks and away statuses from Skype - Noticing joins / parts while we're connected - Sending messages - Receiving messages - Receiving away status changes - `skyped` (the tcp daemon that is a gateway between Skype and tcp) - Error handling when `skyped` is not running and when it exits - Marking received messages as seen so that Skype won't say there are unread messages - Adding / removing contacts - Set away state when you do a `/away`. - When you `account off`, Skype will set status to `Offline` - When you `account on`, Skype will set status to `Online` - Detect when somebody wants to add you and ask for confirmation - Detect when somebody wants to transfer a file - Group chat support: * Detect if we're invited * Send / receive group chat messages * Invite others (using `/invite `) * Part from group chats * Starting a group chat (using `/j #nick`) - Topic changes in group chats: * Show the current topic (if any) on join * Notice when someone changes the topic * Support changing the topic using `/topic` - Viewing the profile using the `info` command. - Handling skype actions (when the `CHATMESSAGE` has `EMOTED` type) - Setting your display name using the `nick` command. - Running Skype on a machine different to BitlBee is possible, the communication is encrypted. - Managing outgoing calls (with call duration at the end): * `/ctcp nick call` * `/ctcp nick hangup` - Managing outgoing SkypeOut or conference calls: * `account skype set call +18005551234` * `account skype set call nick1 nick2` * `account skype set -del call` - Managing incoming calls via questions, just like when you add / remove contacts. - Querying the current SkypeOut balance: * `account skype set balance query` - For debug purposes, it's possible to send any command to `skyped`. To achieve this, you need to: * `account skype set skypeconsole true` * then writing `skypeconsole: ` will work in the control channel. * `account skype set skypeconsole_receive true` will make the `skypeconsole` account dump all the recieved raw traffic for you - If you want to automatically join bookmarked groupchats right after you logged in, do: * `account skype set auto_join true` - Edited messages are shown with the `EDIT:` prefix. If you don't like this, you can set your own prefix using: * `account skype set edit_prefix "updated message:"` - The `echo123` test account is hidden by default. If you want to see it: * `account skype set test_join true` - Mood texts are not shown by default. * If you want to see them: `account skype set show_moods true` * If you want to change your mood text: `account skype set mood_text 'foo bar'` - Group support: * To enable: `account skype set read_groups true` * Skype groups are told to BitlBee * The usual `/invite` in a group channel adds the buddy to the group in skype as well (and if necessary, it creates a new group in Skype) == What needs to be done (aka. TODO) - Notice if foo invites bar. Currently you can see only that bar joined. - Public chats. See link:https://developer.skype.com/jira/browse/SCL-381[this feature request], this is because it is still not possible (under Linux) to `join_chat` to a public chat.. - Add yasrd (Yet Another Skype-Related Daemon) to allow using a public server for users who are behind NAT. == I would like to have support for ... If something does not work and it's not in the TODO section, then please contact me! Please also try the bzr version before reporting a bug, your problem may be already fixed there. In fact, of course, I wrote this documentation after figured out how to do this setup, so maybe I left out some steps. If you needed 'any' additional tricks, then it would be nice to include them here. == Known bugs - File transfers are view-only from BitlBee. Quoting the https://developer.skype.com/Docs/ApiDoc/FILETRANSFER_object[relevant documentation]: 'File transfers cannot be initiated nor accepted via API commands.' So it's not something I can add support for, sadly. == Screenshots You can reach some screenshots link:shot[here]. == Additional resources The Skype API documentation is http://developer.skype.com/resources/public_api_ref.zip[here] if you're interested. == Testimonials ---- 00:56 < scathe> I like your skype plugin :) ---- ---- It's really working great so far. Good Job and thank you! Sebastian ---- ---- Big respect for your work, i really appreciate it. Martin ---- ---- Thanks for bitlbee-skype. As a blind Linux user, I cannot use the skype GUI client because qt apps ar not accessible yet with the available screen readers. bitlbee-skype allows me to make use of skype without having to interact much with the GUI client, which helps me a lot. Lukas ---- ---- 02:12 < newton> i must say, i love this little bee ;) 02:15 < newton> tried it out today with the skype plugin, good work! ---- ---- 18:10 < miCSu> it works fine ---- ---- 13:56 < seo> i just want to thank you :) 13:56 < seo> for bitlbee-skype 13:57 < seo> it's working very well, so, again, thank you for your work, and for sharing it ---- ---- 22:16 < ecraven> vmiklos: thanks a lot for the skype plugin for bitlbee! ---- ---- I'm blind and so I have to use a screen reader, in my case Gnome-Orca. But since Skype is written in QT, while Orca uses gtk+, I have no direct access to the Skype interface. That's why I desided to use Skyped and Erc. The text console is fully accessible. Thank you very much. Hermann ---- ---- i love that bitlbeeplugin. big thx for that. michael ---- ---- 23:47 < krisfremen> thanks for creating this fabulous piece of software vmiklos :) ---- == Thanks to the following people: * people in link:AUTHORS[AUTHORS] for their contributions * Arkadiusz Wahlig, author of skype4py, for making suggestions to skyped * Gabor Adam Toth (tg), for noticing extra code is needed to handle multiline messages * Cristobal Palmer (tarheelcoxn), for helping to testing the plugin in a timezone different to mine * people on `#bitlbee` for feedback Back to my link:/projects[projects page]. // vim: ft=asciidoc bitlbee-3.2.1/protocols/skype/skyped.conf.dist0000644000175000017500000000053412245474076021007 0ustar wilmerwilmer[skyped] # change to your skype username username = john # use `echo -n foo|sha1sum` to generate this hash for your password password = 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 # you have to change the following paths to your home directory: cert = /home/YOUR_USER/.skyped/skyped.cert.pem key = /home/YOUR_USER/.skyped/skyped.key.pem port = 2727 bitlbee-3.2.1/protocols/skype/HACKING0000644000175000017500000000433312245474076016667 0ustar wilmerwilmer== Tabs I use the following tabs during the development: 1) bitlbee-skype: vim, make, etc. 2) bitlbee: gdb --args ./bitlbee -v -n -D run 3) skyped: python skyped.py -n -d 4) irssi == Tests The plugin is tested with a mocked IRC client and a mocked skyped. === Requirements Python pexpect module is required to run the tests. To run tests with bitlbee built in a development tree and not (the one) installed in the system (e.g. /usr), make sure to specify --plugindir= option to ./configure script during the build process: bitlbee% ./configure --skype=1 --plugindir="$(realpath .)" Otherwise bitlbee will try to load skype.so (among other things) from /usr/lib, which is probably not what you want to test, or produce "Unknown protocol" error. === Running Tests can be run by running test.py script in this ("protocols/skype") directory. For more control over how/which tests are being run from there, use "python -m unittest" command: bitlbee/protocols/skype% python -m unittest test bitlbee/protocols/skype% python -m unittest -f test bitlbee/protocols/skype% python -m unittest test.Test.testMsg If bitlbee crashes during tests with SIGSEGV (segmentation fault), it's likely that there is some problem with skype.c plugin. To get a backtrace of such crash, use: bitlbee/protocols/skype% ATTACH_GDB=true python -m unittest test.Test.testMsg Example shows running "testMsg" test with gdb attached to bitlbee, which will produce full backtrace in "t/gdb-.log" files (see pid in pexpect error output of the test). === Adding new tests To add a new test, the following steps are necessary: 1) Add a new -skyped.mock file: just do the test manually, copy&paste the skyped output and clean it up, so Alice talks to Bob. You can test the created mock file by starting skyped with the -m option, and testing it from an IRC client manually. 2) Add a new -bitlbee.mock file: do the test manually from irssi, and use: /connect -rawlog rawlog localhost Then clean up the rawlog: the input lines are parsed as matching patterns, so boring prefix/suffix text can be left out, non-interesting lines can be deleted. The output lines still have to be strict IRC commands, as usual. 3) Add the new test to test.py and run it! // vim: ft=asciidoc bitlbee-3.2.1/protocols/skype/test.py0000755000175000017500000000712512245474076017236 0ustar wilmerwilmer#!/usr/bin/env python2.7 import subprocess import sys import pexpect import unittest import shutil import os import hashlib def openssl(args): with open(os.devnull, "w") as devnull: proc = subprocess.Popen(['openssl'] + args, stdin=subprocess.PIPE, stderr=devnull) for i in range(6): proc.stdin.write("\n") proc.stdin.close() proc.communicate() def setupSkyped(): try: shutil.rmtree("t/skyped") except OSError: pass os.makedirs("t/skyped") cwd = os.getcwd() os.chdir("t/skyped") try: shutil.copyfile("../../skyped.cnf", "skyped.cnf") openssl(['req', '-new', '-x509', '-days', '365', '-nodes', '-config', 'skyped.cnf', '-out', 'skyped.cert.pem', '-keyout', 'skyped.key.pem']) with open("skyped.conf", "w") as sock: sock.write("[skyped]\n") sock.write("username = alice\n") sock.write("password = %s\n" % hashlib.sha1("foo").hexdigest()) sock.write("cert = %s/skyped.cert.pem\n" % os.getcwd()) sock.write("key = %s/skyped.key.pem\n" % os.getcwd()) sock.write("port = 2727\n") finally: os.chdir(cwd) class Test(unittest.TestCase): def mock(self, name): with open("t/skyped.log", "w") as skyped_log,\ open("t/pexpect.log", "w") as pexpect_log: skyped = subprocess.Popen([sys.executable, "skyped.py", "-c", "t/skyped/skyped.conf", "-n", "-d", "-m", "t/%s-skyped.mock" % name], stdout=skyped_log, stderr=subprocess.STDOUT) try: bitlbee = pexpect.spawn('../../bitlbee', ['-d', 't/bitlbee'], logfile=pexpect_log) if os.environ.get('ATTACH_GDB'): subprocess.Popen(['gdb', '-batch-silent', '-ex', 'set logging overwrite on', '-ex', 'set logging file t/gdb-%s.log' % bitlbee.pid, '-ex', 'set logging on', '-ex', 'handle all pass nostop noprint', '-ex', 'handle SIGSEGV pass stop print', '-ex', 'set pagination 0', '-ex', 'continue', '-ex', 'backtrace full', '-ex', 'info registers', '-ex', 'thread apply all backtrace', '-ex', 'quit', '../../bitlbee', str(bitlbee.pid) ]) bitlbee_mock = open("t/%s-bitlbee.mock" % name) for i in bitlbee_mock.readlines(): line = i.strip() if line.startswith(">> "): bitlbee.expect_exact(line[3:], timeout=10) elif line.startswith("<< "): bitlbee.sendline(line[3:]) bitlbee_mock.close() bitlbee.close() finally: skyped.terminate() skyped.communicate() def setUp(self): try: shutil.rmtree("t/bitlbee") except OSError: pass os.makedirs("t/bitlbee") def testMsg(self): self.mock("msg") def testLogin(self): self.mock("login") def testInfo(self): self.mock("info") def testCall(self): self.mock("call") def testCallFailed(self): self.mock("call-failed") def testAddYes(self): self.mock("add-yes") def testAddedYes(self): self.mock("added-yes") def testAddedNo(self): self.mock("added-no") def testGroupchatInvited(self): self.mock("groupchat-invited") def testGroupchatInvite(self): self.mock("groupchat-invite") def testGroupchatLeave(self): self.mock("groupchat-leave") def testGroupchatMsg(self): self.mock("groupchat-msg") def testGroupchatTopic(self): self.mock("groupchat-topic") def testCalledYes(self): self.mock("called-yes") def testCalledNo(self): self.mock("called-no") def testFiletransfer(self): self.mock("filetransfer") def testGroupRead(self): self.mock("group-read") def testGroupAdd(self): self.mock("group-add") def testCtcpHelp(self): self.mock("ctcp-help") def testSetMoodText(self): self.mock("set-mood-text") def testAwaySet(self): self.mock("away-set") if __name__ == '__main__': setupSkyped() unittest.main() bitlbee-3.2.1/protocols/skype/skype.c0000644000175000017500000012531612245474076017204 0ustar wilmerwilmer/* * skype.c - Skype plugin for BitlBee * * Copyright (c) 2007-2013 by Miklos Vajna * * 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 * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ #define _XOPEN_SOURCE #define _BSD_SOURCE #include #include #include #include #define SKYPE_DEFAULT_SERVER "localhost" #define SKYPE_DEFAULT_PORT "2727" #define IRC_LINE_SIZE 16384 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) /* * Enumerations */ enum { SKYPE_CALL_RINGING = 1, SKYPE_CALL_MISSED, SKYPE_CALL_CANCELLED, SKYPE_CALL_FINISHED, SKYPE_CALL_REFUSED }; enum { SKYPE_FILETRANSFER_NEW = 1, SKYPE_FILETRANSFER_TRANSFERRING, SKYPE_FILETRANSFER_COMPLETED, SKYPE_FILETRANSFER_FAILED }; /* * Structures */ struct skype_data { struct im_connection *ic; char *username; /* The effective file descriptor. We store it here so any function can * write() to it. */ int fd; /* File descriptor returned by bitlbee. we store it so we know when * we're connected and when we aren't. */ int bfd; /* ssl_getfd() uses this to get the file desciptor. */ void *ssl; /* When we receive a new message id, we query the properties, finally * the chatname. Store the properties here so that we can use * imcb_buddy_msg() when we got the chatname. */ char *handle; /* List, because of multiline messages. */ GList *body; char *type; /* This is necessary because we send a notification when we get the * handle. So we store the state here and then we can send a * notification about the handle is in a given status. */ int call_status; char *call_id; char *call_duration; /* If the call is outgoing or not */ int call_out; /* Same for file transfers. */ int filetransfer_status; /* Path of the file being transferred. */ char *filetransfer_path; /* Using /j #nick we want to have a groupchat with two people. Usually * not (default). */ char *groupchat_with; /* The user who invited us to the chat. */ char *adder; /* If we are waiting for a confirmation about we changed the topic. */ int topic_wait; /* These are used by the info command. */ char *info_fullname; char *info_phonehome; char *info_phoneoffice; char *info_phonemobile; char *info_nrbuddies; char *info_tz; char *info_seen; char *info_birthday; char *info_sex; char *info_language; char *info_country; char *info_province; char *info_city; char *info_homepage; char *info_about; /* When a call fails, we get the reason and later we get the failure * event, so store the failure code here till then */ int failurereason; /* If this is just an update of an already received message. */ int is_edit; /* List of struct skype_group* */ GList *groups; /* Pending user which has to be added to the next group which is * created. */ char *pending_user; /* If the info command was used, to determine what to do with FULLNAME result. */ int is_info; }; struct skype_away_state { char *code; char *full_name; }; struct skype_buddy_ask_data { struct im_connection *ic; /* This is also used for call IDs for simplicity */ char *handle; }; struct skype_group { int id; char *name; GList *users; }; /* * Tables */ const struct skype_away_state skype_away_state_list[] = { { "AWAY", "Away" }, { "NA", "Not available" }, { "DND", "Do Not Disturb" }, { "INVISIBLE", "Invisible" }, { "OFFLINE", "Offline" }, { "SKYPEME", "Skype Me" }, { "ONLINE", "Online" }, { NULL, NULL} }; /* * Functions */ int skype_write(struct im_connection *ic, char *buf, int len) { struct skype_data *sd = ic->proto_data; struct pollfd pfd[1]; if (!sd->ssl) return FALSE; pfd[0].fd = sd->fd; pfd[0].events = POLLOUT; /* This poll is necessary or we'll get a SIGPIPE when we write() to * sd->fd. */ poll(pfd, 1, 1000); if (pfd[0].revents & POLLHUP) { imc_logout(ic, TRUE); return FALSE; } ssl_write(sd->ssl, buf, len); return TRUE; } int skype_printf(struct im_connection *ic, char *fmt, ...) { va_list args; char str[IRC_LINE_SIZE]; va_start(args, fmt); vsnprintf(str, IRC_LINE_SIZE, fmt, args); va_end(args); return skype_write(ic, str, strlen(str)); } static void skype_buddy_ask_yes(void *data) { struct skype_buddy_ask_data *bla = data; skype_printf(bla->ic, "SET USER %s ISAUTHORIZED TRUE\n", bla->handle); g_free(bla->handle); g_free(bla); } static void skype_buddy_ask_no(void *data) { struct skype_buddy_ask_data *bla = data; skype_printf(bla->ic, "SET USER %s ISAUTHORIZED FALSE\n", bla->handle); g_free(bla->handle); g_free(bla); } void skype_buddy_ask(struct im_connection *ic, char *handle, char *message) { struct skype_buddy_ask_data *bla = g_new0(struct skype_buddy_ask_data, 1); char *buf; bla->ic = ic; bla->handle = g_strdup(handle); buf = g_strdup_printf("The user %s wants to add you to his/her buddy list, saying: '%s'.", handle, message); imcb_ask(ic, buf, bla, skype_buddy_ask_yes, skype_buddy_ask_no); g_free(buf); } static void skype_call_ask_yes(void *data) { struct skype_buddy_ask_data *bla = data; skype_printf(bla->ic, "SET CALL %s STATUS INPROGRESS\n", bla->handle); g_free(bla->handle); g_free(bla); } static void skype_call_ask_no(void *data) { struct skype_buddy_ask_data *bla = data; skype_printf(bla->ic, "SET CALL %s STATUS FINISHED\n", bla->handle); g_free(bla->handle); g_free(bla); } void skype_call_ask(struct im_connection *ic, char *call_id, char *message) { struct skype_buddy_ask_data *bla = g_new0(struct skype_buddy_ask_data, 1); bla->ic = ic; bla->handle = g_strdup(call_id); imcb_ask(ic, message, bla, skype_call_ask_yes, skype_call_ask_no); } static char *skype_call_strerror(int err) { switch (err) { case 1: return "Miscellaneous error"; case 2: return "User or phone number does not exist."; case 3: return "User is offline"; case 4: return "No proxy found"; case 5: return "Session terminated."; case 6: return "No common codec found."; case 7: return "Sound I/O error."; case 8: return "Problem with remote sound device."; case 9: return "Call blocked by recipient."; case 10: return "Recipient not a friend."; case 11: return "Current user not authorized by recipient."; case 12: return "Sound recording error."; default: return "Unknown error"; } } static char *skype_group_by_username(struct im_connection *ic, char *username) { struct skype_data *sd = ic->proto_data; int i, j; /* NEEDSWORK: we just search for the first group of the user, multiple * groups / user is not yet supported by BitlBee. */ for (i = 0; i < g_list_length(sd->groups); i++) { struct skype_group *sg = g_list_nth_data(sd->groups, i); for (j = 0; j < g_list_length(sg->users); j++) { if (!strcmp(g_list_nth_data(sg->users, j), username)) return sg->name; } } return NULL; } static struct skype_group *skype_group_by_name(struct im_connection *ic, char *name) { struct skype_data *sd = ic->proto_data; int i; for (i = 0; i < g_list_length(sd->groups); i++) { struct skype_group *sg = g_list_nth_data(sd->groups, i); if (!strcmp(sg->name, name)) return sg; } return NULL; } static void skype_parse_users(struct im_connection *ic, char *line) { char **i, **nicks; nicks = g_strsplit(line + 6, ", ", 0); for (i = nicks; *i; i++) { skype_printf(ic, "GET USER %s ONLINESTATUS\n", *i); skype_printf(ic, "GET USER %s FULLNAME\n", *i); } g_strfreev(nicks); } static void skype_parse_user(struct im_connection *ic, char *line) { int flags = 0; char *ptr; struct skype_data *sd = ic->proto_data; char *user = strchr(line, ' '); char *status = strrchr(line, ' '); status++; ptr = strchr(++user, ' '); if (!ptr) return; *ptr = '\0'; ptr++; if (!strncmp(ptr, "ONLINESTATUS ", 13)) { if (!strlen(user) || !strcmp(user, sd->username)) return; if (!set_getbool(&ic->acc->set, "test_join") && !strcmp(user, "echo123")) return; ptr = g_strdup_printf("%s@skype.com", user); imcb_add_buddy(ic, ptr, skype_group_by_username(ic, user)); if (strcmp(status, "OFFLINE") && (strcmp(status, "SKYPEOUT") || !set_getbool(&ic->acc->set, "skypeout_offline"))) flags |= OPT_LOGGED_IN; if (strcmp(status, "ONLINE") && strcmp(status, "SKYPEME")) flags |= OPT_AWAY; imcb_buddy_status(ic, ptr, flags, NULL, NULL); g_free(ptr); } else if (!strncmp(ptr, "RECEIVEDAUTHREQUEST ", 20)) { char *message = ptr + 20; if (strlen(message)) skype_buddy_ask(ic, user, message); } else if (!strncmp(ptr, "BUDDYSTATUS ", 12)) { char *st = ptr + 12; if (!strcmp(st, "3")) { char *buf = g_strdup_printf("%s@skype.com", user); imcb_add_buddy(ic, buf, skype_group_by_username(ic, user)); g_free(buf); } } else if (!strncmp(ptr, "MOOD_TEXT ", 10)) { char *buf = g_strdup_printf("%s@skype.com", user); bee_user_t *bu = bee_user_by_handle(ic->bee, ic, buf); g_free(buf); buf = ptr + 10; if (bu) imcb_buddy_status(ic, bu->handle, bu->flags, NULL, *buf ? buf : NULL); if (set_getbool(&ic->acc->set, "show_moods")) imcb_log(ic, "User `%s' changed mood text to `%s'", user, buf); } else if (!strncmp(ptr, "FULLNAME ", 9)) { char *name = ptr + 9; if (sd->is_info) { sd->is_info = FALSE; sd->info_fullname = g_strdup(name); } else { char *buf = g_strdup_printf("%s@skype.com", user); imcb_rename_buddy(ic, buf, name); g_free(buf); } } else if (!strncmp(ptr, "PHONE_HOME ", 11)) sd->info_phonehome = g_strdup(ptr + 11); else if (!strncmp(ptr, "PHONE_OFFICE ", 13)) sd->info_phoneoffice = g_strdup(ptr + 13); else if (!strncmp(ptr, "PHONE_MOBILE ", 13)) sd->info_phonemobile = g_strdup(ptr + 13); else if (!strncmp(ptr, "NROF_AUTHED_BUDDIES ", 20)) sd->info_nrbuddies = g_strdup(ptr + 20); else if (!strncmp(ptr, "TIMEZONE ", 9)) sd->info_tz = g_strdup(ptr + 9); else if (!strncmp(ptr, "LASTONLINETIMESTAMP ", 20)) sd->info_seen = g_strdup(ptr + 20); else if (!strncmp(ptr, "SEX ", 4)) sd->info_sex = g_strdup(ptr + 4); else if (!strncmp(ptr, "LANGUAGE ", 9)) sd->info_language = g_strdup(ptr + 9); else if (!strncmp(ptr, "COUNTRY ", 8)) sd->info_country = g_strdup(ptr + 8); else if (!strncmp(ptr, "PROVINCE ", 9)) sd->info_province = g_strdup(ptr + 9); else if (!strncmp(ptr, "CITY ", 5)) sd->info_city = g_strdup(ptr + 5); else if (!strncmp(ptr, "HOMEPAGE ", 9)) sd->info_homepage = g_strdup(ptr + 9); else if (!strncmp(ptr, "ABOUT ", 6)) { /* Support multiple about lines. */ if (!sd->info_about) sd->info_about = g_strdup(ptr + 6); else { GString *st = g_string_new(sd->info_about); g_string_append_printf(st, "\n%s", ptr + 6); g_free(sd->info_about); sd->info_about = g_strdup(st->str); g_string_free(st, TRUE); } } else if (!strncmp(ptr, "BIRTHDAY ", 9)) { sd->info_birthday = g_strdup(ptr + 9); GString *st = g_string_new("Contact Information\n"); g_string_append_printf(st, "Skype Name: %s\n", user); if (sd->info_fullname) { if (strlen(sd->info_fullname)) g_string_append_printf(st, "Full Name: %s\n", sd->info_fullname); g_free(sd->info_fullname); sd->info_fullname = NULL; } if (sd->info_phonehome) { if (strlen(sd->info_phonehome)) g_string_append_printf(st, "Home Phone: %s\n", sd->info_phonehome); g_free(sd->info_phonehome); sd->info_phonehome = NULL; } if (sd->info_phoneoffice) { if (strlen(sd->info_phoneoffice)) g_string_append_printf(st, "Office Phone: %s\n", sd->info_phoneoffice); g_free(sd->info_phoneoffice); sd->info_phoneoffice = NULL; } if (sd->info_phonemobile) { if (strlen(sd->info_phonemobile)) g_string_append_printf(st, "Mobile Phone: %s\n", sd->info_phonemobile); g_free(sd->info_phonemobile); sd->info_phonemobile = NULL; } g_string_append_printf(st, "Personal Information\n"); if (sd->info_nrbuddies) { if (strlen(sd->info_nrbuddies)) g_string_append_printf(st, "Contacts: %s\n", sd->info_nrbuddies); g_free(sd->info_nrbuddies); sd->info_nrbuddies = NULL; } if (sd->info_tz) { if (strlen(sd->info_tz)) { char ib[256]; time_t t = time(NULL); t += atoi(sd->info_tz)-(60*60*24); struct tm *gt = gmtime(&t); strftime(ib, 256, "%H:%M:%S", gt); g_string_append_printf(st, "Local Time: %s\n", ib); } g_free(sd->info_tz); sd->info_tz = NULL; } if (sd->info_seen) { if (strlen(sd->info_seen)) { char ib[256]; time_t it = atoi(sd->info_seen); struct tm *tm = localtime(&it); strftime(ib, 256, ("%Y. %m. %d. %H:%M"), tm); g_string_append_printf(st, "Last Seen: %s\n", ib); } g_free(sd->info_seen); sd->info_seen = NULL; } if (sd->info_birthday) { if (strlen(sd->info_birthday) && strcmp(sd->info_birthday, "0")) { char ib[256]; struct tm tm; strptime(sd->info_birthday, "%Y%m%d", &tm); strftime(ib, 256, "%B %d, %Y", &tm); g_string_append_printf(st, "Birthday: %s\n", ib); strftime(ib, 256, "%Y", &tm); int year = atoi(ib); time_t t = time(NULL); struct tm *lt = localtime(&t); g_string_append_printf(st, "Age: %d\n", lt->tm_year+1900-year); } g_free(sd->info_birthday); sd->info_birthday = NULL; } if (sd->info_sex) { if (strlen(sd->info_sex)) { char *iptr = sd->info_sex; while (*iptr++) *iptr = tolower(*iptr); g_string_append_printf(st, "Gender: %s\n", sd->info_sex); } g_free(sd->info_sex); sd->info_sex = NULL; } if (sd->info_language) { if (strlen(sd->info_language)) { char *iptr = strchr(sd->info_language, ' '); if (iptr) iptr++; else iptr = sd->info_language; g_string_append_printf(st, "Language: %s\n", iptr); } g_free(sd->info_language); sd->info_language = NULL; } if (sd->info_country) { if (strlen(sd->info_country)) { char *iptr = strchr(sd->info_country, ' '); if (iptr) iptr++; else iptr = sd->info_country; g_string_append_printf(st, "Country: %s\n", iptr); } g_free(sd->info_country); sd->info_country = NULL; } if (sd->info_province) { if (strlen(sd->info_province)) g_string_append_printf(st, "Region: %s\n", sd->info_province); g_free(sd->info_province); sd->info_province = NULL; } if (sd->info_city) { if (strlen(sd->info_city)) g_string_append_printf(st, "City: %s\n", sd->info_city); g_free(sd->info_city); sd->info_city = NULL; } if (sd->info_homepage) { if (strlen(sd->info_homepage)) g_string_append_printf(st, "Homepage: %s\n", sd->info_homepage); g_free(sd->info_homepage); sd->info_homepage = NULL; } if (sd->info_about) { if (strlen(sd->info_about)) g_string_append_printf(st, "%s\n", sd->info_about); g_free(sd->info_about); sd->info_about = NULL; } imcb_log(ic, "%s", st->str); g_string_free(st, TRUE); } } static void skype_parse_chatmessage_said_emoted(struct im_connection *ic, struct groupchat *gc, char *body) { struct skype_data *sd = ic->proto_data; char buf[IRC_LINE_SIZE]; if (!strcmp(sd->type, "SAID")) { if (!sd->is_edit) g_snprintf(buf, IRC_LINE_SIZE, "%s", body); else { g_snprintf(buf, IRC_LINE_SIZE, "%s %s", set_getstr(&ic->acc->set, "edit_prefix"), body); sd->is_edit = 0; } } else g_snprintf(buf, IRC_LINE_SIZE, "/me %s", body); if (!gc) /* Private message */ imcb_buddy_msg(ic, sd->handle, buf, 0, 0); else /* Groupchat message */ imcb_chat_msg(gc, sd->handle, buf, 0, 0); } static void skype_parse_chatmessage(struct im_connection *ic, char *line) { struct skype_data *sd = ic->proto_data; char *id = strchr(line, ' '); if (!++id) return; char *info = strchr(id, ' '); if (!info) return; *info = '\0'; info++; if (!strcmp(info, "STATUS RECEIVED") || !strncmp(info, "EDITED_TIMESTAMP", 16)) { /* New message ID: * (1) Request its from field * (2) Request its body * (3) Request its type * (4) Query chatname */ skype_printf(ic, "GET CHATMESSAGE %s FROM_HANDLE\n", id); if (!strcmp(info, "STATUS RECEIVED")) skype_printf(ic, "GET CHATMESSAGE %s BODY\n", id); else sd->is_edit = 1; skype_printf(ic, "GET CHATMESSAGE %s TYPE\n", id); skype_printf(ic, "GET CHATMESSAGE %s CHATNAME\n", id); } else if (!strncmp(info, "FROM_HANDLE ", 12)) { info += 12; /* New from field value. Store * it, then we can later use it * when we got the message's * body. */ g_free(sd->handle); sd->handle = g_strdup_printf("%s@skype.com", info); } else if (!strncmp(info, "EDITED_BY ", 10)) { info += 10; /* This is the same as * FROM_HANDLE, except that we * never request these lines * from Skype, we just get * them. */ g_free(sd->handle); sd->handle = g_strdup_printf("%s@skype.com", info); } else if (!strncmp(info, "BODY ", 5)) { info += 5; sd->body = g_list_append(sd->body, g_strdup(info)); } else if (!strncmp(info, "TYPE ", 5)) { info += 5; g_free(sd->type); sd->type = g_strdup(info); } else if (!strncmp(info, "CHATNAME ", 9)) { info += 9; if (sd->handle && sd->body && sd->type) { struct groupchat *gc = bee_chat_by_title(ic->bee, ic, info); int i; for (i = 0; i < g_list_length(sd->body); i++) { char *body = g_list_nth_data(sd->body, i); if (!strcmp(sd->type, "SAID") || !strcmp(sd->type, "EMOTED")) { skype_parse_chatmessage_said_emoted(ic, gc, body); } else if (!strcmp(sd->type, "SETTOPIC") && gc) imcb_chat_topic(gc, sd->handle, body, 0); else if (!strcmp(sd->type, "LEFT") && gc) imcb_chat_remove_buddy(gc, sd->handle, NULL); } g_list_free(sd->body); sd->body = NULL; } } } static void skype_parse_call(struct im_connection *ic, char *line) { struct skype_data *sd = ic->proto_data; char *id = strchr(line, ' '); char buf[IRC_LINE_SIZE]; if (!++id) return; char *info = strchr(id, ' '); if (!info) return; *info = '\0'; info++; if (!strncmp(info, "FAILUREREASON ", 14)) sd->failurereason = atoi(strchr(info, ' ')); else if (!strcmp(info, "STATUS RINGING")) { if (sd->call_id) g_free(sd->call_id); sd->call_id = g_strdup(id); skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id); sd->call_status = SKYPE_CALL_RINGING; } else if (!strcmp(info, "STATUS MISSED")) { skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id); sd->call_status = SKYPE_CALL_MISSED; } else if (!strcmp(info, "STATUS CANCELLED")) { skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id); sd->call_status = SKYPE_CALL_CANCELLED; } else if (!strcmp(info, "STATUS FINISHED")) { skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id); sd->call_status = SKYPE_CALL_FINISHED; } else if (!strcmp(info, "STATUS REFUSED")) { skype_printf(ic, "GET CALL %s PARTNER_HANDLE\n", id); sd->call_status = SKYPE_CALL_REFUSED; } else if (!strcmp(info, "STATUS UNPLACED")) { if (sd->call_id) g_free(sd->call_id); /* Save the ID for later usage (Cancel/Finish). */ sd->call_id = g_strdup(id); sd->call_out = TRUE; } else if (!strcmp(info, "STATUS FAILED")) { imcb_error(ic, "Call failed: %s", skype_call_strerror(sd->failurereason)); sd->call_id = NULL; } else if (!strncmp(info, "DURATION ", 9)) { if (sd->call_duration) g_free(sd->call_duration); sd->call_duration = g_strdup(info+9); } else if (!strncmp(info, "PARTNER_HANDLE ", 15)) { info += 15; if (!sd->call_status) return; switch (sd->call_status) { case SKYPE_CALL_RINGING: if (sd->call_out) imcb_log(ic, "You are currently ringing the user %s.", info); else { g_snprintf(buf, IRC_LINE_SIZE, "The user %s is currently ringing you.", info); skype_call_ask(ic, sd->call_id, buf); } break; case SKYPE_CALL_MISSED: imcb_log(ic, "You have missed a call from user %s.", info); break; case SKYPE_CALL_CANCELLED: imcb_log(ic, "You cancelled the call to the user %s.", info); sd->call_status = 0; sd->call_out = FALSE; break; case SKYPE_CALL_REFUSED: if (sd->call_out) imcb_log(ic, "The user %s refused the call.", info); else imcb_log(ic, "You refused the call from user %s.", info); sd->call_out = FALSE; break; case SKYPE_CALL_FINISHED: if (sd->call_duration) imcb_log(ic, "You finished the call to the user %s " "(duration: %s seconds).", info, sd->call_duration); else imcb_log(ic, "You finished the call to the user %s.", info); sd->call_out = FALSE; break; default: /* Don't be noisy, ignore other statuses for now. */ break; } sd->call_status = 0; } } static void skype_parse_filetransfer(struct im_connection *ic, char *line) { struct skype_data *sd = ic->proto_data; char *id = strchr(line, ' '); if (!++id) return; char *info = strchr(id, ' '); if (!info) return; *info = '\0'; info++; if (!strcmp(info, "STATUS NEW")) { skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n", id); sd->filetransfer_status = SKYPE_FILETRANSFER_NEW; } else if (!strcmp(info, "STATUS FAILED")) { skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n", id); sd->filetransfer_status = SKYPE_FILETRANSFER_FAILED; } else if (!strcmp(info, "STATUS COMPLETED")) { skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n", id); sd->filetransfer_status = SKYPE_FILETRANSFER_COMPLETED; } else if (!strcmp(info, "STATUS TRANSFERRING")) { skype_printf(ic, "GET FILETRANSFER %s PARTNER_HANDLE\n", id); sd->filetransfer_status = SKYPE_FILETRANSFER_TRANSFERRING; } else if (!strncmp(info, "FILEPATH ", 9)) { info += 9; sd->filetransfer_path = g_strdup(info); } else if (!strncmp(info, "PARTNER_HANDLE ", 15)) { info += 15; if (!sd->filetransfer_status) return; switch (sd->filetransfer_status) { case SKYPE_FILETRANSFER_NEW: imcb_log(ic, "The user %s offered a new file for you.", info); break; case SKYPE_FILETRANSFER_FAILED: imcb_log(ic, "Failed to transfer file from user %s.", info); break; case SKYPE_FILETRANSFER_COMPLETED: imcb_log(ic, "File transfer from user %s completed.", info); break; case SKYPE_FILETRANSFER_TRANSFERRING: if (sd->filetransfer_path) { imcb_log(ic, "File transfer from user %s started, saving to %s.", info, sd->filetransfer_path); g_free(sd->filetransfer_path); sd->filetransfer_path = NULL; } break; } sd->filetransfer_status = 0; } } static struct skype_group *skype_group_by_id(struct im_connection *ic, int id) { struct skype_data *sd = ic->proto_data; int i; for (i = 0; i < g_list_length(sd->groups); i++) { struct skype_group *sg = (struct skype_group *)g_list_nth_data(sd->groups, i); if (sg->id == id) return sg; } return NULL; } static void skype_group_free(struct skype_group *sg, gboolean usersonly) { int i; for (i = 0; i < g_list_length(sg->users); i++) { char *user = g_list_nth_data(sg->users, i); g_free(user); } sg->users = NULL; if (usersonly) return; g_free(sg->name); g_free(sg); } /* Update the group of each user in this group */ static void skype_group_users(struct im_connection *ic, struct skype_group *sg) { int i; for (i = 0; i < g_list_length(sg->users); i++) { char *user = g_list_nth_data(sg->users, i); char *buf = g_strdup_printf("%s@skype.com", user); imcb_add_buddy(ic, buf, sg->name); g_free(buf); } } static void skype_parse_group(struct im_connection *ic, char *line) { struct skype_data *sd = ic->proto_data; char *id = strchr(line, ' '); if (!++id) return; char *info = strchr(id, ' '); if (!info) return; *info = '\0'; info++; if (!strncmp(info, "DISPLAYNAME ", 12)) { info += 12; /* Name given for a group ID: try to update it or insert a new * one if not found */ struct skype_group *sg = skype_group_by_id(ic, atoi(id)); if (sg) { g_free(sg->name); sg->name = g_strdup(info); } else { sg = g_new0(struct skype_group, 1); sg->id = atoi(id); sg->name = g_strdup(info); sd->groups = g_list_append(sd->groups, sg); } } else if (!strncmp(info, "USERS ", 6)) { struct skype_group *sg = skype_group_by_id(ic, atoi(id)); if (sg) { char **i; char **users = g_strsplit(info + 6, ", ", 0); skype_group_free(sg, TRUE); i = users; while (*i) { sg->users = g_list_append(sg->users, g_strdup(*i)); i++; } g_strfreev(users); skype_group_users(ic, sg); } else log_message(LOGLVL_ERROR, "No skype group with id %s. That's probably a bug.", id); } else if (!strncmp(info, "NROFUSERS ", 10)) { if (!sd->pending_user) { /* Number of users changed in this group, query its type to see * if it's a custom one we should care about. */ skype_printf(ic, "GET GROUP %s TYPE\n", id); return; } /* This is a newly created group, we have a single user * to add. */ struct skype_group *sg = skype_group_by_id(ic, atoi(id)); if (sg) { skype_printf(ic, "ALTER GROUP %d ADDUSER %s\n", sg->id, sd->pending_user); g_free(sd->pending_user); sd->pending_user = NULL; } else log_message(LOGLVL_ERROR, "No skype group with id %s. That's probably a bug.", id); } else if (!strcmp(info, "TYPE CUSTOM_GROUP")) /* This one is interesting, query its users. */ skype_printf(ic, "GET GROUP %s USERS\n", id); } static void skype_parse_chat(struct im_connection *ic, char *line) { struct skype_data *sd = ic->proto_data; char buf[IRC_LINE_SIZE]; char *id = strchr(line, ' '); if (!++id) return; struct groupchat *gc; char *info = strchr(id, ' '); if (!info) return; *info = '\0'; info++; /* Remove fake chat if we created one in skype_chat_with() */ gc = bee_chat_by_title(ic->bee, ic, ""); if (gc) imcb_chat_free(gc); if (!strcmp(info, "STATUS MULTI_SUBSCRIBED")) { gc = bee_chat_by_title(ic->bee, ic, id); if (!gc) { gc = imcb_chat_new(ic, id); imcb_chat_name_hint(gc, id); } skype_printf(ic, "GET CHAT %s ADDER\n", id); skype_printf(ic, "GET CHAT %s TOPIC\n", id); } else if (!strcmp(info, "STATUS DIALOG") && sd->groupchat_with) { gc = imcb_chat_new(ic, id); imcb_chat_name_hint(gc, id); /* According to the docs this * is necessary. However it * does not seem the situation * and it would open an extra * window on our client, so * just leave it out. */ /*skype_printf(ic, "OPEN CHAT %s\n", id);*/ g_snprintf(buf, IRC_LINE_SIZE, "%s@skype.com", sd->groupchat_with); imcb_chat_add_buddy(gc, buf); imcb_chat_add_buddy(gc, sd->username); g_free(sd->groupchat_with); sd->groupchat_with = NULL; skype_printf(ic, "GET CHAT %s ADDER\n", id); skype_printf(ic, "GET CHAT %s TOPIC\n", id); } else if (!strcmp(info, "STATUS UNSUBSCRIBED")) { gc = bee_chat_by_title(ic->bee, ic, id); if (gc) gc->data = (void *)FALSE; } else if (!strncmp(info, "ADDER ", 6)) { info += 6; g_free(sd->adder); sd->adder = g_strdup_printf("%s@skype.com", info); } else if (!strncmp(info, "TOPIC ", 6)) { info += 6; gc = bee_chat_by_title(ic->bee, ic, id); if (gc && (sd->adder || sd->topic_wait)) { if (sd->topic_wait) { sd->adder = g_strdup(sd->username); sd->topic_wait = 0; } imcb_chat_topic(gc, sd->adder, info, 0); g_free(sd->adder); sd->adder = NULL; } } else if (!strncmp(info, "MEMBERS ", 8) || !strncmp(info, "ACTIVEMEMBERS ", 14) ) { if (!strncmp(info, "MEMBERS ", 8)) info += 8; else info += 14; gc = bee_chat_by_title(ic->bee, ic, id); /* Hack! We set ->data to TRUE * while we're on the channel * so that we won't rejoin * after a /part. */ if (!gc || gc->data) return; char **members = g_strsplit(info, " ", 0); int i; for (i = 0; members[i]; i++) { if (!strcmp(members[i], sd->username)) continue; g_snprintf(buf, IRC_LINE_SIZE, "%s@skype.com", members[i]); if (!g_list_find_custom(gc->in_room, buf, (GCompareFunc)strcmp)) imcb_chat_add_buddy(gc, buf); } imcb_chat_add_buddy(gc, sd->username); g_strfreev(members); } } static void skype_parse_password(struct im_connection *ic, char *line) { if (!strncmp(line+9, "OK", 2)) imcb_connected(ic); else { imcb_error(ic, "Authentication Failed"); imc_logout(ic, TRUE); } } static void skype_parse_profile(struct im_connection *ic, char *line) { imcb_log(ic, "SkypeOut balance value is '%s'.", line+21); } static void skype_parse_ping(struct im_connection *ic, char *line) { /* Unused parameter */ line = line; skype_printf(ic, "PONG\n"); } static void skype_parse_chats(struct im_connection *ic, char *line) { char **i; char **chats = g_strsplit(line + 6, ", ", 0); i = chats; while (*i) { skype_printf(ic, "GET CHAT %s STATUS\n", *i); skype_printf(ic, "GET CHAT %s ACTIVEMEMBERS\n", *i); i++; } g_strfreev(chats); } static void skype_parse_groups(struct im_connection *ic, char *line) { if (!set_getbool(&ic->acc->set, "read_groups")) return; char **i; char **groups = g_strsplit(line + 7, ", ", 0); i = groups; while (*i) { skype_printf(ic, "GET GROUP %s DISPLAYNAME\n", *i); skype_printf(ic, "GET GROUP %s USERS\n", *i); i++; } g_strfreev(groups); } static void skype_parse_alter_group(struct im_connection *ic, char *line) { char *id = line + strlen("ALTER GROUP"); if (!++id) return; char *info = strchr(id, ' '); if (!info) return; *info = '\0'; info++; if (!strncmp(info, "ADDUSER ", 8)) { struct skype_group *sg = skype_group_by_id(ic, atoi(id)); info += 8; if (sg) { char *buf = g_strdup_printf("%s@skype.com", info); sg->users = g_list_append(sg->users, g_strdup(info)); imcb_add_buddy(ic, buf, sg->name); g_free(buf); } else log_message(LOGLVL_ERROR, "No skype group with id %s. That's probably a bug.", id); } } typedef void (*skype_parser)(struct im_connection *ic, char *line); static gboolean skype_read_callback(gpointer data, gint fd, b_input_condition cond) { struct im_connection *ic = data; struct skype_data *sd = ic->proto_data; char buf[IRC_LINE_SIZE]; int st, i; char **lines, **lineptr, *line; static struct parse_map { char *k; skype_parser v; } parsers[] = { { "USERS ", skype_parse_users }, { "USER ", skype_parse_user }, { "CHATMESSAGE ", skype_parse_chatmessage }, { "CALL ", skype_parse_call }, { "FILETRANSFER ", skype_parse_filetransfer }, { "CHAT ", skype_parse_chat }, { "GROUP ", skype_parse_group }, { "PASSWORD ", skype_parse_password }, { "PROFILE PSTN_BALANCE ", skype_parse_profile }, { "PING", skype_parse_ping }, { "CHATS ", skype_parse_chats }, { "GROUPS ", skype_parse_groups }, { "ALTER GROUP ", skype_parse_alter_group }, }; /* Unused parameters */ fd = fd; cond = cond; if (!sd || sd->fd == -1) return FALSE; /* Read the whole data. */ st = ssl_read(sd->ssl, buf, sizeof(buf)); if (st >= IRC_LINE_SIZE-1) { /* As we don't buffer incoming data, if IRC_LINE_SIZE amount of bytes * were received, there's a good chance last message was truncated * and the next recv() will yield garbage. */ imcb_error(ic, "Unable to handle incoming data from skyped"); st = 0; } if (st > 0) { buf[st] = '\0'; /* Then split it up to lines. */ lines = g_strsplit(buf, "\n", 0); lineptr = lines; while ((line = *lineptr)) { if (!strlen(line)) break; if (set_getbool(&ic->acc->set, "skypeconsole_receive")) imcb_buddy_msg(ic, "skypeconsole", line, 0, 0); for (i = 0; i < ARRAY_SIZE(parsers); i++) if (!strncmp(line, parsers[i].k, strlen(parsers[i].k))) { parsers[i].v(ic, line); break; } lineptr++; } g_strfreev(lines); } else if (st == 0 || (st < 0 && !sockerr_again())) { ssl_disconnect(sd->ssl); sd->fd = -1; sd->ssl = NULL; imcb_error(ic, "Error while reading from server"); imc_logout(ic, TRUE); return FALSE; } return TRUE; } gboolean skype_start_stream(struct im_connection *ic) { struct skype_data *sd = ic->proto_data; int st; if (!sd) return FALSE; if (sd->bfd <= 0) sd->bfd = b_input_add(sd->fd, B_EV_IO_READ, skype_read_callback, ic); /* Log in */ skype_printf(ic, "USERNAME %s\n", ic->acc->user); skype_printf(ic, "PASSWORD %s\n", ic->acc->pass); /* This will download all buddies and groups. */ st = skype_printf(ic, "SEARCH GROUPS CUSTOM\n"); skype_printf(ic, "SEARCH FRIENDS\n"); skype_printf(ic, "SET USERSTATUS ONLINE\n"); /* Auto join to bookmarked chats if requested.*/ if (set_getbool(&ic->acc->set, "auto_join")) { skype_printf(ic, "SEARCH BOOKMARKEDCHATS\n"); skype_printf(ic, "SEARCH ACTIVECHATS\n"); skype_printf(ic, "SEARCH MISSEDCHATS\n"); skype_printf(ic, "SEARCH RECENTCHATS\n"); } return st; } gboolean skype_connected(gpointer data, int returncode, void *source, b_input_condition cond) { struct im_connection *ic = data; struct skype_data *sd = ic->proto_data; /* Unused parameter */ cond = cond; if (!source) { sd->ssl = NULL; imcb_error(ic, "Could not connect to server"); imc_logout(ic, TRUE); return FALSE; } imcb_log(ic, "Connected to server, logging in"); return skype_start_stream(ic); } static void skype_login(account_t *acc) { struct im_connection *ic = imcb_new(acc); struct skype_data *sd = g_new0(struct skype_data, 1); ic->proto_data = sd; imcb_log(ic, "Connecting"); sd->ssl = ssl_connect(set_getstr(&acc->set, "server"), set_getint(&acc->set, "port"), FALSE, skype_connected, ic); sd->fd = sd->ssl ? ssl_getfd(sd->ssl) : -1; sd->username = g_strdup(acc->user); sd->ic = ic; if (set_getbool(&acc->set, "skypeconsole")) imcb_add_buddy(ic, "skypeconsole", NULL); } static void skype_logout(struct im_connection *ic) { struct skype_data *sd = ic->proto_data; int i; skype_printf(ic, "SET USERSTATUS OFFLINE\n"); while (ic->groupchats) imcb_chat_free(ic->groupchats->data); for (i = 0; i < g_list_length(sd->groups); i++) { struct skype_group *sg = (struct skype_group *)g_list_nth_data(sd->groups, i); skype_group_free(sg, FALSE); } if (sd->ssl) ssl_disconnect(sd->ssl); g_free(sd->username); g_free(sd->handle); g_free(sd); ic->proto_data = NULL; } static int skype_buddy_msg(struct im_connection *ic, char *who, char *message, int flags) { char *ptr, *nick; int st; /* Unused parameter */ flags = flags; nick = g_strdup(who); ptr = strchr(nick, '@'); if (ptr) *ptr = '\0'; if (!strncmp(who, "skypeconsole", 12)) st = skype_printf(ic, "%s\n", message); else st = skype_printf(ic, "MESSAGE %s %s\n", nick, message); g_free(nick); return st; } const struct skype_away_state *skype_away_state_by_name(char *name) { int i; for (i = 0; skype_away_state_list[i].full_name; i++) if (g_strcasecmp(skype_away_state_list[i].full_name, name) == 0) return skype_away_state_list + i; return NULL; } static void skype_set_away(struct im_connection *ic, char *state_txt, char *message) { const struct skype_away_state *state; /* Unused parameter */ message = message; if (state_txt == NULL) state = skype_away_state_by_name("Online"); else state = skype_away_state_by_name(state_txt); skype_printf(ic, "SET USERSTATUS %s\n", state->code); } static GList *skype_away_states(struct im_connection *ic) { static GList *l; int i; /* Unused parameter */ ic = ic; if (l == NULL) for (i = 0; skype_away_state_list[i].full_name; i++) l = g_list_append(l, (void *)skype_away_state_list[i].full_name); return l; } static char *skype_set_display_name(set_t *set, char *value) { account_t *acc = set->data; struct im_connection *ic = acc->ic; skype_printf(ic, "SET PROFILE FULLNAME %s\n", value); return value; } static char *skype_set_mood_text(set_t *set, char *value) { account_t *acc = set->data; struct im_connection *ic = acc->ic; skype_printf(ic, "SET PROFILE MOOD_TEXT %s\n", value); return value; } static char *skype_set_balance(set_t *set, char *value) { account_t *acc = set->data; struct im_connection *ic = acc->ic; skype_printf(ic, "GET PROFILE PSTN_BALANCE\n"); return value; } static void skype_call(struct im_connection *ic, char *value) { char *nick = g_strdup(value); char *ptr = strchr(nick, '@'); if (ptr) *ptr = '\0'; skype_printf(ic, "CALL %s\n", nick); g_free(nick); } static void skype_hangup(struct im_connection *ic) { struct skype_data *sd = ic->proto_data; if (sd->call_id) { skype_printf(ic, "SET CALL %s STATUS FINISHED\n", sd->call_id); g_free(sd->call_id); sd->call_id = 0; } else imcb_error(ic, "There are no active calls currently."); } static char *skype_set_call(set_t *set, char *value) { account_t *acc = set->data; struct im_connection *ic = acc->ic; if (value) skype_call(ic, value); else skype_hangup(ic); return value; } static void skype_add_buddy(struct im_connection *ic, char *who, char *group) { struct skype_data *sd = ic->proto_data; char *nick, *ptr; nick = g_strdup(who); ptr = strchr(nick, '@'); if (ptr) *ptr = '\0'; if (!group) { skype_printf(ic, "SET USER %s BUDDYSTATUS 2 Please authorize me\n", nick); g_free(nick); } else { struct skype_group *sg = skype_group_by_name(ic, group); if (!sg) { /* No such group, we need to create it, then have to * add the user once it's created. */ skype_printf(ic, "CREATE GROUP %s\n", group); sd->pending_user = g_strdup(nick); } else { skype_printf(ic, "ALTER GROUP %d ADDUSER %s\n", sg->id, nick); } } } static void skype_remove_buddy(struct im_connection *ic, char *who, char *group) { char *nick, *ptr; /* Unused parameter */ group = group; nick = g_strdup(who); ptr = strchr(nick, '@'); if (ptr) *ptr = '\0'; skype_printf(ic, "SET USER %s BUDDYSTATUS 1\n", nick); g_free(nick); } void skype_chat_msg(struct groupchat *gc, char *message, int flags) { struct im_connection *ic = gc->ic; /* Unused parameter */ flags = flags; skype_printf(ic, "CHATMESSAGE %s %s\n", gc->title, message); } void skype_chat_leave(struct groupchat *gc) { struct im_connection *ic = gc->ic; skype_printf(ic, "ALTER CHAT %s LEAVE\n", gc->title); gc->data = (void *)TRUE; } void skype_chat_invite(struct groupchat *gc, char *who, char *message) { struct im_connection *ic = gc->ic; char *ptr, *nick; nick = g_strdup(who); ptr = strchr(nick, '@'); if (ptr) *ptr = '\0'; skype_printf(ic, "ALTER CHAT %s ADDMEMBERS %s\n", gc->title, nick); g_free(nick); } void skype_chat_topic(struct groupchat *gc, char *message) { struct im_connection *ic = gc->ic; struct skype_data *sd = ic->proto_data; skype_printf(ic, "ALTER CHAT %s SETTOPIC %s\n", gc->title, message); sd->topic_wait = 1; } struct groupchat *skype_chat_with(struct im_connection *ic, char *who) { struct skype_data *sd = ic->proto_data; char *ptr, *nick; nick = g_strdup(who); ptr = strchr(nick, '@'); if (ptr) *ptr = '\0'; skype_printf(ic, "CHAT CREATE %s\n", nick); sd->groupchat_with = g_strdup(nick); g_free(nick); /* We create a fake chat for now. We will replace it with a real one in * the real callback. */ return imcb_chat_new(ic, ""); } static void skype_get_info(struct im_connection *ic, char *who) { struct skype_data *sd = ic->proto_data; char *ptr, *nick; nick = g_strdup(who); ptr = strchr(nick, '@'); if (ptr) *ptr = '\0'; sd->is_info = TRUE; skype_printf(ic, "GET USER %s FULLNAME\n", nick); skype_printf(ic, "GET USER %s PHONE_HOME\n", nick); skype_printf(ic, "GET USER %s PHONE_OFFICE\n", nick); skype_printf(ic, "GET USER %s PHONE_MOBILE\n", nick); skype_printf(ic, "GET USER %s NROF_AUTHED_BUDDIES\n", nick); skype_printf(ic, "GET USER %s TIMEZONE\n", nick); skype_printf(ic, "GET USER %s LASTONLINETIMESTAMP\n", nick); skype_printf(ic, "GET USER %s SEX\n", nick); skype_printf(ic, "GET USER %s LANGUAGE\n", nick); skype_printf(ic, "GET USER %s COUNTRY\n", nick); skype_printf(ic, "GET USER %s PROVINCE\n", nick); skype_printf(ic, "GET USER %s CITY\n", nick); skype_printf(ic, "GET USER %s HOMEPAGE\n", nick); skype_printf(ic, "GET USER %s ABOUT\n", nick); /* * Hack: we query the bithday property which is always a single line, * so we can send the collected properties to the user when we have * this one. */ skype_printf(ic, "GET USER %s BIRTHDAY\n", nick); } static void skype_init(account_t *acc) { set_t *s; s = set_add(&acc->set, "server", SKYPE_DEFAULT_SERVER, set_eval_account, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "port", SKYPE_DEFAULT_PORT, set_eval_int, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "display_name", NULL, skype_set_display_name, acc); s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY; s = set_add(&acc->set, "mood_text", NULL, skype_set_mood_text, acc); s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY; s = set_add(&acc->set, "call", NULL, skype_set_call, acc); s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY; s = set_add(&acc->set, "balance", NULL, skype_set_balance, acc); s->flags |= SET_NOSAVE | ACC_SET_ONLINE_ONLY; s = set_add(&acc->set, "skypeout_offline", "true", set_eval_bool, acc); s = set_add(&acc->set, "skypeconsole", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "skypeconsole_receive", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "auto_join", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "test_join", "false", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "show_moods", "false", set_eval_bool, acc); s = set_add(&acc->set, "edit_prefix", "EDIT:", NULL, acc); s = set_add(&acc->set, "read_groups", "false", set_eval_bool, acc); } #if BITLBEE_VERSION_CODE > BITLBEE_VER(3, 0, 1) GList *skype_buddy_action_list(bee_user_t *bu) { static GList *ret; /* Unused parameter */ bu = bu; if (ret == NULL) { static const struct buddy_action ba[2] = { {"CALL", "Initiate a call" }, {"HANGUP", "Hang up a call" }, }; int i; for (i = 0; i < ARRAY_SIZE(ba); i++) ret = g_list_prepend(ret, (void *)(ba + i)); } return ret; } void *skype_buddy_action(struct bee_user *bu, const char *action, char * const args[], void *data) { /* Unused parameters */ args = args; data = data; if (!g_strcasecmp(action, "CALL")) skype_call(bu->ic, bu->handle); else if (!g_strcasecmp(action, "HANGUP")) skype_hangup(bu->ic); return NULL; } #endif void init_plugin(void) { struct prpl *ret = g_new0(struct prpl, 1); ret->name = "skype"; ret->login = skype_login; ret->init = skype_init; ret->logout = skype_logout; ret->buddy_msg = skype_buddy_msg; ret->get_info = skype_get_info; ret->away_states = skype_away_states; ret->set_away = skype_set_away; ret->add_buddy = skype_add_buddy; ret->remove_buddy = skype_remove_buddy; ret->chat_msg = skype_chat_msg; ret->chat_leave = skype_chat_leave; ret->chat_invite = skype_chat_invite; ret->chat_with = skype_chat_with; ret->handle_cmp = g_strcasecmp; ret->chat_topic = skype_chat_topic; #if BITLBEE_VERSION_CODE > BITLBEE_VER(3, 0, 1) ret->buddy_action_list = skype_buddy_action_list; ret->buddy_action = skype_buddy_action; #endif register_protocol(ret); } bitlbee-3.2.1/protocols/skype/asciidoc.conf0000644000175000017500000000101612245474076020320 0ustar wilmerwilmerifdef::doctype-manpage[] ifdef::backend-docbook[] [header] template::[header-declarations] {bee_date} {mantitle} {manvolnum} BitlBee BitlBee manual {manname} {manpurpose} endif::backend-docbook[] endif::doctype-manpage[] bitlbee-3.2.1/protocols/skype/skyped.txt0000644000175000017500000000427112245474076017741 0ustar wilmerwilmer= skyped(1) == NAME skyped - allows remote control of the Skype GUI client == SYNOPSIS skyped [] == DESCRIPTION Skype supports remote control of the GUI client only via X11 or DBus messages. This is hard in case you want remote control. This daemon listens on a TCP port and runs on the same machine where the GUI client runs. It passes all the input it gets to Skype directly, except for a few commands which is related to authentication. The whole communication is done via SSL. == CONFIGURATION - Set up `~/.skyped/skyped.conf`: Create the `~/.skyped` directory, copy `skyped.conf` and `skyped.cnf` from `/usr/local/etc/skyped/` to `~/.skyped`, adjust `username` and `password`. The `username` should be your Skype login and the `password` can be whatever you want, but you will have to specify that one when adding the Skype account to BitlBee (see later). NOTE: Here, and later - `/usr/local/etc` can be different on your installation if you used the `--sysconfdir` switch when running the `configure` of BitlBee. - Generate the SSL pem files: ---- $ cd ~/.skyped $ openssl req -new -x509 -days 365 -nodes -config skyped.cnf -out skyped.cert.pem \ -keyout skyped.key.pem ---- - Start `skyped` (the TCP server), initially without detaching and enabling debug messages: ---- $ skyped -d -n ---- - Start your `IRC` client, connect to BitlBee and add your account: ---- account add skype ---- `` should be your Skype account name, `` should be the one you declared in `skyped.conf`. == OPTIONS -c, --config:: Path to configuration file (default: $HOME/.skyped/skyped.conf) -d, --debug:: Enable debug messages -h, --help:: Show short summary of options -H, --host:: Set the tcp host (default: 0.0.0.0) -l, --log:: Set the log file in background mode (default: none) -m, --mock=:: Mock mode: replay session from file, instead of connecting to Skype. -n, --nofork:: Don't run as daemon in the background -s, --dont-start-skype:: Assume that skype is running independently, don't try to start/stop it. -p, --port:: Set the tcp port (default: 2727) -v, --version:: Display version information == AUTHOR Written by Miklos Vajna bitlbee-3.2.1/protocols/twitter/0000755000175000017500000000000012245477444016246 5ustar wilmerwilmerbitlbee-3.2.1/protocols/twitter/twitter.c0000644000175000017500000005264312245474076020124 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * * Copyright 2009-2010 Geert Mulders * * Copyright 2010-2013 Wilmer van der Gaast * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation, version * * 2.1. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the Free Software Foundation, * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * * ****************************************************************************/ #include "nogaim.h" #include "oauth.h" #include "twitter.h" #include "twitter_http.h" #include "twitter_lib.h" #include "url.h" GSList *twitter_connections = NULL; /** * Main loop function */ gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond) { struct im_connection *ic = data; // Check if we are still logged in... if (!g_slist_find(twitter_connections, ic)) return FALSE; // Do stuff.. return twitter_get_timeline(ic, -1) && ((ic->flags & OPT_LOGGED_IN) == OPT_LOGGED_IN); } static void twitter_main_loop_start(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; /* Create the room now that we "logged in". */ if (td->flags & TWITTER_MODE_CHAT) twitter_groupchat_init(ic); imcb_log(ic, "Getting initial statuses"); // Run this once. After this queue the main loop function (or open the // stream if available). twitter_main_loop(ic, -1, 0); if (set_getbool(&ic->acc->set, "stream")) { /* That fetch was just to get backlog, the stream will give us the rest. \o/ */ twitter_open_stream(ic); /* Stream sends keepalives (empty lines) or actual data at least twice a minute. Disconnect if this stops. */ ic->flags |= OPT_PONGS; } else { /* Not using the streaming API, so keep polling the old- fashioned way. :-( */ td->main_loop_id = b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, twitter_main_loop, ic); } } struct groupchat *twitter_groupchat_init(struct im_connection *ic) { char *name_hint; struct groupchat *gc; struct twitter_data *td = ic->proto_data; GSList *l; if (td->timeline_gc) return td->timeline_gc; td->timeline_gc = gc = imcb_chat_new(ic, "twitter/timeline"); name_hint = g_strdup_printf("%s_%s", td->prefix, ic->acc->user); imcb_chat_name_hint(gc, name_hint); g_free(name_hint); for (l = ic->bee->users; l; l = l->next) { bee_user_t *bu = l->data; if (bu->ic == ic) imcb_chat_add_buddy(gc, bu->handle); } imcb_chat_add_buddy(gc, ic->acc->user); return gc; } static void twitter_oauth_start(struct im_connection *ic); void twitter_login_finish(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; td->flags &= ~TWITTER_DOING_TIMELINE; if (set_getbool(&ic->acc->set, "oauth") && !td->oauth_info) twitter_oauth_start(ic); else if (!(td->flags & TWITTER_MODE_ONE) && !(td->flags & TWITTER_HAVE_FRIENDS)) { imcb_log(ic, "Getting contact list"); twitter_get_friends_ids(ic, -1); } else twitter_main_loop_start(ic); } static const struct oauth_service twitter_oauth = { "https://api.twitter.com/oauth/request_token", "https://api.twitter.com/oauth/access_token", "https://api.twitter.com/oauth/authorize", .consumer_key = "xsDNKJuNZYkZyMcu914uEA", .consumer_secret = "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo", }; static const struct oauth_service identica_oauth = { "https://identi.ca/api/oauth/request_token", "https://identi.ca/api/oauth/access_token", "https://identi.ca/api/oauth/authorize", .consumer_key = "e147ff789fcbd8a5a07963afbb43f9da", .consumer_secret = "c596267f277457ec0ce1ab7bb788d828", }; static gboolean twitter_oauth_callback(struct oauth_info *info); static const struct oauth_service *get_oauth_service(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; if (strstr(td->url_host, "identi.ca")) return &identica_oauth; else return &twitter_oauth; /* Could add more services, or allow configuring your own base URL + API keys. */ } static void twitter_oauth_start(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; const char *url = set_getstr(&ic->acc->set, "base_url"); imcb_log(ic, "Requesting OAuth request token"); if (!strstr(url, "twitter.com") && !strstr(url, "identi.ca")) imcb_log(ic, "Warning: OAuth only works with identi.ca and " "Twitter."); td->oauth_info = oauth_request_token(get_oauth_service(ic), twitter_oauth_callback, ic); /* We need help from the user to complete OAuth login, so don't time out on this login. */ ic->flags |= OPT_SLOW_LOGIN; } static gboolean twitter_oauth_callback(struct oauth_info *info) { struct im_connection *ic = info->data; struct twitter_data *td; if (!g_slist_find(twitter_connections, ic)) return FALSE; td = ic->proto_data; if (info->stage == OAUTH_REQUEST_TOKEN) { char *name, *msg; if (info->request_token == NULL) { imcb_error(ic, "OAuth error: %s", twitter_parse_error(info->http)); imc_logout(ic, TRUE); return FALSE; } name = g_strdup_printf("%s_%s", td->prefix, ic->acc->user); msg = g_strdup_printf("To finish OAuth authentication, please visit " "%s and respond with the resulting PIN code.", info->auth_url); imcb_buddy_msg(ic, name, msg, 0, 0); g_free(name); g_free(msg); } else if (info->stage == OAUTH_ACCESS_TOKEN) { const char *sn; if (info->token == NULL || info->token_secret == NULL) { imcb_error(ic, "OAuth error: %s", twitter_parse_error(info->http)); imc_logout(ic, TRUE); return FALSE; } if ((sn = oauth_params_get(&info->params, "screen_name"))) { if (ic->acc->prpl->handle_cmp(sn, ic->acc->user) != 0) imcb_log(ic, "Warning: You logged in via OAuth as %s " "instead of %s.", sn, ic->acc->user); g_free(td->user); td->user = g_strdup(sn); } /* IM mods didn't do this so far and it's ugly but I should be able to get away with it... */ g_free(ic->acc->pass); ic->acc->pass = oauth_to_string(info); twitter_login_finish(ic); } return TRUE; } int twitter_url_len_diff(gchar *msg, unsigned int target_len) { int url_len_diff = 0; static GRegex *regex = NULL; GMatchInfo *match_info; if (regex == NULL) regex = g_regex_new("(^|\\s)(http(s)?://[^\\s$]+)", 0, 0, NULL); g_regex_match(regex, msg, 0, &match_info); while (g_match_info_matches(match_info)) { gchar *url = g_match_info_fetch(match_info, 2); url_len_diff += target_len - g_utf8_strlen(url, -1); /* Add another character for https://t.co/... URLs */ if (g_match_info_fetch(match_info, 3) != NULL) url_len_diff += 1; g_free(url); g_match_info_next(match_info, NULL); } g_match_info_free(match_info); return url_len_diff; } static gboolean twitter_length_check(struct im_connection *ic, gchar * msg) { int max = set_getint(&ic->acc->set, "message_length"), len; int target_len = set_getint(&ic->acc->set, "target_url_length"); int url_len_diff = 0; if (target_len > 0) url_len_diff = twitter_url_len_diff(msg, target_len); if (max == 0 || (len = g_utf8_strlen(msg, -1) + url_len_diff) <= max) return TRUE; twitter_log(ic, "Maximum message length exceeded: %d > %d", len, max); return FALSE; } static char *set_eval_commands(set_t * set, char *value) { if (g_strcasecmp(value, "strict") == 0 ) return value; else return set_eval_bool(set, value); } static char *set_eval_mode(set_t * set, char *value) { if (g_strcasecmp(value, "one") == 0 || g_strcasecmp(value, "many") == 0 || g_strcasecmp(value, "chat") == 0) return value; else return NULL; } static void twitter_init(account_t * acc) { set_t *s; char *def_url; char *def_tul; char *def_mentions; if (strcmp(acc->prpl->name, "twitter") == 0) { def_url = TWITTER_API_URL; def_tul = "22"; def_mentions = "true"; } else { /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */ def_url = IDENTICA_API_URL; def_tul = "0"; def_mentions = "false"; } s = set_add(&acc->set, "auto_reply_timeout", "10800", set_eval_int, acc); s = set_add(&acc->set, "base_url", def_url, NULL, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "commands", "true", set_eval_commands, acc); s = set_add(&acc->set, "fetch_interval", "60", set_eval_int, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "fetch_mentions", def_mentions, set_eval_bool, acc); s = set_add(&acc->set, "message_length", "140", set_eval_int, acc); s = set_add(&acc->set, "target_url_length", def_tul, set_eval_int, acc); s = set_add(&acc->set, "mode", "chat", set_eval_mode, acc); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add(&acc->set, "oauth", "true", set_eval_oauth, acc); s = set_add(&acc->set, "show_ids", "true", set_eval_bool, acc); s = set_add(&acc->set, "show_old_mentions", "20", set_eval_int, acc); s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc); if (strcmp(acc->prpl->name, "twitter") == 0) { s = set_add(&acc->set, "stream", "true", set_eval_bool, acc); s->flags |= ACC_SET_OFFLINE_ONLY; } } /** * Login method. Since the twitter API works with separate HTTP request we * only save the user and pass to the twitter_data object. */ static void twitter_login(account_t * acc) { struct im_connection *ic = imcb_new(acc); struct twitter_data *td; char name[strlen(acc->user) + 9]; url_t url; char *s; if (!url_set(&url, set_getstr(&ic->acc->set, "base_url")) || (url.proto != PROTO_HTTP && url.proto != PROTO_HTTPS)) { imcb_error(ic, "Incorrect API base URL: %s", set_getstr(&ic->acc->set, "base_url")); imc_logout(ic, FALSE); return; } if (!strstr(url.host, "twitter.com") && set_getbool(&ic->acc->set, "stream")) { imcb_error(ic, "Warning: The streaming API is only supported by Twitter, " "and you seem to be connecting to a different service."); } imcb_log(ic, "Connecting"); twitter_connections = g_slist_append(twitter_connections, ic); td = g_new0(struct twitter_data, 1); ic->proto_data = td; td->user = g_strdup(acc->user); td->url_ssl = url.proto == PROTO_HTTPS; td->url_port = url.port; td->url_host = g_strdup(url.host); if (strcmp(url.file, "/") != 0) td->url_path = g_strdup(url.file); else { td->url_path = g_strdup(""); if (g_str_has_suffix(url.host, "twitter.com")) /* May fire for people who turned on HTTPS. */ imcb_error(ic, "Warning: Twitter requires a version number in API calls " "now. Try resetting the base_url account setting."); } /* Hacky string mangling: Turn identi.ca into identi.ca and api.twitter.com into twitter, and try to be sensible if we get anything else. */ td->prefix = g_strdup(url.host); if (g_str_has_suffix(td->prefix, ".com")) td->prefix[strlen(url.host) - 4] = '\0'; if ((s = strrchr(td->prefix, '.')) && strlen(s) > 4) { /* If we have at least 3 chars after the last dot, cut off the rest. (mostly a www/api prefix or sth) */ s = g_strdup(s + 1); g_free(td->prefix); td->prefix = s; } if (strstr(acc->pass, "oauth_token=")) td->oauth_info = oauth_from_string(acc->pass, get_oauth_service(ic)); sprintf(name, "%s_%s", td->prefix, acc->user); imcb_add_buddy(ic, name, NULL); imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL); td->log = g_new0(struct twitter_log_data, TWITTER_LOG_LENGTH); td->log_id = -1; s = set_getstr(&ic->acc->set, "mode"); if (g_strcasecmp(s, "one") == 0) td->flags |= TWITTER_MODE_ONE; else if (g_strcasecmp(s, "many") == 0) td->flags |= TWITTER_MODE_MANY; else td->flags |= TWITTER_MODE_CHAT; twitter_login_finish(ic); } /** * Logout method. Just free the twitter_data. */ static void twitter_logout(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; // Set the status to logged out. ic->flags &= ~OPT_LOGGED_IN; // Remove the main_loop function from the function queue. b_event_remove(td->main_loop_id); if (td->timeline_gc) imcb_chat_free(td->timeline_gc); if (td) { http_close(td->stream); oauth_info_free(td->oauth_info); g_free(td->user); g_free(td->prefix); g_free(td->url_host); g_free(td->url_path); g_free(td->log); g_free(td); } twitter_connections = g_slist_remove(twitter_connections, ic); } static void twitter_handle_command(struct im_connection *ic, char *message); /** * */ static int twitter_buddy_msg(struct im_connection *ic, char *who, char *message, int away) { struct twitter_data *td = ic->proto_data; int plen = strlen(td->prefix); if (g_strncasecmp(who, td->prefix, plen) == 0 && who[plen] == '_' && g_strcasecmp(who + plen + 1, ic->acc->user) == 0) { if (set_getbool(&ic->acc->set, "oauth") && td->oauth_info && td->oauth_info->token == NULL) { char pin[strlen(message) + 1], *s; strcpy(pin, message); for (s = pin + sizeof(pin) - 2; s > pin && isspace(*s); s--) *s = '\0'; for (s = pin; *s && isspace(*s); s++) { } if (!oauth_access_token(s, td->oauth_info)) { imcb_error(ic, "OAuth error: %s", "Failed to send access token request"); imc_logout(ic, TRUE); return FALSE; } } else twitter_handle_command(ic, message); } else { twitter_direct_messages_new(ic, who, message); } return (0); } static void twitter_get_info(struct im_connection *ic, char *who) { } static void twitter_add_buddy(struct im_connection *ic, char *who, char *group) { twitter_friendships_create_destroy(ic, who, 1); } static void twitter_remove_buddy(struct im_connection *ic, char *who, char *group) { twitter_friendships_create_destroy(ic, who, 0); } static void twitter_chat_msg(struct groupchat *c, char *message, int flags) { if (c && message) twitter_handle_command(c->ic, message); } static void twitter_chat_invite(struct groupchat *c, char *who, char *message) { } static void twitter_chat_leave(struct groupchat *c) { struct twitter_data *td = c->ic->proto_data; if (c != td->timeline_gc) return; /* WTF? */ /* If the user leaves the channel: Fine. Rejoin him/her once new tweets come in. */ imcb_chat_free(td->timeline_gc); td->timeline_gc = NULL; } static void twitter_keepalive(struct im_connection *ic) { } static void twitter_add_permit(struct im_connection *ic, char *who) { } static void twitter_rem_permit(struct im_connection *ic, char *who) { } static void twitter_add_deny(struct im_connection *ic, char *who) { } static void twitter_rem_deny(struct im_connection *ic, char *who) { } //static char *twitter_set_display_name( set_t *set, char *value ) //{ // return value; //} static void twitter_buddy_data_add(struct bee_user *bu) { bu->data = g_new0(struct twitter_user_data, 1); } static void twitter_buddy_data_free(struct bee_user *bu) { g_free(bu->data); } /** Convert the given bitlbee tweet ID, bitlbee username, or twitter tweet ID * into a twitter tweet ID. * * Returns 0 if the user provides garbage. */ static guint64 twitter_message_id_from_command_arg(struct im_connection *ic, char *arg, bee_user_t **bu_) { struct twitter_data *td = ic->proto_data; struct twitter_user_data *tud; bee_user_t *bu = NULL; guint64 id = 0; if (bu_) *bu_ = NULL; if (!arg || !arg[0]) return 0; if (arg[0] != '#' && (bu = bee_user_by_handle(ic->bee, ic, arg))) { if ((tud = bu->data)) id = tud->last_id; } else { if (arg[0] == '#') arg++; if (sscanf(arg, "%" G_GINT64_MODIFIER "x", &id) == 1 && id < TWITTER_LOG_LENGTH) { bu = td->log[id].bu; id = td->log[id].id; /* Beware of dangling pointers! */ if (!g_slist_find(ic->bee->users, bu)) bu = NULL; } else if (sscanf(arg, "%" G_GINT64_MODIFIER "d", &id) == 1) { /* Allow normal tweet IDs as well; not a very useful feature but it's always been there. Just ignore very low IDs to avoid accidents. */ if (id < 1000000) id = 0; } } if (bu_) *bu_ = bu; return id; } static void twitter_handle_command(struct im_connection *ic, char *message) { struct twitter_data *td = ic->proto_data; char *cmds, **cmd, *new = NULL; guint64 in_reply_to = 0, id; gboolean allow_post = g_strcasecmp(set_getstr(&ic->acc->set, "commands"), "strict") != 0; bee_user_t *bu = NULL; cmds = g_strdup(message); cmd = split_command_parts(cmds); if (cmd[0] == NULL) { goto eof; } else if (!set_getbool(&ic->acc->set, "commands") && allow_post) { /* Not supporting commands if "commands" is set to true/strict. */ } else if (g_strcasecmp(cmd[0], "undo") == 0) { if (cmd[1] == NULL) twitter_status_destroy(ic, td->last_status_id); else if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) twitter_status_destroy(ic, id); else twitter_log(ic, "Could not undo last action"); goto eof; } else if (g_strcasecmp(cmd[0], "favourite") == 0 && cmd[1]) { if ((id = twitter_message_id_from_command_arg(ic, cmd[1], NULL))) { twitter_favourite_tweet(ic, id); } else { twitter_log(ic, "Please provide a message ID or username."); } goto eof; } else if (g_strcasecmp(cmd[0], "follow") == 0 && cmd[1]) { twitter_add_buddy(ic, cmd[1], NULL); goto eof; } else if (g_strcasecmp(cmd[0], "unfollow") == 0 && cmd[1]) { twitter_remove_buddy(ic, cmd[1], NULL); goto eof; } else if ((g_strcasecmp(cmd[0], "report") == 0 || g_strcasecmp(cmd[0], "spam") == 0) && cmd[1]) { char *screen_name; /* Report nominally works on users but look up the user who posted the given ID if the user wants to do it that way */ twitter_message_id_from_command_arg(ic, cmd[1], &bu); if (bu) screen_name = bu->handle; else screen_name = cmd[1]; twitter_report_spam(ic, screen_name); goto eof; } else if (g_strcasecmp(cmd[0], "rt") == 0 && cmd[1]) { id = twitter_message_id_from_command_arg(ic, cmd[1], NULL); td->last_status_id = 0; if (id) twitter_status_retweet(ic, id); else twitter_log(ic, "User `%s' does not exist or didn't " "post any statuses recently", cmd[1]); goto eof; } else if (g_strcasecmp(cmd[0], "reply") == 0 && cmd[1] && cmd[2]) { id = twitter_message_id_from_command_arg(ic, cmd[1], &bu); if (!id || !bu) { twitter_log(ic, "User `%s' does not exist or didn't " "post any statuses recently", cmd[1]); goto eof; } message = new = g_strdup_printf("@%s %s", bu->handle, message + (cmd[2] - cmd[0])); in_reply_to = id; allow_post = TRUE; } else if (g_strcasecmp(cmd[0], "post") == 0) { message += 5; allow_post = TRUE; } if (allow_post) { char *s; if (!twitter_length_check(ic, message)) goto eof; s = cmd[0] + strlen(cmd[0]) - 1; if (!new && s > cmd[0] && (*s == ':' || *s == ',')) { *s = '\0'; if ((bu = bee_user_by_handle(ic->bee, ic, cmd[0]))) { struct twitter_user_data *tud = bu->data; new = g_strdup_printf("@%s %s", bu->handle, message + (s - cmd[0]) + 2); message = new; if (time(NULL) < tud->last_time + set_getint(&ic->acc->set, "auto_reply_timeout")) in_reply_to = tud->last_id; } } /* If the user runs undo between this request and its response this would delete the second-last Tweet. Prevent that. */ td->last_status_id = 0; twitter_post_status(ic, message, in_reply_to); } else { twitter_log(ic, "Unknown command: %s", cmd[0]); } eof: g_free(new); g_free(cmds); } void twitter_log(struct im_connection *ic, char *format, ... ) { struct twitter_data *td = ic->proto_data; va_list params; char *text; va_start(params, format); text = g_strdup_vprintf(format, params); va_end(params); if (td->timeline_gc) imcb_chat_log(td->timeline_gc, "%s", text); else imcb_log(ic, "%s", text); g_free(text); } void twitter_initmodule() { struct prpl *ret = g_new0(struct prpl, 1); ret->options = OPT_NOOTR; ret->name = "twitter"; ret->login = twitter_login; ret->init = twitter_init; ret->logout = twitter_logout; ret->buddy_msg = twitter_buddy_msg; ret->get_info = twitter_get_info; ret->add_buddy = twitter_add_buddy; ret->remove_buddy = twitter_remove_buddy; ret->chat_msg = twitter_chat_msg; ret->chat_invite = twitter_chat_invite; ret->chat_leave = twitter_chat_leave; ret->keepalive = twitter_keepalive; ret->add_permit = twitter_add_permit; ret->rem_permit = twitter_rem_permit; ret->add_deny = twitter_add_deny; ret->rem_deny = twitter_rem_deny; ret->buddy_data_add = twitter_buddy_data_add; ret->buddy_data_free = twitter_buddy_data_free; ret->handle_cmp = g_strcasecmp; register_protocol(ret); /* And an identi.ca variant: */ ret = g_memdup(ret, sizeof(struct prpl)); ret->name = "identica"; register_protocol(ret); } bitlbee-3.2.1/protocols/twitter/Makefile0000644000175000017500000000145212245474076017706 0ustar wilmerwilmer########################### ## Makefile for BitlBee ## ## ## ## Copyright 2002 Lintux ## ########################### ### DEFINITIONS -include ../../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)protocols/twitter/ endif # [SH] Program variables objects = twitter.o twitter_http.o twitter_lib.o LFLAGS += -r # [SH] Phony targets all: twitter_mod.o check: all lcov: check gcov: gcov *.c .PHONY: all clean distclean clean: rm -f *.o core distclean: clean rm -rf .depend ### MAIN PROGRAM $(objects): ../../Makefile.settings Makefile $(objects): %.o: $(_SRCDIR_)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $(CFLAGS_BITLBEE) $< -o $@ twitter_mod.o: $(objects) @echo '*' Linking twitter_mod.o @$(LD) $(LFLAGS) $(objects) -o twitter_mod.o -include .depend/*.d bitlbee-3.2.1/protocols/twitter/twitter_lib.c0000644000175000017500000010406412245474076020745 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * * Copyright 2009-2010 Geert Mulders * * Copyright 2010-2013 Wilmer van der Gaast * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation, version * * 2.1. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the Free Software Foundation, * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * * ****************************************************************************/ /* For strptime(): */ #if(__sun) #else #define _XOPEN_SOURCE #endif #include "twitter_http.h" #include "twitter.h" #include "bitlbee.h" #include "url.h" #include "misc.h" #include "base64.h" #include "twitter_lib.h" #include "json_util.h" #include #include #define TXL_STATUS 1 #define TXL_USER 2 #define TXL_ID 3 struct twitter_xml_list { int type; gint64 next_cursor; GSList *list; }; struct twitter_xml_user { char *name; char *screen_name; }; struct twitter_xml_status { time_t created_at; char *text; struct twitter_xml_user *user; guint64 id, rt_id; /* Usually equal, with RTs id == *original* id */ guint64 reply_to; }; /** * Frees a twitter_xml_user struct. */ static void txu_free(struct twitter_xml_user *txu) { if (txu == NULL) return; g_free(txu->name); g_free(txu->screen_name); g_free(txu); } /** * Frees a twitter_xml_status struct. */ static void txs_free(struct twitter_xml_status *txs) { if (txs == NULL) return; g_free(txs->text); txu_free(txs->user); g_free(txs); } /** * Free a twitter_xml_list struct. * type is the type of list the struct holds. */ static void txl_free(struct twitter_xml_list *txl) { GSList *l; if (txl == NULL) return; for (l = txl->list; l; l = g_slist_next(l)) { if (txl->type == TXL_STATUS) { txs_free((struct twitter_xml_status *) l->data); } else if (txl->type == TXL_ID) { g_free(l->data); } else if (txl->type == TXL_USER) { txu_free(l->data); } } g_slist_free(txl->list); g_free(txl); } /** * Compare status elements */ static gint twitter_compare_elements(gconstpointer a, gconstpointer b) { struct twitter_xml_status *a_status = (struct twitter_xml_status *) a; struct twitter_xml_status *b_status = (struct twitter_xml_status *) b; if (a_status->created_at < b_status->created_at) { return -1; } else if (a_status->created_at > b_status->created_at) { return 1; } else { return 0; } } /** * Add a buddy if it is not already added, set the status to logged in. */ static void twitter_add_buddy(struct im_connection *ic, char *name, const char *fullname) { struct twitter_data *td = ic->proto_data; // Check if the buddy is already in the buddy list. if (!bee_user_by_handle(ic->bee, ic, name)) { // The buddy is not in the list, add the buddy and set the status to logged in. imcb_add_buddy(ic, name, NULL); imcb_rename_buddy(ic, name, fullname); if (td->flags & TWITTER_MODE_CHAT) { /* Necessary so that nicks always get translated to the exact Twitter username. */ imcb_buddy_nick_hint(ic, name, name); if (td->timeline_gc) imcb_chat_add_buddy(td->timeline_gc, name); } else if (td->flags & TWITTER_MODE_MANY) imcb_buddy_status(ic, name, OPT_LOGGED_IN, NULL, NULL); } } /* Warning: May return a malloc()ed value, which will be free()d on the next call. Only for short-term use. NOT THREADSAFE! */ char *twitter_parse_error(struct http_request *req) { static char *ret = NULL; json_value *root, *err; g_free(ret); ret = NULL; if (req->body_size > 0) { root = json_parse(req->reply_body); err = json_o_get(root, "errors"); if (err && err->type == json_array && (err = err->u.array.values[0]) && err->type == json_object) { const char *msg = json_o_str(err, "message"); if (msg) ret = g_strdup_printf("%s (%s)", req->status_string, msg); } json_value_free(root); } return ret ? ret : req->status_string; } /* WATCH OUT: This function might or might not destroy your connection. Sub-optimal indeed, but just be careful when this returns NULL! */ static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req) { gboolean logging_in = !(ic->flags & OPT_LOGGED_IN); gboolean periodic; struct twitter_data *td = ic->proto_data; json_value *ret; char path[64] = "", *s; if ((s = strchr(req->request, ' '))) { path[sizeof(path)-1] = '\0'; strncpy(path, s + 1, sizeof(path) - 1); if ((s = strchr(path, '?')) || (s = strchr(path, ' '))) *s = '\0'; } /* Kinda nasty. :-( Trying to suppress error messages, but only for periodic (i.e. mentions/timeline) queries. */ periodic = strstr(path, "timeline") || strstr(path, "mentions"); if (req->status_code == 401 && logging_in) { /* IIRC Twitter once had an outage where they were randomly throwing 401s so I'll keep treating this one as fatal only during login. */ imcb_error(ic, "Authentication failure (%s)", twitter_parse_error(req)); imc_logout(ic, FALSE); return NULL; } else if (req->status_code != 200) { // It didn't go well, output the error and return. if (!periodic || logging_in || ++td->http_fails >= 5) twitter_log(ic, "Error: Could not retrieve %s: %s", path, twitter_parse_error(req)); if (logging_in) imc_logout(ic, TRUE); return NULL; } else { td->http_fails = 0; } if ((ret = json_parse(req->reply_body)) == NULL) { imcb_error(ic, "Could not retrieve %s: %s", path, "XML parse error"); } return ret; } static void twitter_http_get_friends_ids(struct http_request *req); /** * Get the friends ids. */ void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor) { // Primitive, but hey! It works... char *args[2]; args[0] = "cursor"; args[1] = g_strdup_printf("%lld", (long long) next_cursor); twitter_http(ic, TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, args, 2); g_free(args[1]); } /** * Fill a list of ids. */ static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl) { json_value *c; int i; // Set the list type. txl->type = TXL_ID; c = json_o_get(node, "ids"); if (!c || c->type != json_array) return FALSE; for (i = 0; i < c->u.array.length; i ++) { if (c->u.array.values[i]->type != json_integer) continue; txl->list = g_slist_prepend(txl->list, g_strdup_printf("%lld", c->u.array.values[i]->u.integer)); } c = json_o_get(node, "next_cursor"); if (c && c->type == json_integer) txl->next_cursor = c->u.integer; else txl->next_cursor = -1; return TRUE; } static void twitter_get_users_lookup(struct im_connection *ic); /** * Callback for getting the friends ids. */ static void twitter_http_get_friends_ids(struct http_request *req) { struct im_connection *ic; json_value *parsed; struct twitter_xml_list *txl; struct twitter_data *td; ic = req->data; // Check if the connection is still active. if (!g_slist_find(twitter_connections, ic)) return; td = ic->proto_data; txl = g_new0(struct twitter_xml_list, 1); txl->list = td->follow_ids; // Parse the data. if (!(parsed = twitter_parse_response(ic, req))) return; twitter_xt_get_friends_id_list(parsed, txl); json_value_free(parsed); td->follow_ids = txl->list; if (txl->next_cursor) /* These were just numbers. Up to 4000 in a response AFAIK so if we get here we may be using a spammer account. \o/ */ twitter_get_friends_ids(ic, txl->next_cursor); else /* Now to convert all those numbers into names.. */ twitter_get_users_lookup(ic); txl->list = NULL; txl_free(txl); } static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl); static void twitter_http_get_users_lookup(struct http_request *req); static void twitter_get_users_lookup(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; char *args[2] = { "user_id", NULL, }; GString *ids = g_string_new(""); int i; /* We can request up to 100 users at a time. */ for (i = 0; i < 100 && td->follow_ids; i ++) { g_string_append_printf(ids, ",%s", (char*) td->follow_ids->data); g_free(td->follow_ids->data); td->follow_ids = g_slist_remove(td->follow_ids, td->follow_ids->data); } if (ids->len > 0) { args[1] = ids->str + 1; /* POST, because I think ids can be up to 1KB long. */ twitter_http(ic, TWITTER_USERS_LOOKUP_URL, twitter_http_get_users_lookup, ic, 1, args, 2); } else { /* We have all users. Continue with login. (Get statuses.) */ td->flags |= TWITTER_HAVE_FRIENDS; twitter_login_finish(ic); } g_string_free(ids, TRUE); } /** * Callback for getting (twitter)friends... * * Be afraid, be very afraid! This function will potentially add hundreds of "friends". "Who has * hundreds of friends?" you wonder? You probably not, since you are reading the source of * BitlBee... Get a life and meet new people! */ static void twitter_http_get_users_lookup(struct http_request *req) { struct im_connection *ic = req->data; json_value *parsed; struct twitter_xml_list *txl; GSList *l = NULL; struct twitter_xml_user *user; // Check if the connection is still active. if (!g_slist_find(twitter_connections, ic)) return; txl = g_new0(struct twitter_xml_list, 1); txl->list = NULL; // Get the user list from the parsed xml feed. if (!(parsed = twitter_parse_response(ic, req))) return; twitter_xt_get_users(parsed, txl); json_value_free(parsed); // Add the users as buddies. for (l = txl->list; l; l = g_slist_next(l)) { user = l->data; twitter_add_buddy(ic, user->screen_name, user->name); } // Free the structure. txl_free(txl); twitter_get_users_lookup(ic); } struct twitter_xml_user *twitter_xt_get_user(const json_value *node) { struct twitter_xml_user *txu; txu = g_new0(struct twitter_xml_user, 1); txu->name = g_strdup(json_o_str(node, "name")); txu->screen_name = g_strdup(json_o_str(node, "screen_name")); return txu; } /** * Function to fill a twitter_xml_list struct. * It sets: * - all s from the element. */ static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl) { struct twitter_xml_user *txu; int i; // Set the type of the list. txl->type = TXL_USER; if (!node || node->type != json_array) return FALSE; // The root node should hold the list of users // Walk over the nodes children. for (i = 0; i < node->u.array.length; i ++) { txu = twitter_xt_get_user(node->u.array.values[i]); if (txu) txl->list = g_slist_prepend(txl->list, txu); } return TRUE; } #ifdef __GLIBC__ #define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S %z %Y" #else #define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y" #endif static char* expand_entities(char* text, const json_value *entities); /** * Function to fill a twitter_xml_status struct. * It sets: * - the status text and * - the created_at timestamp and * - the status id and * - the user in a twitter_xml_user struct. */ static struct twitter_xml_status *twitter_xt_get_status(const json_value *node) { struct twitter_xml_status *txs; const json_value *rt = NULL, *entities = NULL; if (node->type != json_object) return FALSE; txs = g_new0(struct twitter_xml_status, 1); JSON_O_FOREACH (node, k, v) { if (strcmp("text", k) == 0 && v->type == json_string) { txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); strip_html(txs->text); } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) { rt = v; } else if (strcmp("created_at", k) == 0 && v->type == json_string) { struct tm parsed; /* Very sensitive to changes to the formatting of this field. :-( Also assumes the timezone used is UTC since C time handling functions suck. */ if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) txs->created_at = mktime_utc(&parsed); } else if (strcmp("user", k) == 0 && v->type == json_object) { txs->user = twitter_xt_get_user(v); } else if (strcmp("id", k) == 0 && v->type == json_integer) { txs->rt_id = txs->id = v->u.integer; } else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) { txs->reply_to = v->u.integer; } else if (strcmp("entities", k) == 0 && v->type == json_object) { entities = v; } } /* If it's a (truncated) retweet, get the original. Even if the API claims it wasn't truncated because it may be lying. */ if (rt) { struct twitter_xml_status *rtxs = twitter_xt_get_status(rt); if (rtxs) { g_free(txs->text); txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text); txs->id = rtxs->id; txs_free(rtxs); } } else if (entities) { txs->text = expand_entities(txs->text, entities); } if (txs->text && txs->user && txs->id) return txs; txs_free(txs); return NULL; } /** * Function to fill a twitter_xml_status struct (DM variant). */ static struct twitter_xml_status *twitter_xt_get_dm(const json_value *node) { struct twitter_xml_status *txs; const json_value *entities = NULL; if (node->type != json_object) return FALSE; txs = g_new0(struct twitter_xml_status, 1); JSON_O_FOREACH (node, k, v) { if (strcmp("text", k) == 0 && v->type == json_string) { txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); strip_html(txs->text); } else if (strcmp("created_at", k) == 0 && v->type == json_string) { struct tm parsed; /* Very sensitive to changes to the formatting of this field. :-( Also assumes the timezone used is UTC since C time handling functions suck. */ if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) txs->created_at = mktime_utc(&parsed); } else if (strcmp("sender", k) == 0 && v->type == json_object) { txs->user = twitter_xt_get_user(v); } else if (strcmp("id", k) == 0 && v->type == json_integer) { txs->id = v->u.integer; } } if (entities) { txs->text = expand_entities(txs->text, entities); } if (txs->text && txs->user && txs->id) return txs; txs_free(txs); return NULL; } static char* expand_entities(char* text, const json_value *entities) { JSON_O_FOREACH (entities, k, v) { int i; if (v->type != json_array) continue; if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0) continue; for (i = 0; i < v->u.array.length; i ++) { if (v->u.array.values[i]->type != json_object) continue; const char *kort = json_o_str(v->u.array.values[i], "url"); const char *disp = json_o_str(v->u.array.values[i], "display_url"); char *pos, *new; if (!kort || !disp || !(pos = strstr(text, kort))) continue; *pos = '\0'; new = g_strdup_printf("%s%s <%s>%s", text, kort, disp, pos + strlen(kort)); g_free(text); text = new; } } return text; } /** * Function to fill a twitter_xml_list struct. * It sets: * - all es within the element and * - the next_cursor. */ static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node, struct twitter_xml_list *txl) { struct twitter_xml_status *txs; int i; // Set the type of the list. txl->type = TXL_STATUS; if (node->type != json_array) return FALSE; // The root node should hold the list of statuses // Walk over the nodes children. for (i = 0; i < node->u.array.length; i ++) { txs = twitter_xt_get_status(node->u.array.values[i]); if (!txs) continue; txl->list = g_slist_prepend(txl->list, txs); } return TRUE; } /* Will log messages either way. Need to keep track of IDs for stream deduping. Plus, show_ids is on by default and I don't see why anyone would disable it. */ static char *twitter_msg_add_id(struct im_connection *ic, struct twitter_xml_status *txs, const char *prefix) { struct twitter_data *td = ic->proto_data; int reply_to = -1; bee_user_t *bu; if (txs->reply_to) { int i; for (i = 0; i < TWITTER_LOG_LENGTH; i++) if (td->log[i].id == txs->reply_to) { reply_to = i; break; } } if (txs->user && txs->user->screen_name && (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { struct twitter_user_data *tud = bu->data; if (txs->id > tud->last_id) { tud->last_id = txs->id; tud->last_time = txs->created_at; } } td->log_id = (td->log_id + 1) % TWITTER_LOG_LENGTH; td->log[td->log_id].id = txs->id; td->log[td->log_id].bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name); /* This is all getting hairy. :-( If we RT'ed something ourselves, remember OUR id instead so undo will work. In other cases, the original tweet's id should be remembered for deduplicating. */ if (strcmp(txs->user->screen_name, td->user) == 0) td->log[td->log_id].id = txs->rt_id; if (set_getbool(&ic->acc->set, "show_ids")) { if (reply_to != -1) return g_strdup_printf("\002[\002%02x->%02x\002]\002 %s%s", td->log_id, reply_to, prefix, txs->text); else return g_strdup_printf("\002[\002%02x\002]\002 %s%s", td->log_id, prefix, txs->text); } else { if (*prefix) return g_strconcat(prefix, txs->text, NULL); else return NULL; } } /** * Function that is called to see the statuses in a groupchat window. */ static void twitter_status_show_chat(struct im_connection *ic, struct twitter_xml_status *status) { struct twitter_data *td = ic->proto_data; struct groupchat *gc; gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0; char *msg; // Create a new groupchat if it does not exsist. gc = twitter_groupchat_init(ic); if (!me) /* MUST be done before twitter_msg_add_id() to avoid #872. */ twitter_add_buddy(ic, status->user->screen_name, status->user->name); msg = twitter_msg_add_id(ic, status, ""); // Say it! if (me) { imcb_chat_log(gc, "You: %s", msg ? msg : status->text); } else { imcb_chat_msg(gc, status->user->screen_name, msg ? msg : status->text, 0, status->created_at); } g_free(msg); } /** * Function that is called to see statuses as private messages. */ static void twitter_status_show_msg(struct im_connection *ic, struct twitter_xml_status *status) { struct twitter_data *td = ic->proto_data; char from[MAX_STRING] = ""; char *prefix = NULL, *text = NULL; gboolean me = g_strcasecmp(td->user, status->user->screen_name) == 0; if (td->flags & TWITTER_MODE_ONE) { g_snprintf(from, sizeof(from) - 1, "%s_%s", td->prefix, ic->acc->user); from[MAX_STRING - 1] = '\0'; } if (td->flags & TWITTER_MODE_ONE) prefix = g_strdup_printf("\002<\002%s\002>\002 ", status->user->screen_name); else if (!me) twitter_add_buddy(ic, status->user->screen_name, status->user->name); else prefix = g_strdup("You: "); text = twitter_msg_add_id(ic, status, prefix ? prefix : ""); imcb_buddy_msg(ic, *from ? from : status->user->screen_name, text ? text : status->text, 0, status->created_at); g_free(text); g_free(prefix); } static void twitter_status_show(struct im_connection *ic, struct twitter_xml_status *status) { struct twitter_data *td = ic->proto_data; if (status->user == NULL || status->text == NULL) return; /* Grrrr. Would like to do this during parsing, but can't access settings from there. */ if (set_getbool(&ic->acc->set, "strip_newlines")) strip_newlines(status->text); if (td->flags & TWITTER_MODE_CHAT) twitter_status_show_chat(ic, status); else twitter_status_show_msg(ic, status); // Update the timeline_id to hold the highest id, so that by the next request // we won't pick up the updates already in the list. td->timeline_id = MAX(td->timeline_id, status->rt_id); } static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o); static void twitter_http_stream(struct http_request *req) { struct im_connection *ic = req->data; struct twitter_data *td; json_value *parsed; int len = 0; char c, *nl; if (!g_slist_find(twitter_connections, ic)) return; ic->flags |= OPT_PONGED; td = ic->proto_data; if ((req->flags & HTTPC_EOF) || !req->reply_body) { td->stream = NULL; imcb_error(ic, "Stream closed (%s)", req->status_string); imc_logout(ic, TRUE); return; } /* MUST search for CRLF, not just LF: https://dev.twitter.com/docs/streaming-apis/processing#Parsing_responses */ if (!(nl = strstr(req->reply_body, "\r\n"))) return; len = nl - req->reply_body; if (len > 0) { c = req->reply_body[len]; req->reply_body[len] = '\0'; if ((parsed = json_parse(req->reply_body))) { twitter_stream_handle_object(ic, parsed); } json_value_free(parsed); req->reply_body[len] = c; } http_flush_bytes(req, len + 2); /* One notification might bring multiple events! */ if (req->body_size > 0) twitter_http_stream(req); } static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o); static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs); static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o) { struct twitter_data *td = ic->proto_data; struct twitter_xml_status *txs; json_value *c; if ((txs = twitter_xt_get_status(o))) { gboolean ret = twitter_stream_handle_status(ic, txs); txs_free(txs); return ret; } else if ((c = json_o_get(o, "direct_message")) && (txs = twitter_xt_get_dm(c))) { if (strcmp(txs->user->screen_name, td->user) != 0) imcb_buddy_msg(ic, txs->user->screen_name, txs->text, 0, txs->created_at); txs_free(txs); return TRUE; } else if ((c = json_o_get(o, "event")) && c->type == json_string) { twitter_stream_handle_event(ic, o); return TRUE; } else if ((c = json_o_get(o, "disconnect")) && c->type == json_object) { /* HACK: Because we're inside an event handler, we can't just disconnect here. Instead, just change the HTTP status string into a Twitter status string. */ char *reason = json_o_strdup(c, "reason"); if (reason) { g_free(td->stream->status_string); td->stream->status_string = reason; } return TRUE; } return FALSE; } static gboolean twitter_stream_handle_status(struct im_connection *ic, struct twitter_xml_status *txs) { struct twitter_data *td = ic->proto_data; int i; for (i = 0; i < TWITTER_LOG_LENGTH; i++) { if (td->log[i].id == txs->id) { /* Got a duplicate (RT, probably). Drop it. */ return TRUE; } } if (!(strcmp(txs->user->screen_name, td->user) == 0 || set_getbool(&ic->acc->set, "fetch_mentions") || bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { /* Tweet is from an unknown person and the user does not want to see @mentions, so drop it. twitter_stream_handle_event() picks up new follows so this simple filter should be safe. */ /* TODO: The streaming API seems to do poor @mention matching. I.e. I'm getting mentions for @WilmerSomething, not just for @Wilmer. But meh. You want spam, you get spam. */ return TRUE; } twitter_status_show(ic, txs); return TRUE; } static gboolean twitter_stream_handle_event(struct im_connection *ic, json_value *o) { struct twitter_data *td = ic->proto_data; json_value *source = json_o_get(o, "source"); json_value *target = json_o_get(o, "target"); const char *type = json_o_str(o, "event"); if (!type || !source || source->type != json_object || !target || target->type != json_object) { return FALSE; } if (strcmp(type, "follow") == 0) { struct twitter_xml_user *us = twitter_xt_get_user(source); struct twitter_xml_user *ut = twitter_xt_get_user(target); if (strcmp(us->screen_name, td->user) == 0) { twitter_add_buddy(ic, ut->screen_name, ut->name); } txu_free(us); txu_free(ut); } return TRUE; } gboolean twitter_open_stream(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; char *args[2] = {"with", "followings"}; if ((td->stream = twitter_http(ic, TWITTER_USER_STREAM_URL, twitter_http_stream, ic, 0, args, 2))) { /* This flag must be enabled or we'll get no data until EOF (which err, kind of, defeats the purpose of a streaming API). */ td->stream->flags |= HTTPC_STREAMING; return TRUE; } return FALSE; } static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor); static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor); /** * Get the timeline with optionally mentions */ gboolean twitter_get_timeline(struct im_connection *ic, gint64 next_cursor) { struct twitter_data *td = ic->proto_data; gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions"); if (td->flags & TWITTER_DOING_TIMELINE) { if (++td->http_fails >= 5) { imcb_error(ic, "Fetch timeout (%d)", td->flags); imc_logout(ic, TRUE); return FALSE; } } td->flags |= TWITTER_DOING_TIMELINE; twitter_get_home_timeline(ic, next_cursor); if (include_mentions) { twitter_get_mentions(ic, next_cursor); } return TRUE; } /** * Call this one after receiving timeline/mentions. Show to user once we have * both. */ void twitter_flush_timeline(struct im_connection *ic) { struct twitter_data *td = ic->proto_data; gboolean include_mentions = set_getbool(&ic->acc->set, "fetch_mentions"); int show_old_mentions = set_getint(&ic->acc->set, "show_old_mentions"); struct twitter_xml_list *home_timeline = td->home_timeline_obj; struct twitter_xml_list *mentions = td->mentions_obj; guint64 last_id = 0; GSList *output = NULL; GSList *l; imcb_connected(ic); if (!(td->flags & TWITTER_GOT_TIMELINE)) { return; } if (include_mentions && !(td->flags & TWITTER_GOT_MENTIONS)) { return; } if (home_timeline && home_timeline->list) { for (l = home_timeline->list; l; l = g_slist_next(l)) { output = g_slist_insert_sorted(output, l->data, twitter_compare_elements); } } if (include_mentions && mentions && mentions->list) { for (l = mentions->list; l; l = g_slist_next(l)) { if (show_old_mentions < 1 && output && twitter_compare_elements(l->data, output->data) < 0) { continue; } output = g_slist_insert_sorted(output, l->data, twitter_compare_elements); } } // See if the user wants to see the messages in a groupchat window or as private messages. while (output) { struct twitter_xml_status *txs = output->data; if (txs->id != last_id) twitter_status_show(ic, txs); last_id = txs->id; output = g_slist_remove(output, txs); } txl_free(home_timeline); txl_free(mentions); td->flags &= ~(TWITTER_DOING_TIMELINE | TWITTER_GOT_TIMELINE | TWITTER_GOT_MENTIONS); td->home_timeline_obj = td->mentions_obj = NULL; } static void twitter_http_get_home_timeline(struct http_request *req); static void twitter_http_get_mentions(struct http_request *req); /** * Get the timeline. */ static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) { struct twitter_data *td = ic->proto_data; txl_free(td->home_timeline_obj); td->home_timeline_obj = NULL; td->flags &= ~TWITTER_GOT_TIMELINE; char *args[6]; args[0] = "cursor"; args[1] = g_strdup_printf("%lld", (long long) next_cursor); args[2] = "include_entities"; args[3] = "true"; if (td->timeline_id) { args[4] = "since_id"; args[5] = g_strdup_printf("%llu", (long long unsigned int) td->timeline_id); } if (twitter_http(ic, TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, args, td->timeline_id ? 6 : 4) == NULL) { if (++td->http_fails >= 5) imcb_error(ic, "Could not retrieve %s: %s", TWITTER_HOME_TIMELINE_URL, "connection failed"); td->flags |= TWITTER_GOT_TIMELINE; twitter_flush_timeline(ic); } g_free(args[1]); if (td->timeline_id) { g_free(args[5]); } } /** * Get mentions. */ static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor) { struct twitter_data *td = ic->proto_data; txl_free(td->mentions_obj); td->mentions_obj = NULL; td->flags &= ~TWITTER_GOT_MENTIONS; char *args[6]; args[0] = "cursor"; args[1] = g_strdup_printf("%lld", (long long) next_cursor); args[2] = "include_entities"; args[3] = "true"; if (td->timeline_id) { args[4] = "since_id"; args[5] = g_strdup_printf("%llu", (long long unsigned int) td->timeline_id); } else { args[4] = "count"; args[5] = g_strdup_printf("%d", set_getint(&ic->acc->set, "show_old_mentions")); } if (twitter_http(ic, TWITTER_MENTIONS_URL, twitter_http_get_mentions, ic, 0, args, 6) == NULL) { if (++td->http_fails >= 5) imcb_error(ic, "Could not retrieve %s: %s", TWITTER_MENTIONS_URL, "connection failed"); td->flags |= TWITTER_GOT_MENTIONS; twitter_flush_timeline(ic); } g_free(args[1]); g_free(args[5]); } /** * Callback for getting the home timeline. */ static void twitter_http_get_home_timeline(struct http_request *req) { struct im_connection *ic = req->data; struct twitter_data *td; json_value *parsed; struct twitter_xml_list *txl; // Check if the connection is still active. if (!g_slist_find(twitter_connections, ic)) return; td = ic->proto_data; txl = g_new0(struct twitter_xml_list, 1); txl->list = NULL; // The root node should hold the list of statuses if (!(parsed = twitter_parse_response(ic, req))) goto end; twitter_xt_get_status_list(ic, parsed, txl); json_value_free(parsed); td->home_timeline_obj = txl; end: if (!g_slist_find(twitter_connections, ic)) return; td->flags |= TWITTER_GOT_TIMELINE; twitter_flush_timeline(ic); } /** * Callback for getting mentions. */ static void twitter_http_get_mentions(struct http_request *req) { struct im_connection *ic = req->data; struct twitter_data *td; json_value *parsed; struct twitter_xml_list *txl; // Check if the connection is still active. if (!g_slist_find(twitter_connections, ic)) return; td = ic->proto_data; txl = g_new0(struct twitter_xml_list, 1); txl->list = NULL; // The root node should hold the list of statuses if (!(parsed = twitter_parse_response(ic, req))) goto end; twitter_xt_get_status_list(ic, parsed, txl); json_value_free(parsed); td->mentions_obj = txl; end: if (!g_slist_find(twitter_connections, ic)) return; td->flags |= TWITTER_GOT_MENTIONS; twitter_flush_timeline(ic); } /** * Callback to use after sending a POST request to twitter. * (Generic, used for a few kinds of queries.) */ static void twitter_http_post(struct http_request *req) { struct im_connection *ic = req->data; struct twitter_data *td; json_value *parsed, *id; // Check if the connection is still active. if (!g_slist_find(twitter_connections, ic)) return; td = ic->proto_data; td->last_status_id = 0; if (!(parsed = twitter_parse_response(ic, req))) return; if ((id = json_o_get(parsed, "id")) && id->type == json_integer) { td->last_status_id = id->u.integer; } json_value_free(parsed); if (req->flags & TWITTER_HTTP_USER_ACK) twitter_log(ic, "Command processed successfully"); } /** * Function to POST a new status to twitter. */ void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to) { char *args[4] = { "status", msg, "in_reply_to_status_id", g_strdup_printf("%llu", (unsigned long long) in_reply_to) }; twitter_http(ic, TWITTER_STATUS_UPDATE_URL, twitter_http_post, ic, 1, args, in_reply_to ? 4 : 2); g_free(args[3]); } /** * Function to POST a new message to twitter. */ void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg) { char *args[4]; args[0] = "screen_name"; args[1] = who; args[2] = "text"; args[3] = msg; // Use the same callback as for twitter_post_status, since it does basically the same. twitter_http(ic, TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post, ic, 1, args, 4); } void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create) { char *args[2]; args[0] = "screen_name"; args[1] = who; twitter_http(ic, create ? TWITTER_FRIENDSHIPS_CREATE_URL : TWITTER_FRIENDSHIPS_DESTROY_URL, twitter_http_post, ic, 1, args, 2); } void twitter_status_destroy(struct im_connection *ic, guint64 id) { char *url; url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL, (unsigned long long) id, ".json"); twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, TWITTER_HTTP_USER_ACK); g_free(url); } void twitter_status_retweet(struct im_connection *ic, guint64 id) { char *url; url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL, (unsigned long long) id, ".json"); twitter_http_f(ic, url, twitter_http_post, ic, 1, NULL, 0, TWITTER_HTTP_USER_ACK); g_free(url); } /** * Report a user for sending spam. */ void twitter_report_spam(struct im_connection *ic, char *screen_name) { char *args[2] = { "screen_name", NULL, }; args[1] = screen_name; twitter_http_f(ic, TWITTER_REPORT_SPAM_URL, twitter_http_post, ic, 1, args, 2, TWITTER_HTTP_USER_ACK); } /** * Favourite a tweet. */ void twitter_favourite_tweet(struct im_connection *ic, guint64 id) { char *args[2] = { "id", NULL, }; args[1] = g_strdup_printf("%llu", (unsigned long long) id); twitter_http_f(ic, TWITTER_FAVORITE_CREATE_URL, twitter_http_post, ic, 1, args, 2, TWITTER_HTTP_USER_ACK); g_free(args[1]); } bitlbee-3.2.1/protocols/twitter/twitter.h0000644000175000017500000000671712245474076020132 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * * Copyright 2009-2010 Geert Mulders * * Copyright 2010-2012 Wilmer van der Gaast * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation, version * * 2.1. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the Free Software Foundation, * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * * ****************************************************************************/ #include "nogaim.h" #ifndef _TWITTER_H #define _TWITTER_H #ifdef DEBUG_TWITTER #define debug( text... ) imcb_log( ic, text ); #else #define debug( text... ) #endif typedef enum { TWITTER_HAVE_FRIENDS = 0x00001, TWITTER_MODE_ONE = 0x00002, TWITTER_MODE_MANY = 0x00004, TWITTER_MODE_CHAT = 0x00008, TWITTER_DOING_TIMELINE = 0x10000, TWITTER_GOT_TIMELINE = 0x20000, TWITTER_GOT_MENTIONS = 0x40000, } twitter_flags_t; struct twitter_log_data; struct twitter_data { char* user; struct oauth_info *oauth_info; gpointer home_timeline_obj; gpointer mentions_obj; guint64 timeline_id; GSList *follow_ids; guint64 last_status_id; /* For undo */ gint main_loop_id; struct http_request *stream; struct groupchat *timeline_gc; gint http_fails; twitter_flags_t flags; /* set base_url */ gboolean url_ssl; int url_port; char *url_host; char *url_path; char *prefix; /* Used to generate contact + channel name. */ /* set show_ids */ struct twitter_log_data *log; int log_id; }; struct twitter_user_data { guint64 last_id; time_t last_time; }; #define TWITTER_LOG_LENGTH 256 struct twitter_log_data { guint64 id; struct bee_user *bu; /* DANGER: can be a dead pointer. Check it first. */ }; /** * This has the same function as the msn_connections GSList. We use this to * make sure the connection is still alive in callbacks before we do anything * else. */ extern GSList *twitter_connections; void twitter_login_finish( struct im_connection *ic ); struct http_request; char *twitter_parse_error( struct http_request *req ); void twitter_log(struct im_connection *ic, char *format, ... ); struct groupchat *twitter_groupchat_init(struct im_connection *ic); #endif //_TWITTER_H bitlbee-3.2.1/protocols/twitter/twitter_lib.h0000644000175000017500000001115512245474076020750 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * * Copyright 2009 Geert Mulders * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation, version * * 2.1. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the Free Software Foundation, * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * * ****************************************************************************/ #ifndef _TWITTER_LIB_H #define _TWITTER_LIB_H #include "nogaim.h" #include "twitter_http.h" #define TWITTER_API_URL "https://api.twitter.com/1.1" #define IDENTICA_API_URL "https://identi.ca/api" /* Status URLs */ #define TWITTER_STATUS_UPDATE_URL "/statuses/update.json" #define TWITTER_STATUS_SHOW_URL "/statuses/show/" #define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/" #define TWITTER_STATUS_RETWEET_URL "/statuses/retweet/" /* Timeline URLs */ #define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.json" #define TWITTER_FEATURED_USERS_URL "/statuses/featured.json" #define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.json" #define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.json" #define TWITTER_MENTIONS_URL "/statuses/mentions_timeline.json" #define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.json" /* Users URLs */ #define TWITTER_USERS_LOOKUP_URL "/users/lookup.json" /* Direct messages URLs */ #define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.json" #define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.json" #define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.json" #define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/" /* Friendships URLs */ #define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.json" #define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.json" #define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.json" /* Social graphs URLs */ #define TWITTER_FRIENDS_IDS_URL "/friends/ids.json" #define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.json" /* Account URLs */ #define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.json" /* Favorites URLs */ #define TWITTER_FAVORITES_GET_URL "/favorites.json" #define TWITTER_FAVORITE_CREATE_URL "/favorites/create.json" #define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy.json" /* Block URLs */ #define TWITTER_BLOCKS_CREATE_URL "/blocks/create/" #define TWITTER_BLOCKS_DESTROY_URL "/blocks/destroy/" /* Report spam */ #define TWITTER_REPORT_SPAM_URL "/users/report_spam.json" #define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json" gboolean twitter_open_stream(struct im_connection *ic); gboolean twitter_get_timeline(struct im_connection *ic, gint64 next_cursor); void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor); void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor); void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to); void twitter_direct_messages_new(struct im_connection *ic, char *who, char *message); void twitter_friendships_create_destroy(struct im_connection *ic, char *who, int create); void twitter_status_destroy(struct im_connection *ic, guint64 id); void twitter_status_retweet(struct im_connection *ic, guint64 id); void twitter_report_spam(struct im_connection *ic, char *screen_name); void twitter_favourite_tweet(struct im_connection *ic, guint64 id); #endif //_TWITTER_LIB_H bitlbee-3.2.1/protocols/twitter/twitter_http.h0000644000175000017500000000465312245474076021166 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * * Copyright 2009 Geert Mulders * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation, version * * 2.1. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the Free Software Foundation, * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * * ****************************************************************************/ #ifndef _TWITTER_HTTP_H #define _TWITTER_HTTP_H #include "nogaim.h" #include "http_client.h" typedef enum { /* With this set, twitter_http_post() will post a generic confirmation message to the user. */ TWITTER_HTTP_USER_ACK = 0x1000000, } twitter_http_flags_t; struct oauth_info; struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, gpointer data, int is_post, char** arguments, int arguments_len); struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func, gpointer data, int is_post, char** arguments, int arguments_len, twitter_http_flags_t flags); #endif //_TWITTER_HTTP_H bitlbee-3.2.1/protocols/twitter/twitter_http.c0000644000175000017500000001377112245474076021162 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Simple module to facilitate twitter functionality. * * * * Copyright 2009 Geert Mulders * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation, version * * 2.1. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this library; if not, write to the Free Software Foundation, * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * * ****************************************************************************/ /***************************************************************************\ * * * Some funtions within this file have been copied from other files within * * BitlBee. * * * ****************************************************************************/ #include "twitter.h" #include "bitlbee.h" #include "url.h" #include "misc.h" #include "base64.h" #include "oauth.h" #include #include #include "twitter_http.h" static char *twitter_url_append(char *url, char *key, char *value); /** * Do a request. * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c */ struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, gpointer data, int is_post, char **arguments, int arguments_len) { struct twitter_data *td = ic->proto_data; char *tmp; GString *request = g_string_new(""); void *ret; char *url_arguments; url_t *base_url = NULL; url_arguments = g_strdup(""); // Construct the url arguments. if (arguments_len != 0) { int i; for (i = 0; i < arguments_len; i += 2) { tmp = twitter_url_append(url_arguments, arguments[i], arguments[i + 1]); g_free(url_arguments); url_arguments = tmp; } } if (strstr(url_string, "://")) { base_url = g_new0(url_t, 1); if (!url_set(base_url, url_string)) { g_free(base_url); return NULL; } } // Make the request. g_string_printf(request, "%s %s%s%s%s HTTP/1.1\r\n" "Host: %s\r\n" "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n", is_post ? "POST" : "GET", base_url ? base_url->file : td->url_path, base_url ? "" : url_string, is_post ? "" : "?", is_post ? "" : url_arguments, base_url ? base_url->host : td->url_host); // If a pass and user are given we append them to the request. if (td->oauth_info) { char *full_header; char *full_url; if (base_url) full_url = g_strdup(url_string); else full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL); full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET", full_url, url_arguments); g_string_append_printf(request, "Authorization: %s\r\n", full_header); g_free(full_header); g_free(full_url); } else { char userpass[strlen(ic->acc->user) + 2 + strlen(ic->acc->pass)]; char *userpass_base64; g_snprintf(userpass, sizeof(userpass), "%s:%s", ic->acc->user, ic->acc->pass); userpass_base64 = base64_encode((unsigned char *) userpass, strlen(userpass)); g_string_append_printf(request, "Authorization: Basic %s\r\n", userpass_base64); g_free(userpass_base64); } // Do POST stuff.. if (is_post) { // Append the Content-Type and url-encoded arguments. g_string_append_printf(request, "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: %zd\r\n\r\n%s", strlen(url_arguments), url_arguments); } else { // Append an extra \r\n to end the request... g_string_append(request, "\r\n"); } if (base_url) ret = http_dorequest(base_url->host, base_url->port, base_url->proto == PROTO_HTTPS, request->str, func, data); else ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data); g_free(url_arguments); g_string_free(request, TRUE); g_free(base_url); return ret; } struct http_request *twitter_http_f(struct im_connection *ic, char *url_string, http_input_function func, gpointer data, int is_post, char **arguments, int arguments_len, twitter_http_flags_t flags) { struct http_request *ret = twitter_http(ic, url_string, func, data, is_post, arguments, arguments_len); if (ret) ret->flags |= flags; return ret; } static char *twitter_url_append(char *url, char *key, char *value) { char *key_encoded = g_strndup(key, 3 * strlen(key)); http_encode(key_encoded); char *value_encoded = g_strndup(value, 3 * strlen(value)); http_encode(value_encoded); char *retval; if (strlen(url) != 0) retval = g_strdup_printf("%s&%s=%s", url, key_encoded, value_encoded); else retval = g_strdup_printf("%s=%s", key_encoded, value_encoded); g_free(key_encoded); g_free(value_encoded); return retval; } bitlbee-3.2.1/protocols/account.h0000644000175000017500000000472712245474076016361 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 Wilmer van der Gaast and others * \********************************************************************/ /* Account management functions */ /* 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 _ACCOUNT_H #define _ACCOUNT_H typedef struct account { struct prpl *prpl; char *user; char *pass; char *server; char *tag; int auto_connect; int auto_reconnect_delay; int reconnect; int flags; set_t *set; GHashTable *nicks; struct bee *bee; struct im_connection *ic; struct account *next; } account_t; account_t *account_add( bee_t *bee, struct prpl *prpl, char *user, char *pass ); account_t *account_get( bee_t *bee, const char *id ); account_t *account_by_tag( bee_t *bee, const char *tag ); void account_del( bee_t *bee, account_t *acc ); void account_on( bee_t *bee, account_t *a ); void account_off( bee_t *bee, account_t *a ); char *set_eval_account( set_t *set, char *value ); char *set_eval_account_reconnect_delay( set_t *set, char *value ); int account_reconnect_delay( account_t *a ); typedef enum { ACC_SET_OFFLINE_ONLY = 0x02, /* Allow changes only if the acct is offline. */ ACC_SET_ONLINE_ONLY = 0x04, /* Allow changes only if the acct is online. */ } account_set_flag_t; typedef enum { ACC_FLAG_AWAY_MESSAGE = 0x01, /* Supports away messages instead of just states. */ ACC_FLAG_STATUS_MESSAGE = 0x02, /* Supports status messages (without being away). */ ACC_FLAG_HANDLE_DOMAINS = 0x04, /* Contact handles need a domain portion. */ } account_flag_t; #endif bitlbee-3.2.1/protocols/jabber/0000755000175000017500000000000012245477444015771 5ustar wilmerwilmerbitlbee-3.2.1/protocols/jabber/jabber.c0000644000175000017500000004476612245474076017401 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - Main file * * * * Copyright 2006-2013 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include #include #include #include #include #include "ssl_client.h" #include "xmltree.h" #include "bitlbee.h" #include "jabber.h" #include "oauth.h" #include "md5.h" GSList *jabber_connections; /* First enty is the default */ static const int jabber_port_list[] = { 5222, 5223, 5220, 5221, 5224, 5225, 5226, 5227, 5228, 5229, 80, 443, 0 }; static void jabber_init( account_t *acc ) { set_t *s; char str[16]; s = set_add( &acc->set, "activity_timeout", "600", set_eval_int, acc ); s = set_add( &acc->set, "oauth", "false", set_eval_oauth, acc ); g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] ); s = set_add( &acc->set, "port", str, set_eval_int, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add( &acc->set, "priority", "0", set_eval_priority, acc ); s = set_add( &acc->set, "proxy", ";", NULL, acc ); s = set_add( &acc->set, "resource", "BitlBee", NULL, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add( &acc->set, "resource_select", "activity", NULL, acc ); s = set_add( &acc->set, "sasl", "true", set_eval_bool, acc ); s->flags |= ACC_SET_OFFLINE_ONLY | SET_HIDDEN_DEFAULT; s = set_add( &acc->set, "server", NULL, set_eval_account, acc ); s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY | SET_NULL_OK; s = set_add( &acc->set, "ssl", "false", set_eval_bool, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add( &acc->set, "tls", "true", set_eval_tls, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add( &acc->set, "tls_verify", "true", set_eval_bool, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add( &acc->set, "user_agent", "BitlBee", NULL, acc ); s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE | ACC_FLAG_HANDLE_DOMAINS; } static void jabber_generate_id_hash( struct jabber_data *jd ); static void jabber_login( account_t *acc ) { struct im_connection *ic = imcb_new( acc ); struct jabber_data *jd = g_new0( struct jabber_data, 1 ); char *s; /* For now this is needed in the _connected() handlers if using GLib event handling, to make sure we're not handling events on dead connections. */ jabber_connections = g_slist_prepend( jabber_connections, ic ); jd->ic = ic; ic->proto_data = jd; jabber_set_me( ic, acc->user ); jd->fd = jd->r_inpa = jd->w_inpa = -1; if( jd->server == NULL ) { imcb_error( ic, "Incomplete account name (format it like )" ); imc_logout( ic, FALSE ); return; } if( ( s = strchr( jd->server, '/' ) ) ) { *s = 0; set_setstr( &acc->set, "resource", s + 1 ); /* Also remove the /resource from the original variable so we won't have to do this again every time. */ s = strchr( acc->user, '/' ); *s = 0; } jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free ); jd->buddies = g_hash_table_new( g_str_hash, g_str_equal ); if( set_getbool( &acc->set, "oauth" ) ) { GSList *p_in = NULL; const char *tok; jd->fd = jd->r_inpa = jd->w_inpa = -1; if( strstr( jd->server, ".live.com" ) ) jd->oauth2_service = &oauth2_service_mslive; else if( strstr( jd->server, ".facebook.com" ) ) jd->oauth2_service = &oauth2_service_facebook; else jd->oauth2_service = &oauth2_service_google; oauth_params_parse( &p_in, ic->acc->pass ); /* First see if we have a refresh token, in which case any access token we *might* have has probably expired already anyway. */ if( ( tok = oauth_params_get( &p_in, "refresh_token" ) ) ) { sasl_oauth2_refresh( ic, tok ); } /* If we don't have a refresh token, let's hope the access token is still usable. */ else if( ( tok = oauth_params_get( &p_in, "access_token" ) ) ) { jd->oauth2_access_token = g_strdup( tok ); jabber_connect( ic ); } /* If we don't have any, start the OAuth process now. Don't even open an XMPP connection yet. */ else { sasl_oauth2_init( ic ); ic->flags |= OPT_SLOW_LOGIN; } oauth_params_free( &p_in ); } else jabber_connect( ic ); } /* Separate this from jabber_login() so we can do OAuth first if necessary. Putting this in io.c would probably be more correct. */ void jabber_connect( struct im_connection *ic ) { account_t *acc = ic->acc; struct jabber_data *jd = ic->proto_data; int i; char *connect_to; struct ns_srv_reply **srvl = NULL, *srv = NULL; /* Figure out the hostname to connect to. */ if( acc->server && *acc->server ) connect_to = acc->server; else if( ( srvl = srv_lookup( "xmpp-client", "tcp", jd->server ) ) || ( srvl = srv_lookup( "jabber-client", "tcp", jd->server ) ) ) { /* Find the lowest-priority one. These usually come back in random/shuffled order. Not looking at weights etc for now. */ srv = *srvl; for( i = 1; srvl[i]; i ++ ) if( srvl[i]->prio < srv->prio ) srv = srvl[i]; connect_to = srv->name; } else connect_to = jd->server; imcb_log( ic, "Connecting" ); for( i = 0; jabber_port_list[i] > 0; i ++ ) if( set_getint( &acc->set, "port" ) == jabber_port_list[i] ) break; if( jabber_port_list[i] == 0 ) { imcb_log( ic, "Illegal port number" ); imc_logout( ic, FALSE ); return; } /* For non-SSL connections we can try to use the port # from the SRV reply, but let's not do that when using SSL, SSL usually runs on non-standard ports... */ if( set_getbool( &acc->set, "ssl" ) ) { jd->ssl = ssl_connect( connect_to, set_getint( &acc->set, "port" ), set_getbool( &acc->set, "tls_verify" ), jabber_connected_ssl, ic ); jd->fd = jd->ssl ? ssl_getfd( jd->ssl ) : -1; } else { jd->fd = proxy_connect( connect_to, srv ? srv->port : set_getint( &acc->set, "port" ), jabber_connected_plain, ic ); } srv_free( srvl ); if( jd->fd == -1 ) { imcb_error( ic, "Could not connect to server" ); imc_logout( ic, TRUE ); return; } if( set_getbool( &acc->set, "xmlconsole" ) ) { jd->flags |= JFLAG_XMLCONSOLE; /* Shouldn't really do this at this stage already, maybe. But I think this shouldn't break anything. */ imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL ); } jabber_generate_id_hash( jd ); } /* This generates an unfinished md5_state_t variable. Every time we generate an ID, we finish the state by adding a sequence number and take the hash. */ static void jabber_generate_id_hash( struct jabber_data *jd ) { md5_byte_t binbuf[4]; char *s; md5_init( &jd->cached_id_prefix ); md5_append( &jd->cached_id_prefix, (unsigned char *) jd->username, strlen( jd->username ) ); md5_append( &jd->cached_id_prefix, (unsigned char *) jd->server, strlen( jd->server ) ); s = set_getstr( &jd->ic->acc->set, "resource" ); md5_append( &jd->cached_id_prefix, (unsigned char *) s, strlen( s ) ); random_bytes( binbuf, 4 ); md5_append( &jd->cached_id_prefix, binbuf, 4 ); } static void jabber_logout( struct im_connection *ic ) { struct jabber_data *jd = ic->proto_data; while( jd->filetransfers ) imcb_file_canceled( ic, ( ( struct jabber_transfer *) jd->filetransfers->data )->ft, "Logging out" ); while( jd->streamhosts ) { jabber_streamhost_t *sh = jd->streamhosts->data; jd->streamhosts = g_slist_remove( jd->streamhosts, sh ); g_free( sh->jid ); g_free( sh->host ); g_free( sh ); } if( jd->fd >= 0 ) jabber_end_stream( ic ); while( ic->groupchats ) jabber_chat_free( ic->groupchats->data ); if( jd->r_inpa >= 0 ) b_event_remove( jd->r_inpa ); if( jd->w_inpa >= 0 ) b_event_remove( jd->w_inpa ); if( jd->ssl ) ssl_disconnect( jd->ssl ); if( jd->fd >= 0 ) closesocket( jd->fd ); if( jd->tx_len ) g_free( jd->txq ); if( jd->node_cache ) g_hash_table_destroy( jd->node_cache ); jabber_buddy_remove_all( ic ); xt_free( jd->xt ); g_free( jd->oauth2_access_token ); g_free( jd->away_message ); g_free( jd->username ); g_free( jd->me ); g_free( jd ); jabber_connections = g_slist_remove( jabber_connections, ic ); } static int jabber_buddy_msg( struct im_connection *ic, char *who, char *message, int flags ) { struct jabber_data *jd = ic->proto_data; struct jabber_buddy *bud; struct xt_node *node; char *s; int st; if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 ) return jabber_write( ic, message, strlen( message ) ); if( g_strcasecmp( who, JABBER_OAUTH_HANDLE ) == 0 && !( jd->flags & OPT_LOGGED_IN ) && jd->fd == -1 ) { if( sasl_oauth2_get_refresh_token( ic, message ) ) { return 1; } else { imcb_error( ic, "OAuth failure" ); imc_logout( ic, TRUE ); return 0; } } if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) bud = jabber_buddy_by_ext_jid( ic, who, 0 ); else bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_BARE_OK ); node = xt_new_node( "body", message, NULL ); node = jabber_make_packet( "message", "chat", bud ? bud->full_jid : who, node ); if( bud && ( jd->flags & JFLAG_WANT_TYPING ) && ( ( bud->flags & JBFLAG_DOES_XEP85 ) || !( bud->flags & JBFLAG_PROBED_XEP85 ) ) ) { struct xt_node *act; /* If the user likes typing notification and if we don't know (and didn't probe before) if this resource supports XEP85, include a probe in this packet now. Also, if we know this buddy does support XEP85, we have to send this tag to tell that the user stopped typing (well, that's what we guess when s/he pressed Enter...). */ act = xt_new_node( "active", NULL, NULL ); xt_add_attr( act, "xmlns", XMLNS_CHATSTATES ); xt_add_child( node, act ); /* Just make sure we do this only once. */ bud->flags |= JBFLAG_PROBED_XEP85; } st = jabber_write_packet( ic, node ); xt_free_node( node ); return st; } static GList *jabber_away_states( struct im_connection *ic ) { static GList *l = NULL; int i; if( l == NULL ) for( i = 0; jabber_away_state_list[i].full_name; i ++ ) l = g_list_append( l, (void*) jabber_away_state_list[i].full_name ); return l; } static void jabber_get_info( struct im_connection *ic, char *who ) { struct jabber_buddy *bud; bud = jabber_buddy_by_jid( ic, who, GET_BUDDY_FIRST ); while( bud ) { imcb_log( ic, "Buddy %s (%d) information:", bud->full_jid, bud->priority ); if( bud->away_state ) imcb_log( ic, "Away state: %s", bud->away_state->full_name ); imcb_log( ic, "Status message: %s", bud->away_message ? bud->away_message : "(none)" ); bud = bud->next; } jabber_get_vcard( ic, bud ? bud->full_jid : who ); } static void jabber_set_away( struct im_connection *ic, char *state_txt, char *message ) { struct jabber_data *jd = ic->proto_data; /* state_txt == NULL -> Not away. Unknown state -> fall back to the first defined away state. */ if( state_txt == NULL ) jd->away_state = NULL; else if( ( jd->away_state = jabber_away_state_by_name( state_txt ) ) == NULL ) jd->away_state = jabber_away_state_list; g_free( jd->away_message ); jd->away_message = ( message && *message ) ? g_strdup( message ) : NULL; presence_send_update( ic ); } static void jabber_add_buddy( struct im_connection *ic, char *who, char *group ) { struct jabber_data *jd = ic->proto_data; if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 ) { jd->flags |= JFLAG_XMLCONSOLE; imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL ); return; } if( jabber_add_to_roster( ic, who, NULL, group ) ) presence_send_request( ic, who, "subscribe" ); } static void jabber_remove_buddy( struct im_connection *ic, char *who, char *group ) { struct jabber_data *jd = ic->proto_data; if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 ) { jd->flags &= ~JFLAG_XMLCONSOLE; /* Not necessary for now. And for now the code isn't too happy if the buddy is completely gone right after calling this function already. imcb_remove_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL ); */ return; } /* We should always do this part. Clean up our administration a little bit. */ jabber_buddy_remove_bare( ic, who ); if( jabber_remove_from_roster( ic, who ) ) presence_send_request( ic, who, "unsubscribe" ); } static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets ) { struct jabber_data *jd = ic->proto_data; if( strchr( room, '@' ) == NULL ) imcb_error( ic, "%s is not a valid Jabber room name. Maybe you mean %s@conference.%s?", room, room, jd->server ); else if( jabber_chat_by_jid( ic, room ) ) imcb_error( ic, "Already present in chat `%s'", room ); else return jabber_chat_join( ic, room, nick, set_getstr( sets, "password" ) ); return NULL; } static struct groupchat *jabber_chat_with_( struct im_connection *ic, char *who ) { return jabber_chat_with( ic, who ); } static void jabber_chat_msg_( struct groupchat *c, char *message, int flags ) { if( c && message ) jabber_chat_msg( c, message, flags ); } static void jabber_chat_topic_( struct groupchat *c, char *topic ) { if( c && topic ) jabber_chat_topic( c, topic ); } static void jabber_chat_leave_( struct groupchat *c ) { if( c ) jabber_chat_leave( c, NULL ); } static void jabber_chat_invite_( struct groupchat *c, char *who, char *msg ) { struct jabber_data *jd = c->ic->proto_data; struct jabber_chat *jc = c->data; gchar *msg_alt = NULL; if( msg == NULL ) msg_alt = g_strdup_printf( "%s invited you to %s", jd->me, jc->name ); if( c && who ) jabber_chat_invite( c, who, msg ? msg : msg_alt ); g_free( msg_alt ); } static void jabber_keepalive( struct im_connection *ic ) { /* Just any whitespace character is enough as a keepalive for XMPP sessions. */ if( !jabber_write( ic, "\n", 1 ) ) return; /* This runs the garbage collection every minute, which means every packet is in the cache for about a minute (which should be enough AFAIK). */ jabber_cache_clean( ic ); } static int jabber_send_typing( struct im_connection *ic, char *who, int typing ) { struct jabber_data *jd = ic->proto_data; struct jabber_buddy *bud; /* Enable typing notification related code from now. */ jd->flags |= JFLAG_WANT_TYPING; if( ( bud = jabber_buddy_by_jid( ic, who, 0 ) ) == NULL ) { /* Sending typing notifications to unknown buddies is unsupported for now. Shouldn't be a problem, I think. */ return 0; } if( bud->flags & JBFLAG_DOES_XEP85 ) { /* We're only allowed to send this stuff if we know the other side supports it. */ struct xt_node *node; char *type; int st; if( typing & OPT_TYPING ) type = "composing"; else if( typing & OPT_THINKING ) type = "paused"; else type = "active"; node = xt_new_node( type, NULL, NULL ); xt_add_attr( node, "xmlns", XMLNS_CHATSTATES ); node = jabber_make_packet( "message", "chat", bud->full_jid, node ); st = jabber_write_packet( ic, node ); xt_free_node( node ); return st; } return 1; } void jabber_chat_add_settings( account_t *acc, set_t **head ) { /* Meh. Stupid room passwords. Not trying to obfuscate/hide them from the user for now. */ set_add( head, "password", NULL, NULL, NULL ); } void jabber_chat_free_settings( account_t *acc, set_t **head ) { set_del( head, "password" ); } GList *jabber_buddy_action_list( bee_user_t *bu ) { static GList *ret = NULL; if( ret == NULL ) { static const struct buddy_action ba[2] = { { "VERSION", "Get client (version) information" }, }; ret = g_list_prepend( ret, (void*) ba + 0 ); } return ret; } void *jabber_buddy_action( struct bee_user *bu, const char *action, char * const args[], void *data ) { if( g_strcasecmp( action, "VERSION" ) == 0 ) { struct jabber_buddy *bud; if( ( bud = jabber_buddy_by_ext_jid( bu->ic, bu->handle, 0 ) ) == NULL ) bud = jabber_buddy_by_jid( bu->ic, bu->handle, GET_BUDDY_FIRST ); for( ; bud; bud = bud->next ) jabber_iq_version_send( bu->ic, bud, data ); } return NULL; } void jabber_initmodule() { struct prpl *ret = g_new0( struct prpl, 1 ); ret->name = "jabber"; ret->mms = 0; /* no limit */ ret->login = jabber_login; ret->init = jabber_init; ret->logout = jabber_logout; ret->buddy_msg = jabber_buddy_msg; ret->away_states = jabber_away_states; ret->set_away = jabber_set_away; // ret->set_info = jabber_set_info; ret->get_info = jabber_get_info; ret->add_buddy = jabber_add_buddy; ret->remove_buddy = jabber_remove_buddy; ret->chat_msg = jabber_chat_msg_; ret->chat_topic = jabber_chat_topic_; ret->chat_invite = jabber_chat_invite_; ret->chat_leave = jabber_chat_leave_; ret->chat_join = jabber_chat_join_; ret->chat_with = jabber_chat_with_; ret->chat_add_settings = jabber_chat_add_settings; ret->chat_free_settings = jabber_chat_free_settings; ret->keepalive = jabber_keepalive; ret->send_typing = jabber_send_typing; ret->handle_cmp = g_strcasecmp; ret->transfer_request = jabber_si_transfer_request; ret->buddy_action_list = jabber_buddy_action_list; ret->buddy_action = jabber_buddy_action; register_protocol( ret ); } bitlbee-3.2.1/protocols/jabber/jabber.h0000644000175000017500000003633512245474076017377 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - Main file * * * * Copyright 2006-2013 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #ifndef _JABBER_H #define _JABBER_H #include #include "bitlbee.h" #include "md5.h" #include "xmltree.h" extern GSList *jabber_connections; typedef enum { JFLAG_STREAM_STARTED = 1, /* Set when we detected the beginning of the stream and want to do auth. */ JFLAG_AUTHENTICATED = 2, /* Set when we're successfully authenticatd. */ JFLAG_STREAM_RESTART = 4, /* Set when we want to restart the stream (after SASL or TLS). */ JFLAG_WANT_SESSION = 8, /* Set if the server wants a tag before we continue. */ JFLAG_WANT_BIND = 16, /* ... for tag. */ JFLAG_WANT_TYPING = 32, /* Set if we ever sent a typing notification, this activates all XEP-85 related code. */ JFLAG_XMLCONSOLE = 64, /* If the user added an xmlconsole buddy. */ JFLAG_STARTTLS_DONE = 128, /* If a plaintext session was converted to TLS. */ JFLAG_GTALK = 0x100000, /* Is Google Talk, as confirmed by iq discovery */ JFLAG_SASL_FB = 0x10000, /* Trying Facebook authentication. */ } jabber_flags_t; typedef enum { JBFLAG_PROBED_XEP85 = 1, /* Set this when we sent our probe packet to make sure it gets sent only once. */ JBFLAG_DOES_XEP85 = 2, /* Set this when the resource seems to support XEP85 (typing notification shite). */ JBFLAG_IS_CHATROOM = 4, /* It's convenient to use this JID thingy for groupchat state info too. */ JBFLAG_IS_ANONYMOUS = 8, /* For anonymous chatrooms, when we don't have have a real JID. */ JBFLAG_HIDE_SUBJECT = 16, /* Hide the subject field since we probably showed it already. */ } jabber_buddy_flags_t; /* Stores a streamhost's (a.k.a. proxy) data */ typedef struct { char *jid; char *host; char port[6]; } jabber_streamhost_t; typedef enum { JCFLAG_MESSAGE_SENT = 1, /* Set this after sending the first message, so we can detect echoes/backlogs. */ } jabber_chat_flags_t; struct jabber_data { struct im_connection *ic; int fd; void *ssl; char *txq; int tx_len; int r_inpa, w_inpa; struct xt_parser *xt; jabber_flags_t flags; char *username; /* USERNAME@server */ char *server; /* username@SERVER -=> server/domain, not hostname */ char *me; /* bare jid */ const struct oauth2_service *oauth2_service; char *oauth2_access_token; /* After changing one of these two (or the priority setting), call presence_send_update() to inform the server about the changes. */ const struct jabber_away_state *away_state; char *away_message; md5_state_t cached_id_prefix; GHashTable *node_cache; GHashTable *buddies; GSList *filetransfers; GSList *streamhosts; int have_streamhosts; }; struct jabber_away_state { char code[5]; char *full_name; }; typedef xt_status (*jabber_cache_event) ( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); struct jabber_cache_entry { time_t saved_at; struct xt_node *node; jabber_cache_event func; }; /* Somewhat messy data structure: We have a hash table with the bare JID as the key and the head of a struct jabber_buddy list as the value. The head is always a bare JID. If the JID has other resources (often the case, except for some transports that don't support multiple resources), those follow. In that case, the bare JID at the beginning doesn't actually refer to a real session and should only be used for operations that support incomplete JIDs. */ struct jabber_buddy { char *bare_jid; char *full_jid; char *resource; char *ext_jid; /* The JID to use in BitlBee. The real JID if possible, */ /* otherwise something similar to the conference JID. */ int priority; struct jabber_away_state *away_state; char *away_message; GSList *features; time_t last_msg; jabber_buddy_flags_t flags; struct jabber_buddy *next; }; struct jabber_chat { int flags; char *name; char *my_full_jid; /* Separate copy because of case sensitivity. */ struct jabber_buddy *me; char *invite; }; struct jabber_transfer { /* bitlbee's handle for this transfer */ file_transfer_t *ft; /* the stream's private handle */ gpointer streamhandle; /* timeout for discover queries */ gint disco_timeout; gint disco_timeout_fired; struct im_connection *ic; struct jabber_buddy *bud; int watch_in; int watch_out; char *ini_jid; char *tgt_jid; char *iq_id; char *sid; int accepted; size_t bytesread, byteswritten; int fd; struct sockaddr_storage saddr; }; #define JABBER_XMLCONSOLE_HANDLE "_xmlconsole" #define JABBER_OAUTH_HANDLE "jabber_oauth" /* Prefixes to use for packet IDs (mainly for IQ packets ATM). Usually the first one should be used, but when storing a packet in the cache, a "special" kind of ID is assigned to make it easier later to figure out if we have to do call an event handler for the response packet. Also we'll append a hash to make sure we won't trigger on cached packets from other BitlBee users. :-) */ #define JABBER_PACKET_ID "BeeP" #define JABBER_CACHED_ID "BeeC" /* The number of seconds to keep cached packets before garbage collecting them. This gc is done on every keepalive (every minute). */ #define JABBER_CACHE_MAX_AGE 600 /* RFC 392[01] stuff */ #define XMLNS_TLS "urn:ietf:params:xml:ns:xmpp-tls" #define XMLNS_SASL "urn:ietf:params:xml:ns:xmpp-sasl" #define XMLNS_BIND "urn:ietf:params:xml:ns:xmpp-bind" #define XMLNS_SESSION "urn:ietf:params:xml:ns:xmpp-session" #define XMLNS_STANZA_ERROR "urn:ietf:params:xml:ns:xmpp-stanzas" #define XMLNS_STREAM_ERROR "urn:ietf:params:xml:ns:xmpp-streams" #define XMLNS_ROSTER "jabber:iq:roster" /* Some supported extensions/legacy stuff */ #define XMLNS_AUTH "jabber:iq:auth" /* XEP-0078 */ #define XMLNS_VERSION "jabber:iq:version" /* XEP-0092 */ #define XMLNS_TIME_OLD "jabber:iq:time" /* XEP-0090 */ #define XMLNS_TIME "urn:xmpp:time" /* XEP-0202 */ #define XMLNS_PING "urn:xmpp:ping" /* XEP-0199 */ #define XMLNS_RECEIPTS "urn:xmpp:receipts" /* XEP-0184 */ #define XMLNS_VCARD "vcard-temp" /* XEP-0054 */ #define XMLNS_DELAY "jabber:x:delay" /* XEP-0091 */ #define XMLNS_XDATA "jabber:x:data" /* XEP-0004 */ #define XMLNS_CHATSTATES "http://jabber.org/protocol/chatstates" /* XEP-0085 */ #define XMLNS_DISCO_INFO "http://jabber.org/protocol/disco#info" /* XEP-0030 */ #define XMLNS_DISCO_ITEMS "http://jabber.org/protocol/disco#items" /* XEP-0030 */ #define XMLNS_MUC "http://jabber.org/protocol/muc" /* XEP-0045 */ #define XMLNS_MUC_USER "http://jabber.org/protocol/muc#user" /* XEP-0045 */ #define XMLNS_CAPS "http://jabber.org/protocol/caps" /* XEP-0115 */ #define XMLNS_FEATURE "http://jabber.org/protocol/feature-neg" /* XEP-0020 */ #define XMLNS_SI "http://jabber.org/protocol/si" /* XEP-0095 */ #define XMLNS_FILETRANSFER "http://jabber.org/protocol/si/profile/file-transfer" /* XEP-0096 */ #define XMLNS_BYTESTREAMS "http://jabber.org/protocol/bytestreams" /* XEP-0065 */ #define XMLNS_IBB "http://jabber.org/protocol/ibb" /* XEP-0047 */ /* jabber.c */ void jabber_connect( struct im_connection *ic ); /* iq.c */ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ); int jabber_init_iq_auth( struct im_connection *ic ); xt_status jabber_pkt_bind_sess( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); int jabber_get_roster( struct im_connection *ic ); int jabber_get_vcard( struct im_connection *ic, char *bare_jid ); int jabber_add_to_roster( struct im_connection *ic, const char *handle, const char *name, const char *group ); int jabber_remove_from_roster( struct im_connection *ic, char *handle ); xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ); xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns ); void jabber_iq_version_send( struct im_connection *ic, struct jabber_buddy *bud, void *data ); /* si.c */ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode ); void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ); void jabber_si_free_transfer( file_transfer_t *ft); /* s5bytestream.c */ int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode); gboolean jabber_bs_send_start( struct jabber_transfer *tf ); gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len ); /* message.c */ xt_status jabber_pkt_message( struct xt_node *node, gpointer data ); /* presence.c */ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data ); int presence_send_update( struct im_connection *ic ); int presence_send_request( struct im_connection *ic, char *handle, char *request ); /* jabber_util.c */ char *set_eval_priority( set_t *set, char *value ); char *set_eval_tls( set_t *set, char *value ); struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children ); struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code ); void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func ); struct xt_node *jabber_cache_get( struct im_connection *ic, char *id ); void jabber_cache_entry_free( gpointer entry ); void jabber_cache_clean( struct im_connection *ic ); xt_status jabber_cache_handle_packet( struct im_connection *ic, struct xt_node *node ); const struct jabber_away_state *jabber_away_state_by_code( char *code ); const struct jabber_away_state *jabber_away_state_by_name( char *name ); void jabber_buddy_ask( struct im_connection *ic, char *handle ); char *jabber_normalize( const char *orig ); typedef enum { GET_BUDDY_CREAT = 1, /* Try to create it, if necessary. */ GET_BUDDY_EXACT = 2, /* Get an exact match (only makes sense with bare JIDs). */ GET_BUDDY_FIRST = 4, /* No selection, simply get the first resource for this JID. */ GET_BUDDY_BARE = 8, /* Get the bare version of the JID (possibly inexistent). */ GET_BUDDY_BARE_OK = 16, /* Allow returning a bare JID if that seems better. */ } get_buddy_flags_t; struct jabber_error { char *code, *text, *type; }; struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid ); struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid, get_buddy_flags_t flags ); struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid, get_buddy_flags_t flags ); int jabber_buddy_remove( struct im_connection *ic, char *full_jid ); int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid ); void jabber_buddy_remove_all( struct im_connection *ic ); time_t jabber_get_timestamp( struct xt_node *xt ); struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns ); void jabber_error_free( struct jabber_error *err ); gboolean jabber_set_me( struct im_connection *ic, const char *me ); extern const struct jabber_away_state jabber_away_state_list[]; /* io.c */ int jabber_write_packet( struct im_connection *ic, struct xt_node *node ); int jabber_write( struct im_connection *ic, char *buf, int len ); gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond ); gboolean jabber_connected_ssl( gpointer data, int returncode, void *source, b_input_condition cond ); gboolean jabber_start_stream( struct im_connection *ic ); void jabber_end_stream( struct im_connection *ic ); /* sasl.c */ xt_status sasl_pkt_mechanisms( struct xt_node *node, gpointer data ); xt_status sasl_pkt_challenge( struct xt_node *node, gpointer data ); xt_status sasl_pkt_result( struct xt_node *node, gpointer data ); gboolean sasl_supported( struct im_connection *ic ); void sasl_oauth2_init( struct im_connection *ic ); int sasl_oauth2_get_refresh_token( struct im_connection *ic, const char *msg ); int sasl_oauth2_refresh( struct im_connection *ic, const char *refresh_token ); extern const struct oauth2_service oauth2_service_google; extern const struct oauth2_service oauth2_service_facebook; extern const struct oauth2_service oauth2_service_mslive; /* conference.c */ struct groupchat *jabber_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password ); struct groupchat *jabber_chat_with( struct im_connection *ic, char *who ); struct groupchat *jabber_chat_by_jid( struct im_connection *ic, const char *name ); void jabber_chat_free( struct groupchat *c ); int jabber_chat_msg( struct groupchat *ic, char *message, int flags ); int jabber_chat_topic( struct groupchat *c, char *topic ); int jabber_chat_leave( struct groupchat *c, const char *reason ); void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node ); void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node ); void jabber_chat_invite( struct groupchat *c, char *who, char *message ); #endif bitlbee-3.2.1/protocols/jabber/Makefile0000644000175000017500000000153212245474076017430 0ustar wilmerwilmer########################### ## Makefile for BitlBee ## ## ## ## Copyright 2002 Lintux ## ########################### ### DEFINITIONS -include ../../Makefile.settings ifdef _SRCDIR_ _SRCDIR_ := $(_SRCDIR_)protocols/jabber/ endif # [SH] Program variables objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o s5bytestream.o sasl.o si.o LFLAGS += -r # [SH] Phony targets all: jabber_mod.o check: all lcov: check gcov: gcov *.c .PHONY: all clean distclean clean: rm -f *.o core distclean: clean rm -rf .depend ### MAIN PROGRAM $(objects): ../../Makefile.settings Makefile $(objects): %.o: $(_SRCDIR_)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $(CFLAGS_BITLBEE) $< -o $@ jabber_mod.o: $(objects) @echo '*' Linking jabber_mod.o @$(LD) $(LFLAGS) $(objects) -o jabber_mod.o -include .depend/*.d bitlbee-3.2.1/protocols/jabber/presence.c0000644000175000017500000002056012245474076017742 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - Handling of presence (tags), etc * * * * Copyright 2006 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include "jabber.h" xt_status jabber_pkt_presence( struct xt_node *node, gpointer data ) { struct im_connection *ic = data; char *from = xt_find_attr( node, "from" ); char *type = xt_find_attr( node, "type" ); /* NULL should mean the person is online. */ struct xt_node *c, *cap; struct jabber_buddy *bud, *send_presence = NULL; int is_chat = 0; char *s; if( !from ) return XT_HANDLED; if( ( s = strchr( from, '/' ) ) ) { *s = 0; if( jabber_chat_by_jid( ic, from ) ) is_chat = 1; *s = '/'; } if( type == NULL ) { if( !( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT | GET_BUDDY_CREAT ) ) ) { /* imcb_log( ic, "Warning: Could not handle presence information from JID: %s", from ); */ return XT_HANDLED; } g_free( bud->away_message ); if( ( c = xt_find_node( node->children, "status" ) ) && c->text_len > 0 ) bud->away_message = g_strdup( c->text ); else bud->away_message = NULL; if( ( c = xt_find_node( node->children, "show" ) ) && c->text_len > 0 ) { bud->away_state = (void*) jabber_away_state_by_code( c->text ); } else { bud->away_state = NULL; } if( ( c = xt_find_node( node->children, "priority" ) ) && c->text_len > 0 ) bud->priority = atoi( c->text ); else bud->priority = 0; if( bud && ( cap = xt_find_node( node->children, "c" ) ) && ( s = xt_find_attr( cap, "xmlns" ) ) && strcmp( s, XMLNS_CAPS ) == 0 ) { /* This stanza includes an XEP-0115 capabilities part. Not too interesting, but we can see if it has an ext= attribute. */ s = xt_find_attr( cap, "ext" ); if( s && ( strstr( s, "cstates" ) || strstr( s, "chatstate" ) ) ) bud->flags |= JBFLAG_DOES_XEP85; /* This field can contain more information like xhtml support, but we don't support that ourselves. Officially the ext= tag was deprecated, but enough clients do send it. (I'm aware that this is not the right way to use this field.) See for an explanation of ext=: http://www.xmpp.org/extensions/attic/xep-0115-1.3.html*/ } if( is_chat ) jabber_chat_pkt_presence( ic, bud, node ); else send_presence = jabber_buddy_by_jid( ic, bud->bare_jid, 0 ); } else if( strcmp( type, "unavailable" ) == 0 ) { if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL ) { /* imcb_log( ic, "Warning: Received presence information from unknown JID: %s", from ); */ return XT_HANDLED; } /* Handle this before we delete the JID. */ if( is_chat ) { jabber_chat_pkt_presence( ic, bud, node ); } if( strchr( from, '/' ) == NULL ) /* Sometimes servers send a type="unavailable" from a bare JID, which should mean that suddenly all resources for this JID disappeared. */ jabber_buddy_remove_bare( ic, from ); else jabber_buddy_remove( ic, from ); if( is_chat ) { /* Nothing else to do for now? */ } else if( ( s = strchr( from, '/' ) ) ) { *s = 0; /* If another resource is still available, send its presence information. */ if( ( send_presence = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL ) { /* Otherwise, count him/her as offline now. */ imcb_buddy_status( ic, from, 0, NULL, NULL ); } *s = '/'; } else { imcb_buddy_status( ic, from, 0, NULL, NULL ); } } else if( strcmp( type, "subscribe" ) == 0 ) { jabber_buddy_ask( ic, from ); } else if( strcmp( type, "subscribed" ) == 0 ) { /* Not sure about this one, actually... */ imcb_log( ic, "%s just accepted your authorization request", from ); } else if( strcmp( type, "unsubscribe" ) == 0 || strcmp( type, "unsubscribed" ) == 0 ) { /* Do nothing here. Plenty of control freaks or over-curious souls get excited when they can see who still has them in their buddy list and who finally removed them. Somehow I got the impression that those are the people who get removed from many buddy lists for "some" reason... If you're one of those people, this is your chance to write your first line of code in C... */ } else if( strcmp( type, "error" ) == 0 ) { return jabber_cache_handle_packet( ic, node ); /* struct jabber_error *err; if( ( c = xt_find_node( node->children, "error" ) ) ) { err = jabber_error_parse( c, XMLNS_STANZA_ERROR ); imcb_error( ic, "Stanza (%s) error: %s%s%s", node->name, err->code, err->text ? ": " : "", err->text ? err->text : "" ); jabber_error_free( err ); } */ } if( send_presence ) { int is_away = 0; if( send_presence->away_state && strcmp( send_presence->away_state->code, "chat" ) != 0 ) is_away = OPT_AWAY; imcb_buddy_status( ic, send_presence->bare_jid, OPT_LOGGED_IN | is_away, is_away ? send_presence->away_state->full_name : NULL, send_presence->away_message ); } return XT_HANDLED; } /* Whenever presence information is updated, call this function to inform the server. */ int presence_send_update( struct im_connection *ic ) { struct jabber_data *jd = ic->proto_data; struct xt_node *node, *cap; GSList *l; int st; node = jabber_make_packet( "presence", NULL, NULL, NULL ); xt_add_child( node, xt_new_node( "priority", set_getstr( &ic->acc->set, "priority" ), NULL ) ); if( jd->away_state ) xt_add_child( node, xt_new_node( "show", jd->away_state->code, NULL ) ); if( jd->away_message ) xt_add_child( node, xt_new_node( "status", jd->away_message, NULL ) ); /* This makes the packet slightly bigger, but clients interested in capabilities can now cache the discovery info. This reduces the usual post-login iq-flood. See XEP-0115. At least libpurple and Trillian seem to do this right. */ cap = xt_new_node( "c", NULL, NULL ); xt_add_attr( cap, "xmlns", XMLNS_CAPS ); xt_add_attr( cap, "node", "http://bitlbee.org/xmpp/caps" ); xt_add_attr( cap, "ver", BITLBEE_VERSION ); /* The XEP wants this hashed, but nobody's doing that. */ xt_add_child( node, cap ); st = jabber_write_packet( ic, node ); /* Have to send this update to all groupchats too, the server won't do this automatically. */ for( l = ic->groupchats; l && st; l = l->next ) { struct groupchat *c = l->data; struct jabber_chat *jc = c->data; xt_add_attr( node, "to", jc->my_full_jid ); st = jabber_write_packet( ic, node ); } xt_free_node( node ); return st; } /* Send a subscribe/unsubscribe request to a buddy. */ int presence_send_request( struct im_connection *ic, char *handle, char *request ) { struct xt_node *node; int st; node = jabber_make_packet( "presence", NULL, NULL, NULL ); xt_add_attr( node, "to", handle ); xt_add_attr( node, "type", request ); st = jabber_write_packet( ic, node ); xt_free_node( node ); return st; } bitlbee-3.2.1/protocols/jabber/io.c0000644000175000017500000004222112245474076016543 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - I/O stuff (plain, SSL), queues, etc * * * * Copyright 2006-2012 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include "jabber.h" #include "ssl_client.h" static gboolean jabber_write_callback( gpointer data, gint fd, b_input_condition cond ); static gboolean jabber_write_queue( struct im_connection *ic ); int jabber_write_packet( struct im_connection *ic, struct xt_node *node ) { char *buf; int st; buf = xt_to_string( node ); st = jabber_write( ic, buf, strlen( buf ) ); g_free( buf ); return st; } int jabber_write( struct im_connection *ic, char *buf, int len ) { struct jabber_data *jd = ic->proto_data; gboolean ret; if( jd->flags & JFLAG_XMLCONSOLE && !( ic->flags & OPT_LOGGING_OUT ) ) { char *msg, *s; msg = g_strdup_printf( "TX: %s", buf ); /* Don't include auth info in XML logs. */ if( strncmp( msg, "TX: ' ) ) ) { s++; while( *s && *s != '<' ) *(s++) = '*'; } imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 ); g_free( msg ); } if( jd->tx_len == 0 ) { /* If the queue is empty, allocate a new buffer. */ jd->tx_len = len; jd->txq = g_memdup( buf, len ); /* Try if we can write it immediately so we don't have to do it via the event handler. If not, add the handler. (In most cases it probably won't be necessary.) */ if( ( ret = jabber_write_queue( ic ) ) && jd->tx_len > 0 ) jd->w_inpa = b_input_add( jd->fd, B_EV_IO_WRITE, jabber_write_callback, ic ); } else { /* Just add it to the buffer if it's already filled. The event handler is already set. */ jd->txq = g_renew( char, jd->txq, jd->tx_len + len ); memcpy( jd->txq + jd->tx_len, buf, len ); jd->tx_len += len; /* The return value for write() doesn't necessarily mean that everything got sent, it mainly means that the connection (officially) still exists and can still be accessed without hitting SIGSEGV. IOW: */ ret = TRUE; } return ret; } /* Splitting up in two separate functions: One to use as a callback and one to use in the function above to escape from having to wait for the event handler to call us, if possible. Two different functions are necessary because of the return values: The callback should only return TRUE if the write was successful AND if the buffer is not empty yet (ie. if the handler has to be called again when the socket is ready for more data). */ static gboolean jabber_write_callback( gpointer data, gint fd, b_input_condition cond ) { struct jabber_data *jd = ((struct im_connection *)data)->proto_data; return jd->fd != -1 && jabber_write_queue( data ) && jd->tx_len > 0; } static gboolean jabber_write_queue( struct im_connection *ic ) { struct jabber_data *jd = ic->proto_data; int st; if( jd->ssl ) st = ssl_write( jd->ssl, jd->txq, jd->tx_len ); else st = write( jd->fd, jd->txq, jd->tx_len ); if( st == jd->tx_len ) { /* We wrote everything, clear the buffer. */ g_free( jd->txq ); jd->txq = NULL; jd->tx_len = 0; return TRUE; } else if( st == 0 || ( st < 0 && !ssl_sockerr_again( jd->ssl ) ) ) { /* Set fd to -1 to make sure we won't write to it anymore. */ closesocket( jd->fd ); /* Shouldn't be necessary after errors? */ jd->fd = -1; imcb_error( ic, "Short write() to server" ); imc_logout( ic, TRUE ); return FALSE; } else if( st > 0 ) { char *s; s = g_memdup( jd->txq + st, jd->tx_len - st ); jd->tx_len -= st; g_free( jd->txq ); jd->txq = s; return TRUE; } else { /* Just in case we had EINPROGRESS/EAGAIN: */ return TRUE; } } static gboolean jabber_read_callback( gpointer data, gint fd, b_input_condition cond ) { struct im_connection *ic = data; struct jabber_data *jd = ic->proto_data; char buf[512]; int st; if( jd->fd == -1 ) return FALSE; if( jd->ssl ) st = ssl_read( jd->ssl, buf, sizeof( buf ) ); else st = read( jd->fd, buf, sizeof( buf ) ); if( st > 0 ) { /* Parse. */ if( xt_feed( jd->xt, buf, st ) < 0 ) { imcb_error( ic, "XML stream error" ); imc_logout( ic, TRUE ); return FALSE; } /* Execute all handlers. */ if( !xt_handle( jd->xt, NULL, 1 ) ) { /* Don't do anything, the handlers should have aborted the connection already. */ return FALSE; } if( jd->flags & JFLAG_STREAM_RESTART ) { jd->flags &= ~JFLAG_STREAM_RESTART; jabber_start_stream( ic ); } /* Garbage collection. */ xt_cleanup( jd->xt, NULL, 1 ); /* This is a bit hackish, unfortunately. Although xmltree has nifty event handler stuff, it only calls handlers when nodes are complete. Since the server should only send an opening tag, we have to check this by hand. :-( */ if( !( jd->flags & JFLAG_STREAM_STARTED ) && jd->xt && jd->xt->root ) { if( g_strcasecmp( jd->xt->root->name, "stream:stream" ) == 0 ) { jd->flags |= JFLAG_STREAM_STARTED; /* If there's no version attribute, assume this is an old server that can't do SASL authentication. */ if( !set_getbool( &ic->acc->set, "sasl") || !sasl_supported( ic ) ) { /* If there's no version= tag, we suppose this server does NOT implement: XMPP 1.0, SASL and TLS. */ if( set_getbool( &ic->acc->set, "tls" ) ) { imcb_error( ic, "TLS is turned on for this " "account, but is not supported by this server" ); imc_logout( ic, FALSE ); return FALSE; } else { return jabber_init_iq_auth( ic ); } } } else { imcb_error( ic, "XML stream error" ); imc_logout( ic, TRUE ); return FALSE; } } } else if( st == 0 || ( st < 0 && !ssl_sockerr_again( jd->ssl ) ) ) { closesocket( jd->fd ); jd->fd = -1; imcb_error( ic, "Error while reading from server" ); imc_logout( ic, TRUE ); return FALSE; } if( ssl_pending( jd->ssl ) ) /* OpenSSL empties the TCP buffers completely but may keep some data in its internap buffers. select() won't see that, but ssl_pending() does. */ return jabber_read_callback( data, fd, cond ); else return TRUE; } gboolean jabber_connected_plain( gpointer data, gint source, b_input_condition cond ) { struct im_connection *ic = data; if( g_slist_find( jabber_connections, ic ) == NULL ) return FALSE; if( source == -1 ) { imcb_error( ic, "Could not connect to server" ); imc_logout( ic, TRUE ); return FALSE; } imcb_log( ic, "Connected to server, logging in" ); return jabber_start_stream( ic ); } gboolean jabber_connected_ssl( gpointer data, int returncode, void *source, b_input_condition cond ) { struct im_connection *ic = data; struct jabber_data *jd; if( g_slist_find( jabber_connections, ic ) == NULL ) return FALSE; jd = ic->proto_data; if( source == NULL ) { /* The SSL connection will be cleaned up by the SSL lib already, set it to NULL here to prevent a double cleanup: */ jd->ssl = NULL; if( returncode != 0 ) { char *err = ssl_verify_strerror( returncode ); imcb_error( ic, "Certificate verification problem 0x%x: %s", returncode, err ? err : "Unknown" ); g_free( err ); imc_logout( ic, FALSE ); } else { imcb_error( ic, "Could not connect to server" ); imc_logout( ic, TRUE ); } return FALSE; } imcb_log( ic, "Connected to server, logging in" ); return jabber_start_stream( ic ); } static xt_status jabber_end_of_stream( struct xt_node *node, gpointer data ) { imc_logout( data, TRUE ); return XT_ABORT; } static xt_status jabber_pkt_features( struct xt_node *node, gpointer data ) { struct im_connection *ic = data; struct jabber_data *jd = ic->proto_data; struct xt_node *c, *reply; int trytls; trytls = g_strcasecmp( set_getstr( &ic->acc->set, "tls" ), "try" ) == 0; c = xt_find_node( node->children, "starttls" ); if( c && !jd->ssl ) { /* If the server advertises the STARTTLS feature and if we're not in a secure connection already: */ c = xt_find_node( c->children, "required" ); if( c && ( !trytls && !set_getbool( &ic->acc->set, "tls" ) ) ) { imcb_error( ic, "Server requires TLS connections, but TLS is turned off for this account" ); imc_logout( ic, FALSE ); return XT_ABORT; } /* Only run this if the tls setting is set to true or try: */ if( ( trytls || set_getbool( &ic->acc->set, "tls" ) ) ) { reply = xt_new_node( "starttls", NULL, NULL ); xt_add_attr( reply, "xmlns", XMLNS_TLS ); if( !jabber_write_packet( ic, reply ) ) { xt_free_node( reply ); return XT_ABORT; } xt_free_node( reply ); return XT_HANDLED; } } else if( !c && !jd->ssl ) { /* If the server does not advertise the STARTTLS feature and we're not in a secure connection already: (Servers have a habit of not advertising anymore when already using SSL/TLS. */ if( !trytls && set_getbool( &ic->acc->set, "tls" ) ) { imcb_error( ic, "TLS is turned on for this account, but is not supported by this server" ); imc_logout( ic, FALSE ); return XT_ABORT; } } /* This one used to be in jabber_handlers[], but it has to be done from here to make sure the TLS session will be initialized properly before we attempt SASL authentication. */ if( ( c = xt_find_node( node->children, "mechanisms" ) ) ) { if( sasl_pkt_mechanisms( c, data ) == XT_ABORT ) return XT_ABORT; } /* If the server *SEEMS* to support SASL authentication but doesn't support it after all, we should try to do authentication the other way. jabber.com doesn't seem to do SASL while it pretends to be XMPP 1.0 compliant! */ else if( !( jd->flags & JFLAG_AUTHENTICATED ) && set_getbool( &ic->acc->set, "sasl") && sasl_supported( ic ) ) { if( !jabber_init_iq_auth( ic ) ) return XT_ABORT; } if( ( c = xt_find_node( node->children, "bind" ) ) ) jd->flags |= JFLAG_WANT_BIND; if( ( c = xt_find_node( node->children, "session" ) ) ) jd->flags |= JFLAG_WANT_SESSION; if( jd->flags & JFLAG_AUTHENTICATED ) return jabber_pkt_bind_sess( ic, NULL, NULL ); return XT_HANDLED; } static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data ) { struct im_connection *ic = data; struct jabber_data *jd = ic->proto_data; char *xmlns, *tlsname; xmlns = xt_find_attr( node, "xmlns" ); /* Just ignore it when it doesn't seem to be TLS-related (is that at all possible??). */ if( !xmlns || strcmp( xmlns, XMLNS_TLS ) != 0 ) return XT_HANDLED; /* We don't want event handlers to touch our TLS session while it's still initializing! */ b_event_remove( jd->r_inpa ); if( jd->tx_len > 0 ) { /* Actually the write queue should be empty here, but just to be sure... */ b_event_remove( jd->w_inpa ); g_free( jd->txq ); jd->txq = NULL; jd->tx_len = 0; } jd->w_inpa = jd->r_inpa = 0; imcb_log( ic, "Converting stream to TLS" ); jd->flags |= JFLAG_STARTTLS_DONE; /* If the user specified a server for the account, use this server as the * hostname in the certificate verification. Else we use the domain from * the username. */ if( ic->acc->server && *ic->acc->server ) tlsname = ic->acc->server; else tlsname = jd->server; jd->ssl = ssl_starttls( jd->fd, tlsname, set_getbool( &ic->acc->set, "tls_verify" ), jabber_connected_ssl, ic ); return XT_HANDLED; } static xt_status jabber_pkt_stream_error( struct xt_node *node, gpointer data ) { struct im_connection *ic = data; struct jabber_data *jd = ic->proto_data; int allow_reconnect = TRUE; struct jabber_error *err; struct xt_node *host; if( !( ic->flags & OPT_LOGGED_IN ) && ( host = xt_find_node( node->children, "see-other-host" ) ) && host->text ) { char *s; int port = set_getint( &ic->acc->set, "port" ); /* Let's try to obey this request, if we're not logged in yet (i.e. not have too much state yet). */ if( jd->ssl ) ssl_disconnect( jd->ssl ); closesocket( jd->fd ); b_event_remove( jd->r_inpa ); b_event_remove( jd->w_inpa ); jd->ssl = NULL; jd->r_inpa = jd->w_inpa = 0; jd->flags &= JFLAG_XMLCONSOLE; s = strchr( host->text, ':' ); if( s != NULL ) sscanf( s + 1, "%d", &port ); imcb_log( ic, "Redirected to %s", host->text ); jd->fd = proxy_connect( host->text, port, jabber_connected_plain, ic ); return XT_ABORT; } err = jabber_error_parse( node, XMLNS_STREAM_ERROR ); /* Tssk... */ if( err->code == NULL ) { imcb_error( ic, "Unknown stream error reported by server" ); imc_logout( ic, allow_reconnect ); jabber_error_free( err ); return XT_ABORT; } /* We know that this is a fatal error. If it's a "conflict" error, we should turn off auto-reconnect to make sure we won't get some nasty infinite loop! */ if( strcmp( err->code, "conflict" ) == 0 ) { imcb_error( ic, "Account and resource used from a different location" ); allow_reconnect = FALSE; } else { imcb_error( ic, "Stream error: %s%s%s", err->code, err->text ? ": " : "", err->text ? err->text : "" ); } jabber_error_free( err ); imc_logout( ic, allow_reconnect ); return XT_ABORT; } static xt_status jabber_xmlconsole( struct xt_node *node, gpointer data ) { struct im_connection *ic = data; struct jabber_data *jd = ic->proto_data; if( jd->flags & JFLAG_XMLCONSOLE ) { char *msg, *pkt; pkt = xt_to_string( node ); msg = g_strdup_printf( "RX: %s", pkt ); imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 ); g_free( msg ); g_free( pkt ); } return XT_NEXT; } static const struct xt_handler_entry jabber_handlers[] = { { NULL, "stream:stream", jabber_xmlconsole }, { "stream:stream", "", jabber_end_of_stream }, { "message", "stream:stream", jabber_pkt_message }, { "presence", "stream:stream", jabber_pkt_presence }, { "iq", "stream:stream", jabber_pkt_iq }, { "stream:features", "stream:stream", jabber_pkt_features }, { "stream:error", "stream:stream", jabber_pkt_stream_error }, { "proceed", "stream:stream", jabber_pkt_proceed_tls }, { "challenge", "stream:stream", sasl_pkt_challenge }, { "success", "stream:stream", sasl_pkt_result }, { "failure", "stream:stream", sasl_pkt_result }, { NULL, NULL, NULL } }; gboolean jabber_start_stream( struct im_connection *ic ) { struct jabber_data *jd = ic->proto_data; int st; char *greet; /* We'll start our stream now, so prepare everything to receive one from the server too. */ xt_free( jd->xt ); /* In case we're RE-starting. */ jd->xt = xt_new( jabber_handlers, ic ); if( jd->r_inpa <= 0 ) jd->r_inpa = b_input_add( jd->fd, B_EV_IO_READ, jabber_read_callback, ic ); greet = g_strdup_printf( "%s", ( jd->flags & JFLAG_STARTTLS_DONE ) ? "" : "", jd->server ); st = jabber_write( ic, greet, strlen( greet ) ); g_free( greet ); return st; } void jabber_end_stream( struct im_connection *ic ) { struct jabber_data *jd = ic->proto_data; /* Let's only do this if the queue is currently empty, otherwise it'd take too long anyway. */ if( jd->tx_len == 0 ) { char eos[] = ""; struct xt_node *node; int st = 1; if( ic->flags & OPT_LOGGED_IN ) { node = jabber_make_packet( "presence", "unavailable", NULL, NULL ); st = jabber_write_packet( ic, node ); xt_free_node( node ); } if( st ) jabber_write( ic, eos, strlen( eos ) ); } } bitlbee-3.2.1/protocols/jabber/sasl.c0000644000175000017500000003746412245474076017113 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - SASL authentication * * * * Copyright 2006-2012 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include #include "jabber.h" #include "base64.h" #include "oauth2.h" #include "oauth.h" const struct oauth2_service oauth2_service_google = { "https://accounts.google.com/o/oauth2/auth", "https://accounts.google.com/o/oauth2/token", "urn:ietf:wg:oauth:2.0:oob", "https://www.googleapis.com/auth/googletalk", "783993391592.apps.googleusercontent.com", "6C-Zgf7Tr7gEQTPlBhMUgo7R", }; const struct oauth2_service oauth2_service_facebook = { "https://www.facebook.com/dialog/oauth", "https://graph.facebook.com/oauth/access_token", "http://www.bitlbee.org/main.php/Facebook/oauth2.html", "offline_access,xmpp_login", "126828914005625", "4b100f0f244d620bf3f15f8b217d4c32", }; const struct oauth2_service oauth2_service_mslive = { "https://oauth.live.com/authorize", "https://oauth.live.com/token", "http://www.bitlbee.org/main.php/Messenger/oauth2.html", "wl.offline_access%20wl.messenger", "000000004C06FCD1", "IRKlBPzJJAWcY-TbZjiTEJu9tn7XCFaV", }; xt_status sasl_pkt_mechanisms( struct xt_node *node, gpointer data ) { struct im_connection *ic = data; struct jabber_data *jd = ic->proto_data; struct xt_node *c, *reply; char *s; int sup_plain = 0, sup_digest = 0, sup_gtalk = 0, sup_fb = 0, sup_ms = 0; int want_oauth = FALSE; GString *mechs; if( !sasl_supported( ic ) ) { /* Should abort this now, since we should already be doing IQ authentication. Strange things happen when you try to do both... */ imcb_log( ic, "XMPP 1.0 non-compliant server seems to support SASL, please report this as a BitlBee bug!" ); return XT_HANDLED; } s = xt_find_attr( node, "xmlns" ); if( !s || strcmp( s, XMLNS_SASL ) != 0 ) { imcb_log( ic, "Stream error while authenticating" ); imc_logout( ic, FALSE ); return XT_ABORT; } want_oauth = set_getbool( &ic->acc->set, "oauth" ); mechs = g_string_new( "" ); c = node->children; while( ( c = xt_find_node( c, "mechanism" ) ) ) { if( c->text && g_strcasecmp( c->text, "PLAIN" ) == 0 ) sup_plain = 1; else if( c->text && g_strcasecmp( c->text, "DIGEST-MD5" ) == 0 ) sup_digest = 1; else if( c->text && g_strcasecmp( c->text, "X-OAUTH2" ) == 0 ) sup_gtalk = 1; else if( c->text && g_strcasecmp( c->text, "X-FACEBOOK-PLATFORM" ) == 0 ) sup_fb = 1; else if( c->text && g_strcasecmp( c->text, "X-MESSENGER-OAUTH2" ) == 0 ) sup_ms = 1; if( c->text ) g_string_append_printf( mechs, " %s", c->text ); c = c->next; } if( !want_oauth && !sup_plain && !sup_digest ) { if( !sup_gtalk && !sup_fb && !sup_ms ) imcb_error( ic, "This server requires OAuth " "(supported schemes:%s)", mechs->str ); else imcb_error( ic, "BitlBee does not support any of the offered SASL " "authentication schemes:%s", mechs->str ); imc_logout( ic, FALSE ); g_string_free( mechs, TRUE ); return XT_ABORT; } g_string_free( mechs, TRUE ); reply = xt_new_node( "auth", NULL, NULL ); xt_add_attr( reply, "xmlns", XMLNS_SASL ); if( sup_gtalk && want_oauth ) { int len; /* X-OAUTH2 is, not *the* standard OAuth2 SASL/XMPP implementation. It's currently used by GTalk and vaguely documented on http://code.google.com/apis/cloudprint/docs/rawxmpp.html . */ xt_add_attr( reply, "mechanism", "X-OAUTH2" ); len = strlen( jd->username ) + strlen( jd->oauth2_access_token ) + 2; s = g_malloc( len + 1 ); s[0] = 0; strcpy( s + 1, jd->username ); strcpy( s + 2 + strlen( jd->username ), jd->oauth2_access_token ); reply->text = base64_encode( (unsigned char *)s, len ); reply->text_len = strlen( reply->text ); g_free( s ); } else if( sup_ms && want_oauth ) { xt_add_attr( reply, "mechanism", "X-MESSENGER-OAUTH2" ); reply->text = g_strdup( jd->oauth2_access_token ); reply->text_len = strlen( jd->oauth2_access_token ); } else if( sup_fb && want_oauth ) { xt_add_attr( reply, "mechanism", "X-FACEBOOK-PLATFORM" ); jd->flags |= JFLAG_SASL_FB; } else if( want_oauth ) { imcb_error( ic, "OAuth requested, but not supported by server" ); imc_logout( ic, FALSE ); xt_free_node( reply ); return XT_ABORT; } else if( sup_digest ) { xt_add_attr( reply, "mechanism", "DIGEST-MD5" ); /* The rest will be done later, when we receive a . */ } else if( sup_plain ) { int len; xt_add_attr( reply, "mechanism", "PLAIN" ); /* With SASL PLAIN in XMPP, the text should be b64(\0user\0pass) */ len = strlen( jd->username ) + strlen( ic->acc->pass ) + 2; s = g_malloc( len + 1 ); s[0] = 0; strcpy( s + 1, jd->username ); strcpy( s + 2 + strlen( jd->username ), ic->acc->pass ); reply->text = base64_encode( (unsigned char *)s, len ); reply->text_len = strlen( reply->text ); g_free( s ); } if( reply && !jabber_write_packet( ic, reply ) ) { xt_free_node( reply ); return XT_ABORT; } xt_free_node( reply ); /* To prevent classic authentication from happening. */ jd->flags |= JFLAG_STREAM_STARTED; return XT_HANDLED; } /* Non-static function, but not mentioned in jabber.h because it's for internal use, just that the unittest should be able to reach it... */ char *sasl_get_part( char *data, char *field ) { int i, len; len = strlen( field ); while( isspace( *data ) || *data == ',' ) data ++; if( g_strncasecmp( data, field, len ) == 0 && data[len] == '=' ) { i = strlen( field ) + 1; } else { for( i = 0; data[i]; i ++ ) { /* If we have a ", skip until it's closed again. */ if( data[i] == '"' ) { i ++; while( data[i] != '"' || data[i-1] == '\\' ) i ++; } /* If we got a comma, we got a new field. Check it, find the next key after it. */ if( data[i] == ',' ) { while( isspace( data[i] ) || data[i] == ',' ) i ++; if( g_strncasecmp( data + i, field, len ) == 0 && data[i+len] == '=' ) { i += len + 1; break; } } } } if( data[i] == '"' ) { int j; char *ret; i ++; len = 0; while( data[i+len] != '"' || data[i+len-1] == '\\' ) len ++; ret = g_strndup( data + i, len ); for( i = j = 0; ret[i]; i ++ ) { if( ret[i] == '\\' ) { ret[j++] = ret[++i]; } else { ret[j++] = ret[i]; } } ret[j] = 0; return ret; } else if( data[i] ) { len = 0; while( data[i+len] && data[i+len] != ',' ) len ++; return g_strndup( data + i, len ); } else { return NULL; } } xt_status sasl_pkt_challenge( struct xt_node *node, gpointer data ) { struct im_connection *ic = data; struct jabber_data *jd = ic->proto_data; struct xt_node *reply_pkt = NULL; char *nonce = NULL, *realm = NULL, *cnonce = NULL; unsigned char cnonce_bin[30]; char *digest_uri = NULL; char *dec = NULL; char *s = NULL, *reply = NULL; xt_status ret = XT_ABORT; if( node->text_len == 0 ) goto error; dec = frombase64( node->text ); if( jd->flags & JFLAG_SASL_FB ) { /* New-style Facebook OAauth2 support. Instead of sending a refresh token, they just send an access token that should never expire. */ GSList *p_in = NULL, *p_out = NULL; char time[33]; oauth_params_parse( &p_in, dec ); oauth_params_add( &p_out, "nonce", oauth_params_get( &p_in, "nonce" ) ); oauth_params_add( &p_out, "method", oauth_params_get( &p_in, "method" ) ); oauth_params_free( &p_in ); g_snprintf( time, sizeof( time ), "%lld", (long long) ( gettime() * 1000 ) ); oauth_params_add( &p_out, "call_id", time ); oauth_params_add( &p_out, "api_key", oauth2_service_facebook.consumer_key ); oauth_params_add( &p_out, "v", "1.0" ); oauth_params_add( &p_out, "format", "XML" ); oauth_params_add( &p_out, "access_token", jd->oauth2_access_token ); reply = oauth_params_string( p_out ); oauth_params_free( &p_out ); } else if( !( s = sasl_get_part( dec, "rspauth" ) ) ) { /* See RFC 2831 for for information. */ md5_state_t A1, A2, H; md5_byte_t A1r[16], A2r[16], Hr[16]; char A1h[33], A2h[33], Hh[33]; int i; nonce = sasl_get_part( dec, "nonce" ); realm = sasl_get_part( dec, "realm" ); if( !nonce ) goto error; /* Jabber.Org considers the realm part optional and doesn't specify one. Oh well, actually they're right, but still, don't know if this is right... */ if( !realm ) realm = g_strdup( jd->server ); random_bytes( cnonce_bin, sizeof( cnonce_bin ) ); cnonce = base64_encode( cnonce_bin, sizeof( cnonce_bin ) ); digest_uri = g_strdup_printf( "%s/%s", "xmpp", jd->server ); /* Generate the MD5 hash of username:realm:password, I decided to call it H. */ md5_init( &H ); s = g_strdup_printf( "%s:%s:%s", jd->username, realm, ic->acc->pass ); md5_append( &H, (unsigned char *) s, strlen( s ) ); g_free( s ); md5_finish( &H, Hr ); /* Now generate the hex. MD5 hash of H:nonce:cnonce, called A1. */ md5_init( &A1 ); s = g_strdup_printf( ":%s:%s", nonce, cnonce ); md5_append( &A1, Hr, 16 ); md5_append( &A1, (unsigned char *) s, strlen( s ) ); g_free( s ); md5_finish( &A1, A1r ); for( i = 0; i < 16; i ++ ) sprintf( A1h + i * 2, "%02x", A1r[i] ); /* A2... */ md5_init( &A2 ); s = g_strdup_printf( "%s:%s", "AUTHENTICATE", digest_uri ); md5_append( &A2, (unsigned char *) s, strlen( s ) ); g_free( s ); md5_finish( &A2, A2r ); for( i = 0; i < 16; i ++ ) sprintf( A2h + i * 2, "%02x", A2r[i] ); /* Final result: A1:nonce:00000001:cnonce:auth:A2. Let's reuse H for it. */ md5_init( &H ); s = g_strdup_printf( "%s:%s:%s:%s:%s:%s", A1h, nonce, "00000001", cnonce, "auth", A2h ); md5_append( &H, (unsigned char *) s, strlen( s ) ); g_free( s ); md5_finish( &H, Hr ); for( i = 0; i < 16; i ++ ) sprintf( Hh + i * 2, "%02x", Hr[i] ); /* Now build the SASL response string: */ reply = g_strdup_printf( "username=\"%s\",realm=\"%s\",nonce=\"%s\",cnonce=\"%s\"," "nc=%08x,qop=auth,digest-uri=\"%s\",response=%s,charset=%s", jd->username, realm, nonce, cnonce, 1, digest_uri, Hh, "utf-8" ); } else { /* We found rspauth, but don't really care... */ g_free( s ); } s = reply ? tobase64( reply ) : NULL; reply_pkt = xt_new_node( "response", s, NULL ); xt_add_attr( reply_pkt, "xmlns", XMLNS_SASL ); if( !jabber_write_packet( ic, reply_pkt ) ) goto silent_error; ret = XT_HANDLED; goto silent_error; error: imcb_error( ic, "Incorrect SASL challenge received" ); imc_logout( ic, FALSE ); silent_error: g_free( digest_uri ); g_free( cnonce ); g_free( nonce ); g_free( reply ); g_free( realm ); g_free( dec ); g_free( s ); xt_free_node( reply_pkt ); return ret; } xt_status sasl_pkt_result( struct xt_node *node, gpointer data ) { struct im_connection *ic = data; struct jabber_data *jd = ic->proto_data; char *s; s = xt_find_attr( node, "xmlns" ); if( !s || strcmp( s, XMLNS_SASL ) != 0 ) { imcb_log( ic, "Stream error while authenticating" ); imc_logout( ic, FALSE ); return XT_ABORT; } if( strcmp( node->name, "success" ) == 0 ) { imcb_log( ic, "Authentication finished" ); jd->flags |= JFLAG_AUTHENTICATED | JFLAG_STREAM_RESTART; } else if( strcmp( node->name, "failure" ) == 0 ) { imcb_error( ic, "Authentication failure" ); imc_logout( ic, FALSE ); return XT_ABORT; } return XT_HANDLED; } /* This one is needed to judge if we'll do authentication using IQ or SASL. It's done by checking if the from the server has a version attribute. I don't know if this is the right way though... */ gboolean sasl_supported( struct im_connection *ic ) { struct jabber_data *jd = ic->proto_data; return ( jd->xt && jd->xt->root && xt_find_attr( jd->xt->root, "version" ) ) != 0; } void sasl_oauth2_init( struct im_connection *ic ) { struct jabber_data *jd = ic->proto_data; char *msg, *url; imcb_log( ic, "Starting OAuth authentication" ); /* Temporary contact, just used to receive the OAuth response. */ imcb_add_buddy( ic, JABBER_OAUTH_HANDLE, NULL ); url = oauth2_url( jd->oauth2_service ); msg = g_strdup_printf( "Open this URL in your browser to authenticate: %s", url ); imcb_buddy_msg( ic, JABBER_OAUTH_HANDLE, msg, 0, 0 ); imcb_buddy_msg( ic, JABBER_OAUTH_HANDLE, "Respond to this message with the returned " "authorization token.", 0, 0 ); g_free( msg ); g_free( url ); } static gboolean sasl_oauth2_remove_contact( gpointer data, gint fd, b_input_condition cond ) { struct im_connection *ic = data; if( g_slist_find( jabber_connections, ic ) ) imcb_remove_buddy( ic, JABBER_OAUTH_HANDLE, NULL ); return FALSE; } static void sasl_oauth2_got_token( gpointer data, const char *access_token, const char *refresh_token, const char *error ); int sasl_oauth2_get_refresh_token( struct im_connection *ic, const char *msg ) { struct jabber_data *jd = ic->proto_data; char *code; int ret; imcb_log( ic, "Requesting OAuth access token" ); /* Don't do it here because the caller may get confused if the contact we're currently sending a message to is deleted. */ b_timeout_add( 1, sasl_oauth2_remove_contact, ic ); code = g_strdup( msg ); g_strstrip( code ); ret = oauth2_access_token( jd->oauth2_service, OAUTH2_AUTH_CODE, code, sasl_oauth2_got_token, ic ); g_free( code ); return ret; } int sasl_oauth2_refresh( struct im_connection *ic, const char *refresh_token ) { struct jabber_data *jd = ic->proto_data; return oauth2_access_token( jd->oauth2_service, OAUTH2_AUTH_REFRESH, refresh_token, sasl_oauth2_got_token, ic ); } static void sasl_oauth2_got_token( gpointer data, const char *access_token, const char *refresh_token, const char *error ) { struct im_connection *ic = data; struct jabber_data *jd; GSList *auth = NULL; if( g_slist_find( jabber_connections, ic ) == NULL ) return; jd = ic->proto_data; if( access_token == NULL ) { imcb_error( ic, "OAuth failure (%s)", error ); imc_logout( ic, TRUE ); return; } oauth_params_parse( &auth, ic->acc->pass ); if( refresh_token ) oauth_params_set( &auth, "refresh_token", refresh_token ); if( access_token ) oauth_params_set( &auth, "access_token", access_token ); g_free( ic->acc->pass ); ic->acc->pass = oauth_params_string( auth ); oauth_params_free( &auth ); g_free( jd->oauth2_access_token ); jd->oauth2_access_token = g_strdup( access_token ); jabber_connect( ic ); } bitlbee-3.2.1/protocols/jabber/conference.c0000644000175000017500000003060712245474076020250 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - Conference rooms * * * * Copyright 2007-2012 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include "jabber.h" #include "sha1.h" static xt_status jabber_chat_join_failed( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); struct groupchat *jabber_chat_join( struct im_connection *ic, const char *room, const char *nick, const char *password ) { struct jabber_chat *jc; struct xt_node *node; struct groupchat *c; char *roomjid; roomjid = g_strdup_printf( "%s/%s", room, nick ); node = xt_new_node( "x", NULL, NULL ); xt_add_attr( node, "xmlns", XMLNS_MUC ); if( password ) xt_add_child( node, xt_new_node( "password", password, NULL ) ); node = jabber_make_packet( "presence", NULL, roomjid, node ); jabber_cache_add( ic, node, jabber_chat_join_failed ); if( !jabber_write_packet( ic, node ) ) { g_free( roomjid ); return NULL; } jc = g_new0( struct jabber_chat, 1 ); jc->name = jabber_normalize( room ); if( ( jc->me = jabber_buddy_add( ic, roomjid ) ) == NULL ) { g_free( roomjid ); g_free( jc->name ); g_free( jc ); return NULL; } /* roomjid isn't normalized yet, and we need an original version of the nick to send a proper presence update. */ jc->my_full_jid = roomjid; c = imcb_chat_new( ic, room ); c->data = jc; return c; } struct groupchat *jabber_chat_with( struct im_connection *ic, char *who ) { struct jabber_data *jd = ic->proto_data; struct jabber_chat *jc; struct groupchat *c; sha1_state_t sum; double now = gettime(); char *uuid, *rjid, *cserv; sha1_init( &sum ); sha1_append( &sum, (uint8_t*) ic->acc->user, strlen( ic->acc->user ) ); sha1_append( &sum, (uint8_t*) &now, sizeof( now ) ); sha1_append( &sum, (uint8_t*) who, strlen( who ) ); uuid = sha1_random_uuid( &sum ); if( jd->flags & JFLAG_GTALK ) cserv = g_strdup( "groupchat.google.com" ); else /* Guess... */ cserv = g_strdup_printf( "conference.%s", jd->server ); rjid = g_strdup_printf( "private-chat-%s@%s", uuid, cserv ); g_free( uuid ); g_free( cserv ); c = jabber_chat_join( ic, rjid, jd->username, NULL ); g_free( rjid ); if( c == NULL ) return NULL; jc = c->data; jc->invite = g_strdup( who ); return c; } static xt_status jabber_chat_join_failed( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct jabber_error *err; struct jabber_buddy *bud; char *room; room = xt_find_attr( orig, "to" ); bud = jabber_buddy_by_jid( ic, room, 0 ); err = jabber_error_parse( xt_find_node( node->children, "error" ), XMLNS_STANZA_ERROR ); if( err ) { imcb_error( ic, "Error joining groupchat %s: %s%s%s", room, err->code, err->text ? ": " : "", err->text ? err->text : "" ); jabber_error_free( err ); } if( bud ) jabber_chat_free( jabber_chat_by_jid( ic, bud->bare_jid ) ); return XT_HANDLED; } struct groupchat *jabber_chat_by_jid( struct im_connection *ic, const char *name ) { char *normalized = jabber_normalize( name ); GSList *l; struct groupchat *ret; struct jabber_chat *jc; for( l = ic->groupchats; l; l = l->next ) { ret = l->data; jc = ret->data; if( strcmp( normalized, jc->name ) == 0 ) break; } g_free( normalized ); return l ? ret : NULL; } void jabber_chat_free( struct groupchat *c ) { struct jabber_chat *jc = c->data; jabber_buddy_remove_bare( c->ic, jc->name ); g_free( jc->my_full_jid ); g_free( jc->name ); g_free( jc->invite ); g_free( jc ); imcb_chat_free( c ); } int jabber_chat_msg( struct groupchat *c, char *message, int flags ) { struct im_connection *ic = c->ic; struct jabber_chat *jc = c->data; struct xt_node *node; jc->flags |= JCFLAG_MESSAGE_SENT; node = xt_new_node( "body", message, NULL ); node = jabber_make_packet( "message", "groupchat", jc->name, node ); if( !jabber_write_packet( ic, node ) ) { xt_free_node( node ); return 0; } xt_free_node( node ); return 1; } int jabber_chat_topic( struct groupchat *c, char *topic ) { struct im_connection *ic = c->ic; struct jabber_chat *jc = c->data; struct xt_node *node; node = xt_new_node( "subject", topic, NULL ); node = jabber_make_packet( "message", "groupchat", jc->name, node ); if( !jabber_write_packet( ic, node ) ) { xt_free_node( node ); return 0; } xt_free_node( node ); return 1; } int jabber_chat_leave( struct groupchat *c, const char *reason ) { struct im_connection *ic = c->ic; struct jabber_chat *jc = c->data; struct xt_node *node; node = xt_new_node( "x", NULL, NULL ); xt_add_attr( node, "xmlns", XMLNS_MUC ); node = jabber_make_packet( "presence", "unavailable", jc->my_full_jid, node ); if( !jabber_write_packet( ic, node ) ) { xt_free_node( node ); return 0; } xt_free_node( node ); return 1; } void jabber_chat_invite( struct groupchat *c, char *who, char *message ) { struct xt_node *node; struct im_connection *ic = c->ic; struct jabber_chat *jc = c->data; node = xt_new_node( "reason", message, NULL ); node = xt_new_node( "invite", NULL, node ); xt_add_attr( node, "to", who ); node = xt_new_node( "x", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_MUC_USER ); node = jabber_make_packet( "message", NULL, jc->name, node ); jabber_write_packet( ic, node ); xt_free_node( node ); } /* Not really the same syntax as the normal pkt_ functions, but this isn't called by the xmltree parser directly and this way I can add some extra parameters so we won't have to repeat too many things done by the caller already. */ void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node ) { struct groupchat *chat; struct xt_node *c; char *type = xt_find_attr( node, "type" ); struct jabber_data *jd = ic->proto_data; struct jabber_chat *jc; char *s; if( ( chat = jabber_chat_by_jid( ic, bud->bare_jid ) ) == NULL ) { /* How could this happen?? We could do kill( self, 11 ) now or just wait for the OS to do it. :-) */ return; } jc = chat->data; if( type == NULL && !( bud->flags & JBFLAG_IS_CHATROOM ) ) { bud->flags |= JBFLAG_IS_CHATROOM; /* If this one wasn't set yet, this buddy just joined the chat. Slightly hackish way of finding out eh? ;-) */ /* This is pretty messy... Here it sets ext_jid to the real JID of the participant. Works for non-anonymized channels. Might break if someone joins a chat twice, though. */ for( c = node->children; ( c = xt_find_node( c, "x" ) ); c = c->next ) if( ( s = xt_find_attr( c, "xmlns" ) ) && ( strcmp( s, XMLNS_MUC_USER ) == 0 ) ) { struct xt_node *item; item = xt_find_node( c->children, "item" ); if( ( s = xt_find_attr( item, "jid" ) ) ) { /* Yay, found what we need. :-) */ bud->ext_jid = jabber_normalize( s ); break; } } /* Make up some other handle, if necessary. */ if( bud->ext_jid == NULL ) { if( bud == jc->me ) { bud->ext_jid = g_strdup( jd->me ); } else { int i; /* Don't want the nick to be at the end, so let's think of some slightly different notation to use for anonymous groupchat participants in BitlBee. */ bud->ext_jid = g_strdup_printf( "%s=%s", bud->resource, bud->bare_jid ); /* And strip any unwanted characters. */ for( i = 0; bud->resource[i]; i ++ ) if( bud->ext_jid[i] == '=' || bud->ext_jid[i] == '@' ) bud->ext_jid[i] = '_'; /* Some program-specific restrictions. */ imcb_clean_handle( ic, bud->ext_jid ); } bud->flags |= JBFLAG_IS_ANONYMOUS; } if( bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS ) { /* If JIDs are anonymized, add them to the local list for the duration of this chat. */ imcb_add_buddy( ic, bud->ext_jid, NULL ); imcb_buddy_nick_hint( ic, bud->ext_jid, bud->resource ); } if( bud == jc->me && jc->invite != NULL ) { char *msg = g_strdup_printf( "Please join me in room %s", jc->name ); jabber_chat_invite( chat, jc->invite, msg ); g_free( jc->invite ); g_free( msg ); jc->invite = NULL; } s = strchr( bud->ext_jid, '/' ); if( s ) *s = 0; /* Should NEVER be NULL, but who knows... */ imcb_chat_add_buddy( chat, bud->ext_jid ); if( s ) *s = '/'; } else if( type ) /* type can only be NULL or "unavailable" in this function */ { if( ( bud->flags & JBFLAG_IS_CHATROOM ) && bud->ext_jid ) { s = strchr( bud->ext_jid, '/' ); if( s ) *s = 0; imcb_chat_remove_buddy( chat, bud->ext_jid, NULL ); if( bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS ) imcb_remove_buddy( ic, bud->ext_jid, NULL ); if( s ) *s = '/'; } if( bud == jc->me ) jabber_chat_free( chat ); } } void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node ) { struct xt_node *subject = xt_find_node( node->children, "subject" ); struct xt_node *body = xt_find_node( node->children, "body" ); struct groupchat *chat = bud ? jabber_chat_by_jid( ic, bud->bare_jid ) : NULL; struct jabber_chat *jc = chat ? chat->data : NULL; char *s; if( subject && chat ) { s = bud ? strchr( bud->ext_jid, '/' ) : NULL; if( s ) *s = 0; imcb_chat_topic( chat, bud ? bud->ext_jid : NULL, subject->text_len > 0 ? subject->text : NULL, jabber_get_timestamp( node ) ); if( s ) *s = '/'; } if( bud == NULL || ( jc && ~jc->flags & JCFLAG_MESSAGE_SENT && bud == jc->me ) ) { char *nick; if( body == NULL || body->text_len == 0 ) /* Meh. Empty messages aren't very interesting, no matter how much some servers love to send them. */ return; s = xt_find_attr( node, "from" ); /* pkt_message() already NULL-checked this one. */ nick = strchr( s, '/' ); if( nick ) { /* If this message included a resource/nick we don't know, we might still know the groupchat itself. */ *nick = 0; chat = jabber_chat_by_jid( ic, s ); *nick = '/'; nick ++; } else { /* message.c uses the EXACT_JID option, so bud should always be NULL here for bare JIDs. */ chat = jabber_chat_by_jid( ic, s ); } if( nick == NULL ) { /* This is fine, the groupchat itself isn't in jd->buddies. */ if( chat ) imcb_chat_log( chat, "From conference server: %s", body->text ); else imcb_log( ic, "System message from unknown groupchat %s: %s", s, body->text ); } else { /* This can happen too, at least when receiving a backlog when just joining a channel. */ if( chat ) imcb_chat_log( chat, "Message from unknown participant %s: %s", nick, body->text ); else imcb_log( ic, "Groupchat message from unknown JID %s: %s", s, body->text ); } return; } else if( chat == NULL ) { /* How could this happen?? We could do kill( self, 11 ) now or just wait for the OS to do it. :-) */ return; } if( body && body->text_len > 0 ) { s = strchr( bud->ext_jid, '/' ); if( s ) *s = 0; imcb_chat_msg( chat, bud->ext_jid, body->text, 0, jabber_get_timestamp( node ) ); if( s ) *s = '/'; } } bitlbee-3.2.1/protocols/jabber/s5bytestream.c0000644000175000017500000010170312245474076020564 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - SOCKS5 Bytestreams ( XEP-0065 ) * * * * Copyright 2007 Uli Meis * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include "jabber.h" #include "sha1.h" #include "lib/ftutil.h" #include struct bs_transfer { struct jabber_transfer *tf; jabber_streamhost_t *sh; GSList *streamhosts; enum { BS_PHASE_CONNECT, BS_PHASE_CONNECTED, BS_PHASE_REQUEST, BS_PHASE_REPLY } phase; /* SHA1( SID + Initiator JID + Target JID) */ char *pseudoadr; gint connect_timeout; char peek_buf[64]; int peek_buf_len; }; struct socks5_message { unsigned char ver; union { unsigned char cmd; unsigned char rep; } cmdrep; unsigned char rsv; unsigned char atyp; unsigned char addrlen; unsigned char address[40]; in_port_t port; } __attribute__ ((packed)); char *socks5_reply_code[] = { "succeeded", "general SOCKS server failure", "connection not allowed by ruleset", "Network unreachable", "Host unreachable", "Connection refused", "TTL expired", "Command not supported", "Address type not supported", "unassigned"}; /* connect() timeout in seconds. */ #define JABBER_BS_CONTIMEOUT 15 /* listen timeout */ #define JABBER_BS_LISTEN_TIMEOUT 90 /* very useful */ #define ASSERTSOCKOP(op, msg) \ if( (op) == -1 ) \ return jabber_bs_abort( bt , msg ": %s", strerror( errno ) ); gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... ); void jabber_bs_canceled( file_transfer_t *ft , char *reason ); void jabber_bs_free_transfer( file_transfer_t *ft ); gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond ); gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents ); gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen ); void jabber_bs_recv_answer_request( struct bs_transfer *bt ); gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ); gboolean jabber_bs_recv_write_request( file_transfer_t *ft ); gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond ); gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error ); int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode ); gboolean jabber_bs_send_handshake_abort( struct bs_transfer *bt, char *error ); gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts ); gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond ); static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); void jabber_bs_send_activate( struct bs_transfer *bt ); /* * Frees a bs_transfer struct and calls the SI free function */ void jabber_bs_free_transfer( file_transfer_t *ft) { struct jabber_transfer *tf = ft->data; struct bs_transfer *bt = tf->streamhandle; jabber_streamhost_t *sh; if ( bt->connect_timeout ) { b_event_remove( bt->connect_timeout ); bt->connect_timeout = 0; } if ( tf->watch_in ) b_event_remove( tf->watch_in ); if( tf->watch_out ) b_event_remove( tf->watch_out ); g_free( bt->pseudoadr ); while( bt->streamhosts ) { sh = bt->streamhosts->data; bt->streamhosts = g_slist_remove( bt->streamhosts, sh ); g_free( sh->jid ); g_free( sh->host ); g_free( sh ); } g_free( bt ); jabber_si_free_transfer( ft ); } /* * Checks if buflen data is available on the socket and * writes it to buffer if that's the case. */ gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen ) { int ret; int fd = bt->tf->fd; if( buflen > sizeof( bt->peek_buf ) ) return jabber_bs_abort( bt, "BUG: %d > sizeof(peek_buf)", buflen ); ASSERTSOCKOP( ret = recv( fd, bt->peek_buf + bt->peek_buf_len, buflen - bt->peek_buf_len, 0 ), "recv() on SOCKS5 connection" ); if( ret == 0 ) return jabber_bs_abort( bt, "Remote end closed connection" ); bt->peek_buf_len += ret; memcpy( buffer, bt->peek_buf, bt->peek_buf_len ); if( bt->peek_buf_len == buflen ) { /* If we have everything the caller wanted, reset the peek buffer. */ bt->peek_buf_len = 0; return buflen; } else return bt->peek_buf_len; } /* * This function is scheduled in bs_handshake via b_timeout_add after a (non-blocking) connect(). */ gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond ) { struct bs_transfer *bt = data; bt->connect_timeout = 0; jabber_bs_abort( bt, "no connection after %d seconds", bt->tf->ft->sending ? JABBER_BS_LISTEN_TIMEOUT : JABBER_BS_CONTIMEOUT ); return FALSE; } /* * Polls the socket, checks for errors and removes a connect timer * if there is one. */ gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents ) { struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR }; if ( bt->connect_timeout ) { b_event_remove( bt->connect_timeout ); bt->connect_timeout = 0; } ASSERTSOCKOP( poll( &pfd, 1, 0 ), "poll()" ) if( pfd.revents & POLLERR ) { int sockerror; socklen_t errlen = sizeof( sockerror ); if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) ) return jabber_bs_abort( bt, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" ); if ( bt->phase == BS_PHASE_CONNECTED ) return jabber_bs_abort( bt, "connect failed: %s", strerror( sockerror ) ); return jabber_bs_abort( bt, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) ); } if( pfd.revents & POLLHUP ) return jabber_bs_abort( bt, "Remote end closed connection" ); *revents = pfd.revents; return TRUE; } /* * Used for receive and send path. */ gboolean jabber_bs_abort( struct bs_transfer *bt, 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 ); if( bt->tf->ft->sending ) return jabber_bs_send_handshake_abort( bt, error ); else return jabber_bs_recv_handshake_abort( bt, error ); } /* Bad luck */ void jabber_bs_canceled( file_transfer_t *ft , char *reason ) { struct jabber_transfer *tf = ft->data; imcb_log( tf->ic, "File transfer aborted: %s", reason ); } /* * Parses an incoming bytestream request and calls jabber_bs_handshake on success. */ int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode) { char *sid, *ini_jid, *tgt_jid, *mode, *iq_id; struct jabber_data *jd = ic->proto_data; struct jabber_transfer *tf = NULL; GSList *tflist; struct bs_transfer *bt; GSList *shlist=NULL; struct xt_node *shnode; sha1_state_t sha; char hash_hex[41]; unsigned char hash[20]; int i; if( !(iq_id = xt_find_attr( node, "id" ) ) || !(ini_jid = xt_find_attr( node, "from" ) ) || !(tgt_jid = xt_find_attr( node, "to" ) ) || !(sid = xt_find_attr( qnode, "sid" ) ) ) { imcb_log( ic, "WARNING: Received incomplete SI bytestream request"); return XT_HANDLED; } if( ( mode = xt_find_attr( qnode, "mode" ) ) && ( strcmp( mode, "tcp" ) != 0 ) ) { imcb_log( ic, "WARNING: Received SI Request for unsupported bytestream mode %s", xt_find_attr( qnode, "mode" ) ); return XT_HANDLED; } shnode = qnode->children; while( ( shnode = xt_find_node( shnode, "streamhost" ) ) ) { char *jid, *host, *port_s; int port; if( ( jid = xt_find_attr( shnode, "jid" ) ) && ( host = xt_find_attr( shnode, "host" ) ) && ( port_s = xt_find_attr( shnode, "port" ) ) && ( sscanf( port_s, "%d", &port ) == 1 ) ) { jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 ); sh->jid = g_strdup(jid); sh->host = g_strdup(host); sprintf( sh->port, "%u", port ); shlist = g_slist_append( shlist, sh ); } shnode = shnode->next; } if( !shlist ) { imcb_log( ic, "WARNING: Received incomplete SI bytestream request, no parseable streamhost entries"); return XT_HANDLED; } /* Let's see if we can find out what this bytestream should be for... */ for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) { struct jabber_transfer *tft = tflist->data; if( ( strcmp( tft->sid, sid ) == 0 ) && ( strcmp( tft->ini_jid, ini_jid ) == 0 ) && ( strcmp( tft->tgt_jid, tgt_jid ) == 0 ) ) { tf = tft; break; } } if (!tf) { imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); return XT_HANDLED; } /* iq_id and canceled can be reused since SI is done */ g_free( tf->iq_id ); tf->iq_id = g_strdup( iq_id ); tf->ft->canceled = jabber_bs_canceled; /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ sha1_init( &sha ); sha1_append( &sha, (unsigned char*) sid, strlen( sid ) ); sha1_append( &sha, (unsigned char*) ini_jid, strlen( ini_jid ) ); sha1_append( &sha, (unsigned char*) tgt_jid, strlen( tgt_jid ) ); sha1_finish( &sha, hash ); for( i = 0; i < 20; i ++ ) sprintf( hash_hex + i * 2, "%02x", hash[i] ); bt = g_new0( struct bs_transfer, 1 ); bt->tf = tf; bt->streamhosts = shlist; bt->sh = shlist->data; bt->phase = BS_PHASE_CONNECT; bt->pseudoadr = g_strdup( hash_hex ); tf->streamhandle = bt; tf->ft->free = jabber_bs_free_transfer; jabber_bs_recv_handshake( bt, -1, 0 ); return XT_HANDLED; } /* * This is what a protocol handshake can look like in cooperative multitasking :) * Might be confusing at first because it's called from different places and is recursing. * (places being the event thread, bs_request, bs_handshake_abort, and itself) * * All in all, it turned out quite nice :) */ gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond ) { struct bs_transfer *bt = data; short revents; int gret; if ( ( fd != -1 ) && !jabber_bs_poll( bt, fd, &revents ) ) return FALSE; switch( bt->phase ) { case BS_PHASE_CONNECT: { struct addrinfo hints, *rp; memset( &hints, 0, sizeof( struct addrinfo ) ); hints.ai_socktype = SOCK_STREAM; if ( ( gret = getaddrinfo( bt->sh->host, bt->sh->port, &hints, &rp ) ) != 0 ) return jabber_bs_abort( bt, "getaddrinfo() failed: %s", gai_strerror( gret ) ); ASSERTSOCKOP( bt->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" ); sock_make_nonblocking( fd ); imcb_log( bt->tf->ic, "File %s: Connecting to streamhost %s:%s", bt->tf->ft->file_name, bt->sh->host, bt->sh->port ); if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) && ( errno != EINPROGRESS ) ) return jabber_bs_abort( bt , "connect() failed: %s", strerror( errno ) ); freeaddrinfo( rp ); bt->phase = BS_PHASE_CONNECTED; bt->tf->watch_out = b_input_add( fd, B_EV_IO_WRITE, jabber_bs_recv_handshake, bt ); /* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */ bt->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bt ); bt->tf->watch_in = 0; return FALSE; } case BS_PHASE_CONNECTED: { struct { unsigned char ver; unsigned char nmethods; unsigned char method; } socks5_hello = { .ver = 5, .nmethods = 1, .method = 0x00 /* no auth */ /* one could also implement username/password. If you know * a jabber client or proxy that actually does it, tell me. */ }; ASSERTSOCKOP( send( fd, &socks5_hello, sizeof( socks5_hello ) , 0 ), "Sending auth request" ); bt->phase = BS_PHASE_REQUEST; bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_recv_handshake, bt ); bt->tf->watch_out = 0; return FALSE; } case BS_PHASE_REQUEST: { struct socks5_message socks5_connect = { .ver = 5, .cmdrep.cmd = 0x01, .rsv = 0, .atyp = 0x03, .addrlen = strlen( bt->pseudoadr ), .port = 0 }; int ret; char buf[2]; /* If someone's trying to be funny and sends only one byte at a time we'll fail :) */ ASSERTSOCKOP( ret = recv( fd, buf, 2, 0 ) , "Receiving auth reply" ); if( !( ret == 2 ) || !( buf[0] == 5 ) || !( buf[1] == 0 ) ) return jabber_bs_abort( bt, "Auth not accepted by streamhost (reply: len=%d, ver=%d, status=%d)", ret, buf[0], buf[1] ); /* copy hash into connect message */ memcpy( socks5_connect.address, bt->pseudoadr, socks5_connect.addrlen ); ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_message ), 0 ) , "Sending SOCKS5 Connect" ); bt->phase = BS_PHASE_REPLY; return TRUE; } case BS_PHASE_REPLY: { struct socks5_message socks5_reply; int ret; if ( !( ret = jabber_bs_peek( bt, &socks5_reply, sizeof( struct socks5_message ) ) ) ) return FALSE; if ( ret < 5 ) /* header up to address length */ return TRUE; else if( ret < sizeof( struct socks5_message ) ) { /* Either a buggy proxy or just one that doesnt regard * the SHOULD in XEP-0065 saying the reply SHOULD * contain the address. We'll take it, so make sure the * next jabber_bs_peek starts with an empty buffer. */ bt->peek_buf_len = 0; } if( !( socks5_reply.ver == 5 ) || !( socks5_reply.cmdrep.rep == 0 ) ) { char errstr[128] = ""; if( ( socks5_reply.ver == 5 ) && ( socks5_reply.cmdrep.rep < ( sizeof( socks5_reply_code ) / sizeof( socks5_reply_code[0] ) ) ) ) { sprintf( errstr, "with \"%s\" ", socks5_reply_code[ socks5_reply.cmdrep.rep ] ); } return jabber_bs_abort( bt, "SOCKS5 CONNECT failed %s(reply: ver=%d, rep=%d, atyp=%d, addrlen=%d)", errstr, socks5_reply.ver, socks5_reply.cmdrep.rep, socks5_reply.atyp, socks5_reply.addrlen); } /* usually a proxy sends back the 40 bytes address but I encountered at least one (of jabber.cz) * that sends atyp=0 addrlen=0 and only 6 bytes (one less than one would expect). * Therefore I removed the wait for more bytes. Since we don't care about what else the proxy * is sending, it shouldnt matter */ if( bt->tf->ft->sending ) jabber_bs_send_activate( bt ); else jabber_bs_recv_answer_request( bt ); return FALSE; } default: /* BUG */ imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" ); bt->tf->watch_in = 0; return FALSE; } } /* * If the handshake failed we can try the next streamhost, if there is one. * An intelligent sender would probably specify himself as the first streamhost and * a proxy as the second (Kopete and PSI are examples here). That way, a (potentially) * slow proxy is only used if neccessary. This of course also means, that the timeout * per streamhost should be kept short. If one or two firewalled adresses are specified, * they have to timeout first before a proxy is tried. */ gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error ) { struct jabber_transfer *tf = bt->tf; struct xt_node *reply, *iqnode; GSList *shlist; imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)", tf->ft->file_name, bt->sh->host, bt->sh->port, error ); /* Alright, this streamhost failed, let's try the next... */ bt->phase = BS_PHASE_CONNECT; shlist = g_slist_find( bt->streamhosts, bt->sh ); if( shlist && shlist->next ) { bt->sh = shlist->next->data; return jabber_bs_recv_handshake( bt, -1, 0 ); } /* out of stream hosts */ iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL ); reply = jabber_make_error_packet( iqnode, "item-not-found", "cancel" , "404" ); xt_free_node( iqnode ); xt_add_attr( reply, "id", tf->iq_id ); if( !jabber_write_packet( tf->ic, reply ) ) imcb_log( tf->ic, "WARNING: Error transmitting bytestream response" ); xt_free_node( reply ); imcb_file_canceled( tf->ic, tf->ft, "couldn't connect to any streamhosts" ); /* MUST always return FALSE! */ return FALSE; } /* * After the SOCKS5 handshake succeeds we need to inform the initiator which streamhost we chose. * If he is the streamhost himself, he might already know that. However, if it's a proxy, * the initiator will have to make a connection himself. */ void jabber_bs_recv_answer_request( struct bs_transfer *bt ) { struct jabber_transfer *tf = bt->tf; struct xt_node *reply; imcb_log( tf->ic, "File %s: established SOCKS5 connection to %s:%s", tf->ft->file_name, bt->sh->host, bt->sh->port ); tf->ft->data = tf; tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt ); tf->ft->write_request = jabber_bs_recv_write_request; reply = xt_new_node( "streamhost-used", NULL, NULL ); xt_add_attr( reply, "jid", bt->sh->jid ); reply = xt_new_node( "query", NULL, reply ); xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS ); reply = jabber_make_packet( "iq", "result", tf->ini_jid, reply ); xt_add_attr( reply, "id", tf->iq_id ); if( !jabber_write_packet( tf->ic, reply ) ) imcb_file_canceled( tf->ic, tf->ft, "Error transmitting bytestream response" ); xt_free_node( reply ); } /* * This function is called from write_request directly. If no data is available, it will install itself * as a watcher for input on fd and once that happens, deliver the data and unschedule itself again. */ gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond ) { int ret; struct bs_transfer *bt = data; struct jabber_transfer *tf = bt->tf; if( fd != -1 ) /* called via event thread */ { tf->watch_in = 0; ASSERTSOCKOP( ret = recv( fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) , "Receiving" ); } else { /* called directly. There might not be any data available. */ if( ( ( ret = recv( tf->fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) ) == -1 ) && ( errno != EAGAIN ) ) return jabber_bs_abort( bt, "Receiving: %s", strerror( errno ) ); if( ( ret == -1 ) && ( errno == EAGAIN ) ) { tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt ); return FALSE; } } /* shouldn't happen since we know the file size */ if( ret == 0 ) return jabber_bs_abort( bt, "Remote end closed connection" ); tf->bytesread += ret; if( tf->bytesread >= tf->ft->file_size ) imcb_file_finished( tf->ic, tf->ft ); tf->ft->write( tf->ft, tf->ft->buffer, ret ); return FALSE; } /* * imc callback that is invoked when it is ready to receive some data. */ gboolean jabber_bs_recv_write_request( file_transfer_t *ft ) { struct jabber_transfer *tf = ft->data; if( tf->watch_in ) { imcb_file_canceled( tf->ic, ft, "BUG in jabber file transfer: write_request called when already watching for input" ); return FALSE; } jabber_bs_recv_read( tf->streamhandle, -1 , 0 ); return TRUE; } /* * Issues a write_request to imc. * */ gboolean jabber_bs_send_can_write( gpointer data, gint fd, b_input_condition cond ) { struct bs_transfer *bt = data; bt->tf->watch_out = 0; bt->tf->ft->write_request( bt->tf->ft ); 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). */ gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len ) { struct jabber_transfer *tf = ft->data; struct bs_transfer *bt = tf->streamhandle; int ret; if( tf->watch_out ) return jabber_bs_abort( bt, "BUG: write() called while watching " ); /* TODO: catch broken pipe */ ASSERTSOCKOP( ret = send( tf->fd, buffer, len, 0 ), "Sending" ); tf->byteswritten += ret; /* TODO: this should really not be fatal */ if( ret < len ) return jabber_bs_abort( bt, "send() sent %d instead of %d (send buffer too big!)", ret, len ); if( tf->byteswritten >= ft->file_size ) imcb_file_finished( tf->ic, ft ); else bt->tf->watch_out = b_input_add( tf->fd, B_EV_IO_WRITE, jabber_bs_send_can_write, bt ); return TRUE; } /* * Handles the reply by the receiver containing the used streamhost. */ static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct jabber_transfer *tf = NULL; struct jabber_data *jd = ic->proto_data; struct bs_transfer *bt; GSList *tflist; struct xt_node *c; char *sid, *jid; if( !( c = xt_find_node( node->children, "query" ) ) || !( c = xt_find_node( c->children, "streamhost-used" ) ) || !( jid = xt_find_attr( c, "jid" ) ) ) { imcb_log( ic, "WARNING: Received incomplete bytestream reply" ); return XT_HANDLED; } if( !( c = xt_find_node( orig->children, "query" ) ) || !( sid = xt_find_attr( c, "sid" ) ) ) { imcb_log( ic, "WARNING: Error parsing request corresponding to the incoming bytestream reply" ); return XT_HANDLED; } /* Let's see if we can find out what this bytestream should be for... */ for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) { struct jabber_transfer *tft = tflist->data; if( ( strcmp( tft->sid, sid ) == 0 ) ) { tf = tft; break; } } if( !tf ) { imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply to unknown request" ); return XT_HANDLED; } bt = tf->streamhandle; tf->accepted = TRUE; if( strcmp( jid, tf->ini_jid ) == 0 ) { /* we're streamhost and target */ if( bt->phase == BS_PHASE_REPLY ) { /* handshake went through, let's start transferring */ tf->ft->write_request( tf->ft ); } } else { /* using a proxy, abort listen */ if( tf->watch_in ) { b_event_remove( tf->watch_in ); tf->watch_in = 0; } if( tf->fd != -1 ) { closesocket( tf->fd ); tf->fd = -1; } if ( bt->connect_timeout ) { b_event_remove( bt->connect_timeout ); bt->connect_timeout = 0; } GSList *shlist; for( shlist = jd->streamhosts ; shlist ; shlist = g_slist_next( shlist ) ) { jabber_streamhost_t *sh = shlist->data; if( strcmp( sh->jid, jid ) == 0 ) { bt->sh = sh; jabber_bs_recv_handshake( bt, -1, 0 ); return XT_HANDLED; } } imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply with unknown streamhost %s", jid ); } return XT_HANDLED; } /* * Tell the proxy to activate the stream. Looks like this: * * * * tgt_jid * * */ void jabber_bs_send_activate( struct bs_transfer *bt ) { struct xt_node *node; node = xt_new_node( "activate", bt->tf->tgt_jid, NULL ); node = xt_new_node( "query", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_BYTESTREAMS ); xt_add_attr( node, "sid", bt->tf->sid ); node = jabber_make_packet( "iq", "set", bt->sh->jid, node ); jabber_cache_add( bt->tf->ic, node, jabber_bs_send_handle_activate ); jabber_write_packet( bt->tf->ic, node ); } /* * The proxy has activated the bytestream. * We can finally start pushing some data out. */ static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { char *sid; GSList *tflist; struct jabber_transfer *tf = NULL; struct xt_node *query; struct jabber_data *jd = ic->proto_data; query = xt_find_node( orig->children, "query" ); sid = xt_find_attr( query, "sid" ); for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) { struct jabber_transfer *tft = tflist->data; if( ( strcmp( tft->sid, sid ) == 0 ) ) { tf = tft; break; } } if( !tf ) { imcb_log( ic, "WARNING: Received SOCKS5 bytestream activation for unknown stream" ); return XT_HANDLED; } imcb_log( tf->ic, "File %s: SOCKS5 handshake and activation successful! Transfer about to start...", tf->ft->file_name ); /* handshake went through, let's start transferring */ tf->ft->write_request( tf->ft ); return XT_HANDLED; } jabber_streamhost_t *jabber_si_parse_proxy( struct im_connection *ic, char *proxy ) { char *host, *port, *jid; jabber_streamhost_t *sh; if( ( ( host = strchr( proxy, ',' ) ) == 0 ) || ( ( port = strchr( host+1, ',' ) ) == 0 ) ) { imcb_log( ic, "Error parsing proxy setting: \"%s\" (ignored)", proxy ); return NULL; } jid = proxy; *host++ = '\0'; *port++ = '\0'; sh = g_new0( jabber_streamhost_t, 1 ); sh->jid = g_strdup( jid ); sh->host = g_strdup( host ); g_snprintf( sh->port, sizeof( sh->port ), "%s", port ); return sh; } void jabber_si_set_proxies( struct bs_transfer *bt ) { struct jabber_transfer *tf = bt->tf; struct jabber_data *jd = tf->ic->proto_data; char *proxysetting = g_strdup ( set_getstr( &tf->ic->acc->set, "proxy" ) ); char *proxy, *next, *errmsg = NULL; char port[6]; char host[HOST_NAME_MAX+1]; jabber_streamhost_t *sh, *sh2; GSList *streamhosts = jd->streamhosts; proxy = proxysetting; while ( proxy && ( *proxy!='\0' ) ) { if( ( next = strchr( proxy, ';' ) ) ) *next++ = '\0'; if( strcmp( proxy, "" ) == 0 ) { if( ( tf->fd = ft_listen( &tf->saddr, host, port, jd->fd, FALSE, &errmsg ) ) != -1 ) { sh = g_new0( jabber_streamhost_t, 1 ); sh->jid = g_strdup( tf->ini_jid ); sh->host = g_strdup( host ); g_snprintf( sh->port, sizeof( sh->port ), "%s", port ); bt->streamhosts = g_slist_append( bt->streamhosts, sh ); bt->tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_send_handshake, bt ); bt->connect_timeout = b_timeout_add( JABBER_BS_LISTEN_TIMEOUT * 1000, jabber_bs_connect_timeout, bt ); } else { imcb_log( tf->ic, "Transferring file %s: couldn't listen locally(non fatal, check your ft_listen setting in bitlbee.conf): %s", tf->ft->file_name, errmsg ); } } else if( strcmp( proxy, "" ) == 0 ) { while ( streamhosts ) { sh = g_new0( jabber_streamhost_t, 1 ); sh2 = streamhosts->data; sh->jid = g_strdup( sh2->jid ); sh->host = g_strdup( sh2->host ); strcpy( sh->port, sh2->port ); bt->streamhosts = g_slist_append( bt->streamhosts, sh ); streamhosts = g_slist_next( streamhosts ); } } else if( ( sh = jabber_si_parse_proxy( tf->ic, proxy ) ) ) bt->streamhosts = g_slist_append( bt->streamhosts, sh ); proxy = next; } } /* * Starts a bytestream. */ gboolean jabber_bs_send_start( struct jabber_transfer *tf ) { struct bs_transfer *bt; sha1_state_t sha; char hash_hex[41]; unsigned char hash[20]; int i,ret; /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */ sha1_init( &sha ); sha1_append( &sha, (unsigned char*) tf->sid, strlen( tf->sid ) ); sha1_append( &sha, (unsigned char*) tf->ini_jid, strlen( tf->ini_jid ) ); sha1_append( &sha, (unsigned char*) tf->tgt_jid, strlen( tf->tgt_jid ) ); sha1_finish( &sha, hash ); for( i = 0; i < 20; i ++ ) sprintf( hash_hex + i * 2, "%02x", hash[i] ); bt = g_new0( struct bs_transfer, 1 ); bt->tf = tf; bt->phase = BS_PHASE_CONNECT; bt->pseudoadr = g_strdup( hash_hex ); tf->streamhandle = bt; tf->ft->free = jabber_bs_free_transfer; tf->ft->canceled = jabber_bs_canceled; jabber_si_set_proxies( bt ); ret = jabber_bs_send_request( tf, bt->streamhosts); return ret; } gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts ) { struct xt_node *shnode, *query, *iq; query = xt_new_node( "query", NULL, NULL ); xt_add_attr( query, "xmlns", XMLNS_BYTESTREAMS ); xt_add_attr( query, "sid", tf->sid ); xt_add_attr( query, "mode", "tcp" ); while( streamhosts ) { jabber_streamhost_t *sh = streamhosts->data; shnode = xt_new_node( "streamhost", NULL, NULL ); xt_add_attr( shnode, "jid", sh->jid ); xt_add_attr( shnode, "host", sh->host ); xt_add_attr( shnode, "port", sh->port ); xt_add_child( query, shnode ); streamhosts = g_slist_next( streamhosts ); } iq = jabber_make_packet( "iq", "set", tf->tgt_jid, query ); xt_add_attr( iq, "from", tf->ini_jid ); jabber_cache_add( tf->ic, iq, jabber_bs_send_handle_reply ); if( !jabber_write_packet( tf->ic, iq ) ) imcb_file_canceled( tf->ic, tf->ft, "Error transmitting bytestream request" ); return TRUE; } gboolean jabber_bs_send_handshake_abort(struct bs_transfer *bt, char *error ) { struct jabber_transfer *tf = bt->tf; struct jabber_data *jd = tf->ic->proto_data; /* TODO: did the receiver get here somehow??? */ imcb_log( tf->ic, "Transferring file %s: SOCKS5 handshake failed: %s", tf->ft->file_name, error ); if( jd->streamhosts==NULL ) /* we're done here unless we have a proxy to try */ imcb_file_canceled( tf->ic, tf->ft, error ); /* MUST always return FALSE! */ return FALSE; } /* * SOCKS5BYTESTREAM protocol for the sender */ gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond ) { struct bs_transfer *bt = data; struct jabber_transfer *tf = bt->tf; short revents; if ( !jabber_bs_poll( bt, fd, &revents ) ) return FALSE; switch( bt->phase ) { case BS_PHASE_CONNECT: { struct sockaddr_storage clt_addr; socklen_t ssize = sizeof( clt_addr ); /* Connect */ ASSERTSOCKOP( tf->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" ); closesocket( fd ); fd = tf->fd; sock_make_nonblocking( fd ); bt->phase = BS_PHASE_CONNECTED; bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_send_handshake, bt ); return FALSE; } case BS_PHASE_CONNECTED: { int ret, have_noauth=FALSE; struct { unsigned char ver; unsigned char method; } socks5_auth_reply = { .ver = 5, .method = 0 }; struct { unsigned char ver; unsigned char nmethods; unsigned char method; } socks5_hello; if( !( ret = jabber_bs_peek( bt, &socks5_hello, sizeof( socks5_hello ) ) ) ) return FALSE; if( ret < sizeof( socks5_hello ) ) return TRUE; if( !( socks5_hello.ver == 5 ) || !( socks5_hello.nmethods >= 1 ) || !( socks5_hello.nmethods < 32 ) ) return jabber_bs_abort( bt, "Invalid auth request ver=%d nmethods=%d method=%d", socks5_hello.ver, socks5_hello.nmethods, socks5_hello.method ); have_noauth = socks5_hello.method == 0; if( socks5_hello.nmethods > 1 ) { char mbuf[32]; int i; ASSERTSOCKOP( ret = recv( fd, mbuf, socks5_hello.nmethods - 1, 0 ) , "Receiving auth methods" ); if( ret < ( socks5_hello.nmethods - 1 ) ) return jabber_bs_abort( bt, "Partial auth request"); for( i = 0 ; !have_noauth && ( i < socks5_hello.nmethods - 1 ) ; i ++ ) if( mbuf[i] == 0 ) have_noauth = TRUE; } if( !have_noauth ) return jabber_bs_abort( bt, "Auth request didn't include no authentication" ); ASSERTSOCKOP( send( fd, &socks5_auth_reply, sizeof( socks5_auth_reply ) , 0 ), "Sending auth reply" ); bt->phase = BS_PHASE_REQUEST; return TRUE; } case BS_PHASE_REQUEST: { struct socks5_message socks5_connect; int msgsize = sizeof( struct socks5_message ); int ret; if( !( ret = jabber_bs_peek( bt, &socks5_connect, msgsize ) ) ) return FALSE; if( ret < msgsize ) return TRUE; if( !( socks5_connect.ver == 5) || !( socks5_connect.cmdrep.cmd == 1 ) || !( socks5_connect.atyp == 3 ) || !(socks5_connect.addrlen == 40 ) ) return jabber_bs_abort( bt, "Invalid SOCKS5 Connect message (addrlen=%d, ver=%d, cmd=%d, atyp=%d)", socks5_connect.addrlen, socks5_connect.ver, socks5_connect.cmdrep.cmd, socks5_connect.atyp ); if( !( memcmp( socks5_connect.address, bt->pseudoadr, 40 ) == 0 ) ) return jabber_bs_abort( bt, "SOCKS5 Connect message contained wrong digest"); socks5_connect.cmdrep.rep = 0; ASSERTSOCKOP( send( fd, &socks5_connect, msgsize, 0 ), "Sending connect reply" ); bt->phase = BS_PHASE_REPLY; imcb_log( tf->ic, "File %s: SOCKS5 handshake successful! Transfer about to start...", tf->ft->file_name ); if( tf->accepted ) { /* streamhost-used message came already in(possible?), let's start sending */ tf->ft->write_request( tf->ft ); } tf->watch_in = 0; return FALSE; } default: /* BUG */ imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" ); bt->tf->watch_in = 0; return FALSE; } } #undef ASSERTSOCKOP bitlbee-3.2.1/protocols/jabber/jabber_util.c0000644000175000017500000005115712245474076020426 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - Misc. stuff * * * * Copyright 2006-2010 Wilmer van der Gaast * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include "jabber.h" #include "md5.h" #include "base64.h" static unsigned int next_id = 1; char *set_eval_priority( set_t *set, char *value ) { account_t *acc = set->data; int i; if( sscanf( value, "%d", &i ) == 1 ) { /* Priority is a signed 8-bit integer, according to RFC 3921. */ if( i < -128 || i > 127 ) return SET_INVALID; } else return SET_INVALID; /* Only run this stuff if the account is online ATM, and if the setting seems to be acceptable. */ if( acc->ic ) { /* Although set_eval functions usually are very nice and convenient, they have one disadvantage: If I would just call p_s_u() now to send the new prio setting, it would send the old setting because the set->value gets changed after the (this) eval returns a non-NULL value. So now I can choose between implementing post-set functions next to evals, or just do this little hack: */ g_free( set->value ); set->value = g_strdup( value ); /* (Yes, sorry, I prefer the hack. :-P) */ presence_send_update( acc->ic ); } return value; } char *set_eval_tls( set_t *set, char *value ) { if( g_strcasecmp( value, "try" ) == 0 ) return value; else return set_eval_bool( set, value ); } struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_node *children ) { struct xt_node *node; node = xt_new_node( name, NULL, children ); if( type ) xt_add_attr( node, "type", type ); if( to ) xt_add_attr( node, "to", to ); /* IQ packets should always have an ID, so let's generate one. It might get overwritten by jabber_cache_add() if this packet has to be saved until we receive a response. Cached packets get slightly different IDs so we can recognize them. */ if( strcmp( name, "iq" ) == 0 ) { char *id = g_strdup_printf( "%s%05x", JABBER_PACKET_ID, ( next_id++ ) & 0xfffff ); xt_add_attr( node, "id", id ); g_free( id ); } return node; } struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type, char *err_code ) { struct xt_node *node, *c; char *to; /* Create the "defined-condition" tag. */ c = xt_new_node( err_cond, NULL, NULL ); xt_add_attr( c, "xmlns", XMLNS_STANZA_ERROR ); /* Put it in an tag. */ c = xt_new_node( "error", NULL, c ); xt_add_attr( c, "type", err_type ); /* Add the error code, if present */ if (err_code) xt_add_attr( c, "code", err_code ); /* To make the actual error packet, we copy the original packet and add our /type="error" tag. Including the original packet is recommended, so let's just do it. */ node = xt_dup( orig ); xt_add_child( node, c ); xt_add_attr( node, "type", "error" ); /* Return to sender. */ if( ( to = xt_find_attr( node, "from" ) ) ) { xt_add_attr( node, "to", to ); xt_remove_attr( node, "from" ); } return node; } /* Cache a node/packet for later use. Mainly useful for IQ packets if you need them when you receive the response. Use this BEFORE sending the packet so it'll get a new id= tag, and do NOT free() the packet after sending it! */ void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func ) { struct jabber_data *jd = ic->proto_data; struct jabber_cache_entry *entry = g_new0( struct jabber_cache_entry, 1 ); md5_state_t id_hash; md5_byte_t id_sum[16]; char *id, *asc_hash; next_id ++; id_hash = jd->cached_id_prefix; md5_append( &id_hash, (md5_byte_t*) &next_id, sizeof( next_id ) ); md5_finish( &id_hash, id_sum ); asc_hash = base64_encode( id_sum, 12 ); id = g_strdup_printf( "%s%s", JABBER_CACHED_ID, asc_hash ); xt_add_attr( node, "id", id ); g_free( id ); g_free( asc_hash ); entry->node = node; entry->func = func; entry->saved_at = time( NULL ); g_hash_table_insert( jd->node_cache, xt_find_attr( node, "id" ), entry ); } void jabber_cache_entry_free( gpointer data ) { struct jabber_cache_entry *entry = data; xt_free_node( entry->node ); g_free( entry ); } gboolean jabber_cache_clean_entry( gpointer key, gpointer entry, gpointer nullpointer ); /* This one should be called from time to time (from keepalive, in this case) to make sure things don't stay in the node cache forever. By marking nodes during the first run and deleting marked nodes during a next run, every node should be available in the cache for at least a minute (assuming the function is indeed called every minute). */ void jabber_cache_clean( struct im_connection *ic ) { struct jabber_data *jd = ic->proto_data; time_t threshold = time( NULL ) - JABBER_CACHE_MAX_AGE; g_hash_table_foreach_remove( jd->node_cache, jabber_cache_clean_entry, &threshold ); } gboolean jabber_cache_clean_entry( gpointer key, gpointer entry_, gpointer threshold_ ) { struct jabber_cache_entry *entry = entry_; time_t *threshold = threshold_; return entry->saved_at < *threshold; } xt_status jabber_cache_handle_packet( struct im_connection *ic, struct xt_node *node ) { struct jabber_data *jd = ic->proto_data; struct jabber_cache_entry *entry; char *s; if( ( s = xt_find_attr( node, "id" ) ) == NULL || strncmp( s, JABBER_CACHED_ID, strlen( JABBER_CACHED_ID ) ) != 0 ) { /* Silently ignore it, without an ID (or a non-cache ID) we don't know how to handle the packet and we probably don't have to. */ return XT_HANDLED; } entry = g_hash_table_lookup( jd->node_cache, s ); if( entry == NULL ) { /* There's no longer an easy way to see if we generated this one or someone else, and there's a ten-minute timeout anyway, so meh. imcb_log( ic, "Warning: Received %s-%s packet with unknown/expired ID %s!", node->name, xt_find_attr( node, "type" ) ? : "(no type)", s ); */ } else if( entry->func ) { return entry->func( ic, node, entry->node ); } return XT_HANDLED; } const struct jabber_away_state jabber_away_state_list[] = { { "away", "Away" }, { "chat", "Free for Chat" }, /* WTF actually uses this? */ { "dnd", "Do not Disturb" }, { "xa", "Extended Away" }, { "", NULL } }; const struct jabber_away_state *jabber_away_state_by_code( char *code ) { int i; if( code == NULL ) return NULL; for( i = 0; jabber_away_state_list[i].full_name; i ++ ) if( g_strcasecmp( jabber_away_state_list[i].code, code ) == 0 ) return jabber_away_state_list + i; return NULL; } const struct jabber_away_state *jabber_away_state_by_name( char *name ) { int i; if( name == NULL ) return NULL; for( i = 0; jabber_away_state_list[i].full_name; i ++ ) if( g_strcasecmp( jabber_away_state_list[i].full_name, name ) == 0 ) return jabber_away_state_list + i; return NULL; } struct jabber_buddy_ask_data { struct im_connection *ic; char *handle; char *realname; }; static void jabber_buddy_ask_yes( void *data ) { struct jabber_buddy_ask_data *bla = data; presence_send_request( bla->ic, bla->handle, "subscribed" ); imcb_ask_add( bla->ic, bla->handle, NULL ); g_free( bla->handle ); g_free( bla ); } static void jabber_buddy_ask_no( void *data ) { struct jabber_buddy_ask_data *bla = data; presence_send_request( bla->ic, bla->handle, "subscribed" ); g_free( bla->handle ); g_free( bla ); } void jabber_buddy_ask( struct im_connection *ic, char *handle ) { struct jabber_buddy_ask_data *bla = g_new0( struct jabber_buddy_ask_data, 1 ); char *buf; bla->ic = ic; bla->handle = g_strdup( handle ); buf = g_strdup_printf( "The user %s wants to add you to his/her buddy list.", handle ); imcb_ask( ic, buf, bla, jabber_buddy_ask_yes, jabber_buddy_ask_no ); g_free( buf ); } /* Returns a new string. Don't leak it! */ char *jabber_normalize( const char *orig ) { int len, i; char *new; len = strlen( orig ); new = g_new( char, len + 1 ); /* So it turns out the /resource part is case sensitive. Yeah, and it's Unicode but feck Unicode. :-P So stop once we see a slash. */ for( i = 0; i < len && orig[i] != '/' ; i ++ ) new[i] = tolower( orig[i] ); for( ; orig[i]; i ++ ) new[i] = orig[i]; new[i] = 0; return new; } /* Adds a buddy/resource to our list. Returns NULL if full_jid is not really a FULL jid or if we already have this buddy/resource. XXX: No, great, actually buddies from transports don't (usually) have resources. So we'll really have to deal with that properly. Set their ->resource property to NULL. Do *NOT* allow to mix this stuff, though... */ struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_ ) { struct jabber_data *jd = ic->proto_data; struct jabber_buddy *bud, *new, *bi; char *s, *full_jid; full_jid = jabber_normalize( full_jid_ ); if( ( s = strchr( full_jid, '/' ) ) ) *s = 0; new = g_new0( struct jabber_buddy, 1 ); if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) ) { /* The first entry is always a bare JID. If there are more, we should ignore the first one here. */ if( bud->next ) bud = bud->next; /* If this is a transport buddy or whatever, it can't have more than one instance, so this is always wrong: */ if( s == NULL || bud->resource == NULL ) { if( s ) *s = '/'; g_free( new ); g_free( full_jid ); return NULL; } new->bare_jid = bud->bare_jid; /* We already have another resource for this buddy, add the new one to the list. */ for( bi = bud; bi; bi = bi->next ) { /* Check for dupes. */ if( strcmp( bi->resource, s + 1 ) == 0 ) { *s = '/'; g_free( new ); g_free( full_jid ); return NULL; } /* Append the new item to the list. */ else if( bi->next == NULL ) { bi->next = new; break; } } } else { new->full_jid = new->bare_jid = g_strdup( full_jid ); g_hash_table_insert( jd->buddies, new->bare_jid, new ); if( s ) { new->next = g_new0( struct jabber_buddy, 1 ); new->next->bare_jid = new->bare_jid; new = new->next; } } if( s ) { *s = '/'; new->full_jid = full_jid; new->resource = strchr( new->full_jid, '/' ) + 1; } else { /* Let's waste some more bytes of RAM instead of to make memory management a total disaster here. And it saves me one g_free() call in this function. :-P */ new->full_jid = full_jid; } return new; } /* Finds a buddy from our structures. Can find both full- and bare JIDs. When asked for a bare JID, it uses the "resource_select" setting to see which resource to pick. */ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags ) { struct jabber_data *jd = ic->proto_data; struct jabber_buddy *bud, *head; char *s, *jid; jid = jabber_normalize( jid_ ); if( ( s = strchr( jid, '/' ) ) ) { int bare_exists = 0; *s = 0; if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) ) { bare_exists = 1; if( bud->next ) bud = bud->next; /* Just return the first one for this bare JID. */ if( flags & GET_BUDDY_FIRST ) { *s = '/'; g_free( jid ); return bud; } /* Is this one of those no-resource buddies? */ if( bud->resource == NULL ) { *s = '/'; g_free( jid ); return NULL; } /* See if there's an exact match. */ for( ; bud; bud = bud->next ) if( strcmp( bud->resource, s + 1 ) == 0 ) break; } if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && ( bare_exists || bee_user_by_handle( ic->bee, ic, jid ) ) ) { *s = '/'; bud = jabber_buddy_add( ic, jid ); } g_free( jid ); return bud; } else { struct jabber_buddy *best_prio, *best_time; char *set; head = g_hash_table_lookup( jd->buddies, jid ); bud = ( head && head->next ) ? head->next : head; g_free( jid ); if( bud == NULL ) /* No match. Create it now? */ return ( ( flags & GET_BUDDY_CREAT ) && bee_user_by_handle( ic->bee, ic, jid_ ) ) ? jabber_buddy_add( ic, jid_ ) : NULL; else if( bud->resource && ( flags & GET_BUDDY_EXACT ) ) /* We want an exact match, so in thise case there shouldn't be a /resource. */ return NULL; else if( bud->resource == NULL || bud->next == NULL ) /* No need for selection if there's only one option. */ return bud; else if( flags & GET_BUDDY_FIRST ) /* Looks like the caller doesn't care about details. */ return bud; else if( flags & GET_BUDDY_BARE ) return head; best_prio = best_time = bud; for( ; bud; bud = bud->next ) { if( bud->priority > best_prio->priority ) best_prio = bud; if( bud->last_msg > best_time->last_msg ) best_time = bud; } if( ( set = set_getstr( &ic->acc->set, "resource_select" ) ) == NULL ) return NULL; else if( strcmp( set, "priority" ) == 0 ) return best_prio; else if( flags & GET_BUDDY_BARE_OK ) /* && strcmp( set, "activity" ) == 0 */ { if( best_time->last_msg + set_getint( &ic->acc->set, "activity_timeout" ) >= time( NULL ) ) return best_time; else return head; } else return best_time; } } /* I'm keeping a separate ext_jid attribute to save a JID that makes sense to export to BitlBee. This is mainly for groupchats right now. It's a bit of a hack, but I just think having the user nickname in the hostname part of the hostmask doesn't look nice on IRC. Normally you can convert a normal JID to ext_jid by swapping the part before and after the / and replacing the / with a =. But there should be some stripping (@s are allowed in Jabber nicks...). */ struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags ) { struct jabber_buddy *bud; char *s, *jid; jid = jabber_normalize( jid_ ); if( ( s = strchr( jid, '=' ) ) == NULL ) return NULL; for( bud = jabber_buddy_by_jid( ic, s + 1, GET_BUDDY_FIRST ); bud; bud = bud->next ) { /* Hmmm, could happen if not all people in the chat are anonymized? */ if( bud->ext_jid == NULL ) continue; if( strcmp( bud->ext_jid, jid ) == 0 ) break; } g_free( jid ); return bud; } /* Remove one specific full JID from our list. Use this when a buddy goes off-line (because (s)he can still be online from a different location. XXX: See above, we should accept bare JIDs too... */ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ ) { struct jabber_data *jd = ic->proto_data; struct jabber_buddy *bud, *prev = NULL, *bi; char *s, *full_jid; full_jid = jabber_normalize( full_jid_ ); if( ( s = strchr( full_jid, '/' ) ) ) *s = 0; if( ( bud = g_hash_table_lookup( jd->buddies, full_jid ) ) ) { if( bud->next ) bud = (prev=bud)->next; /* If there's only one item in the list (and if the resource matches), removing it is simple. (And the hash reference should be removed too!) */ if( bud->next == NULL && ( ( s == NULL && bud->resource == NULL ) || ( bud->resource && s && strcmp( bud->resource, s + 1 ) == 0 ) ) ) { int st = jabber_buddy_remove_bare( ic, full_jid ); g_free( full_jid ); return st; } else if( s == NULL || bud->resource == NULL ) { /* Tried to remove a bare JID while this JID does seem to have resources... (Or the opposite.) *sigh* */ g_free( full_jid ); return 0; } else { for( bi = bud; bi; bi = (prev=bi)->next ) if( strcmp( bi->resource, s + 1 ) == 0 ) break; g_free( full_jid ); if( bi ) { if( prev ) prev->next = bi->next; else /* Don't think this should ever happen anymore. */ g_hash_table_replace( jd->buddies, bi->bare_jid, bi->next ); g_free( bi->ext_jid ); g_free( bi->full_jid ); g_free( bi->away_message ); g_free( bi ); return 1; } else { return 0; } } } else { g_free( full_jid ); return 0; } } /* Remove a buddy completely; removes all resources that belong to the specified bare JID. Use this when removing someone from the contact list, for example. */ int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid ) { struct jabber_data *jd = ic->proto_data; struct jabber_buddy *bud, *next; if( strchr( bare_jid, '/' ) ) return 0; if( ( bud = jabber_buddy_by_jid( ic, bare_jid, GET_BUDDY_FIRST ) ) ) { /* Most important: Remove the hash reference. We don't know this buddy anymore. */ g_hash_table_remove( jd->buddies, bud->bare_jid ); g_free( bud->bare_jid ); /* Deallocate the linked list of resources. */ while( bud ) { /* ext_jid && anonymous means that this buddy is specific to one groupchat (the one we're currently cleaning up) so it can be deleted completely. */ if( bud->ext_jid && bud->flags & JBFLAG_IS_ANONYMOUS ) imcb_remove_buddy( ic, bud->ext_jid, NULL ); next = bud->next; g_free( bud->ext_jid ); g_free( bud->full_jid ); g_free( bud->away_message ); g_free( bud ); bud = next; } return 1; } else { return 0; } } static gboolean jabber_buddy_remove_all_cb( gpointer key, gpointer value, gpointer data ) { struct jabber_buddy *bud, *next; bud = value; if( bud->bare_jid != bud->full_jid ) g_free( bud->bare_jid ); while( bud ) { next = bud->next; g_free( bud->ext_jid ); g_free( bud->full_jid ); g_free( bud->away_message ); g_free( bud ); bud = next; } return TRUE; } void jabber_buddy_remove_all( struct im_connection *ic ) { struct jabber_data *jd = ic->proto_data; g_hash_table_foreach_remove( jd->buddies, jabber_buddy_remove_all_cb, NULL ); g_hash_table_destroy( jd->buddies ); } time_t jabber_get_timestamp( struct xt_node *xt ) { struct xt_node *c; char *s = NULL; struct tm tp; for( c = xt->children; ( c = xt_find_node( c, "x" ) ); c = c->next ) { if( ( s = xt_find_attr( c, "xmlns" ) ) && strcmp( s, XMLNS_DELAY ) == 0 ) break; } if( !c || !( s = xt_find_attr( c, "stamp" ) ) ) return 0; memset( &tp, 0, sizeof( tp ) ); if( sscanf( s, "%4d%2d%2dT%2d:%2d:%2d", &tp.tm_year, &tp.tm_mon, &tp.tm_mday, &tp.tm_hour, &tp.tm_min, &tp.tm_sec ) != 6 ) return 0; tp.tm_year -= 1900; tp.tm_mon --; return mktime_utc( &tp ); } struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns ) { struct jabber_error *err; struct xt_node *c; char *s; if( node == NULL ) return NULL; err = g_new0( struct jabber_error, 1 ); err->type = xt_find_attr( node, "type" ); for( c = node->children; c; c = c->next ) { if( !( s = xt_find_attr( c, "xmlns" ) ) || strcmp( s, xmlns ) != 0 ) continue; if( strcmp( c->name, "text" ) != 0 ) { err->code = c->name; } /* Only use the text if it doesn't have an xml:lang attribute, if it's empty or if it's set to something English. */ else if( !( s = xt_find_attr( c, "xml:lang" ) ) || !*s || strncmp( s, "en", 2 ) == 0 ) { err->text = c->text; } } return err; } void jabber_error_free( struct jabber_error *err ) { g_free( err ); } gboolean jabber_set_me( struct im_connection *ic, const char *me ) { struct jabber_data *jd = ic->proto_data; if( strchr( me, '@' ) == NULL ) return FALSE; g_free( jd->username ); g_free( jd->me ); jd->me = jabber_normalize( me ); jd->server = strchr( jd->me, '@' ); jd->username = g_strndup( jd->me, jd->server - jd->me ); jd->server ++; return TRUE; } bitlbee-3.2.1/protocols/jabber/message.c0000644000175000017500000001434112245474076017562 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - Handling of message(s) (tags), etc * * * * Copyright 2006-2012 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include "jabber.h" xt_status jabber_pkt_message( struct xt_node *node, gpointer data ) { struct im_connection *ic = data; char *from = xt_find_attr( node, "from" ); char *type = xt_find_attr( node, "type" ); char *id = xt_find_attr( node, "id" ); struct xt_node *body = xt_find_node( node->children, "body" ), *c; struct xt_node *request = xt_find_node( node->children, "request" ); struct jabber_buddy *bud = NULL; char *s, *room = NULL, *reason = NULL; if( !from ) return XT_HANDLED; /* Consider this packet corrupted. */ if( request && id ) { /* Send a message receipt (XEP-0184), looking like this: * * * */ struct xt_node *received, *receipt; received = xt_new_node( "received", NULL, NULL ); xt_add_attr( received, "xmlns", XMLNS_RECEIPTS ); xt_add_attr( received, "id", id ); receipt = jabber_make_packet( "message", NULL, from, received ); jabber_write_packet( ic, receipt ); xt_free_node( receipt ); } bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT ); if( type && strcmp( type, "error" ) == 0 ) { /* Handle type=error packet. */ } else if( type && from && strcmp( type, "groupchat" ) == 0 ) { jabber_chat_pkt_message( ic, bud, node ); } else /* "chat", "normal", "headline", no-type or whatever. Should all be pretty similar. */ { GString *fullmsg = g_string_new( "" ); for( c = node->children; ( c = xt_find_node( c, "x" ) ); c = c->next ) { char *ns = xt_find_attr( c, "xmlns" ); struct xt_node *inv; if( ns && strcmp( ns, XMLNS_MUC_USER ) == 0 && ( inv = xt_find_node( c->children, "invite" ) ) ) { /* This is an invitation. Set some vars which will be passed to imcb_chat_invite() below. */ room = from; if( ( from = xt_find_attr( inv, "from" ) ) == NULL ) from = room; if( ( inv = xt_find_node( inv->children, "reason" ) ) && inv->text_len > 0 ) reason = inv->text; } } if( ( s = strchr( from, '/' ) ) ) { if( bud ) { bud->last_msg = time( NULL ); from = bud->ext_jid ? bud->ext_jid : bud->bare_jid; } else *s = 0; /* We need to generate a bare JID now. */ } if( type && strcmp( type, "headline" ) == 0 ) { if( ( c = xt_find_node( node->children, "subject" ) ) && c->text_len > 0 ) g_string_append_printf( fullmsg, "Headline: %s\n", c->text ); /* http://.... can contain a URL, it seems. */ for( c = node->children; c; c = c->next ) { struct xt_node *url; if( ( url = xt_find_node( c->children, "url" ) ) && url->text_len > 0 ) g_string_append_printf( fullmsg, "URL: %s\n", url->text ); } } else if( ( c = xt_find_node( node->children, "subject" ) ) && c->text_len > 0 && ( !bud || !( bud->flags & JBFLAG_HIDE_SUBJECT ) ) ) { g_string_append_printf( fullmsg, "<< \002BitlBee\002 - Message with subject: %s >>\n", c->text ); if( bud ) bud->flags |= JBFLAG_HIDE_SUBJECT; } else if( bud && !c ) { /* Yeah, possibly we're hiding changes to this field now. But nobody uses this for anything useful anyway, except GMail when people reply to an e-mail via chat, repeating the same subject all the time. I don't want to have to remember full subject strings for everyone. */ bud->flags &= ~JBFLAG_HIDE_SUBJECT; } if( body && body->text_len > 0 ) /* Could be just a typing notification. */ fullmsg = g_string_append( fullmsg, body->text ); if( fullmsg->len > 0 ) imcb_buddy_msg( ic, from, fullmsg->str, 0, jabber_get_timestamp( node ) ); if( room ) imcb_chat_invite( ic, room, from, reason ); g_string_free( fullmsg, TRUE ); /* Handling of incoming typing notifications. */ if( bud == NULL ) { /* Can't handle these for unknown buddies. */ } else if( xt_find_node( node->children, "composing" ) ) { bud->flags |= JBFLAG_DOES_XEP85; imcb_buddy_typing( ic, from, OPT_TYPING ); } /* No need to send a "stopped typing" signal when there's a message. */ else if( xt_find_node( node->children, "active" ) && ( body == NULL ) ) { bud->flags |= JBFLAG_DOES_XEP85; imcb_buddy_typing( ic, from, 0 ); } else if( xt_find_node( node->children, "paused" ) ) { bud->flags |= JBFLAG_DOES_XEP85; imcb_buddy_typing( ic, from, OPT_THINKING ); } if( s ) *s = '/'; /* And convert it back to a full JID. */ } return XT_HANDLED; } bitlbee-3.2.1/protocols/jabber/iq.c0000644000175000017500000006771712245474076016566 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - IQ packets * * * * Copyright 2006-2012 Wilmer van der Gaast * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include "jabber.h" #include "sha1.h" static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); static xt_status jabber_iq_display_vcard( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); static int jabber_iq_disco_server( struct im_connection *ic ); xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) { struct im_connection *ic = data; struct jabber_data *jd = ic->proto_data; struct xt_node *c, *reply = NULL; char *type, *s; int st, pack = 1; type = xt_find_attr( node, "type" ); if( !type ) { imcb_error( ic, "Received IQ packet without type." ); imc_logout( ic, TRUE ); return XT_ABORT; } if( strcmp( type, "result" ) == 0 || strcmp( type, "error" ) == 0 ) { return jabber_cache_handle_packet( ic, node ); } else if( strcmp( type, "get" ) == 0 ) { if( !( ( c = xt_find_node( node->children, "query" ) ) || ( c = xt_find_node( node->children, "ping" ) ) || ( c = xt_find_node( node->children, "time" ) ) ) || !( s = xt_find_attr( c, "xmlns" ) ) ) { /* Sigh. Who decided to suddenly invent new elements instead of just sticking with ? */ return XT_HANDLED; } reply = xt_new_node( "query", NULL, NULL ); xt_add_attr( reply, "xmlns", s ); /* Of course this is a very essential query to support. ;-) */ if( strcmp( s, XMLNS_VERSION ) == 0 ) { xt_add_child( reply, xt_new_node( "name", set_getstr( &ic->acc->set, "user_agent" ), NULL ) ); xt_add_child( reply, xt_new_node( "version", BITLBEE_VERSION, NULL ) ); xt_add_child( reply, xt_new_node( "os", ARCH, NULL ) ); } else if( strcmp( s, XMLNS_TIME_OLD ) == 0 ) { time_t time_ep; char buf[1024]; buf[sizeof(buf)-1] = 0; time_ep = time( NULL ); strftime( buf, sizeof( buf ) - 1, "%Y%m%dT%H:%M:%S", gmtime( &time_ep ) ); xt_add_child( reply, xt_new_node( "utc", buf, NULL ) ); strftime( buf, sizeof( buf ) - 1, "%Z", localtime( &time_ep ) ); xt_add_child( reply, xt_new_node( "tz", buf, NULL ) ); } else if( strcmp( s, XMLNS_TIME ) == 0 ) { time_t time_ep; char buf[1024]; buf[sizeof(buf)-1] = 0; time_ep = time( NULL ); xt_free_node( reply ); reply = xt_new_node( "time", NULL, NULL ); xt_add_attr( reply, "xmlns", XMLNS_TIME ); strftime( buf, sizeof( buf ) - 1, "%Y%m%dT%H:%M:%SZ", gmtime( &time_ep ) ); xt_add_child( reply, xt_new_node( "utc", buf, NULL ) ); strftime( buf, sizeof( buf ) - 1, "%z", localtime( &time_ep ) ); if( strlen( buf ) >= 5 ) { buf[6] = '\0'; buf[5] = buf[4]; buf[4] = buf[3]; buf[3] = ':'; } xt_add_child( reply, xt_new_node( "tzo", buf, NULL ) ); } else if( strcmp( s, XMLNS_PING ) == 0 ) { xt_free_node( reply ); reply = jabber_make_packet( "iq", "result", xt_find_attr( node, "from" ), NULL ); if( ( s = xt_find_attr( node, "id" ) ) ) xt_add_attr( reply, "id", s ); pack = 0; } else if( strcmp( s, XMLNS_DISCO_INFO ) == 0 ) { const char *features[] = { XMLNS_DISCO_INFO, XMLNS_VERSION, XMLNS_TIME_OLD, XMLNS_TIME, XMLNS_CHATSTATES, XMLNS_MUC, XMLNS_PING, XMLNS_RECEIPTS, XMLNS_SI, XMLNS_BYTESTREAMS, XMLNS_FILETRANSFER, NULL }; const char **f; c = xt_new_node( "identity", NULL, NULL ); xt_add_attr( c, "category", "client" ); xt_add_attr( c, "type", "pc" ); xt_add_attr( c, "name", set_getstr( &ic->acc->set, "user_agent" ) ); xt_add_child( reply, c ); for( f = features; *f; f ++ ) { c = xt_new_node( "feature", NULL, NULL ); xt_add_attr( c, "var", *f ); xt_add_child( reply, c ); } } else { xt_free_node( reply ); reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); pack = 0; } } else if( strcmp( type, "set" ) == 0 ) { if( ( c = xt_find_node( node->children, "si" ) ) && ( s = xt_find_attr( c, "xmlns" ) ) && ( strcmp( s, XMLNS_SI ) == 0 ) ) { return jabber_si_handle_request( ic, node, c ); } else if( !( c = xt_find_node( node->children, "query" ) ) || !( s = xt_find_attr( c, "xmlns" ) ) ) { return XT_HANDLED; } else if( strcmp( s, XMLNS_ROSTER ) == 0 ) { /* This is a roster push. XMPP servers send this when someone was added to (or removed from) the buddy list. AFAIK they're sent even if we added this buddy in our own session. */ int bare_len = strlen( jd->me ); if( ( s = xt_find_attr( node, "from" ) ) == NULL || ( strncmp( s, jd->me, bare_len ) == 0 && ( s[bare_len] == 0 || s[bare_len] == '/' ) ) ) { jabber_parse_roster( ic, node, NULL ); /* Should we generate a reply here? Don't think it's very important... */ } else { imcb_log( ic, "Warning: %s tried to fake a roster push!", s ? s : "(unknown)" ); xt_free_node( reply ); reply = jabber_make_error_packet( node, "not-allowed", "cancel", NULL ); pack = 0; } } else if( strcmp( s, XMLNS_BYTESTREAMS ) == 0 ) { /* Bytestream Request (stage 2 of file transfer) */ return jabber_bs_recv_request( ic, node, c ); } else { xt_free_node( reply ); reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); pack = 0; } } /* If we recognized the xmlns and managed to generate a reply, finish and send it. */ if( reply ) { /* Normally we still have to pack it into an iq-result packet, but for errors, for example, we don't. */ if( pack ) { reply = jabber_make_packet( "iq", "result", xt_find_attr( node, "from" ), reply ); if( ( s = xt_find_attr( node, "id" ) ) ) xt_add_attr( reply, "id", s ); } st = jabber_write_packet( ic, reply ); xt_free_node( reply ); if( !st ) return XT_ABORT; } return XT_HANDLED; } static xt_status jabber_do_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); static xt_status jabber_finish_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); int jabber_init_iq_auth( struct im_connection *ic ) { struct jabber_data *jd = ic->proto_data; struct xt_node *node; int st; node = xt_new_node( "query", NULL, xt_new_node( "username", jd->username, NULL ) ); xt_add_attr( node, "xmlns", XMLNS_AUTH ); node = jabber_make_packet( "iq", "get", NULL, node ); jabber_cache_add( ic, node, jabber_do_iq_auth ); st = jabber_write_packet( ic, node ); return st; } static xt_status jabber_do_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct jabber_data *jd = ic->proto_data; struct xt_node *reply, *query; xt_status st; char *s; if( !( query = xt_find_node( node->children, "query" ) ) ) { imcb_log( ic, "Warning: Received incomplete IQ packet while authenticating" ); imc_logout( ic, FALSE ); return XT_HANDLED; } /* Time to authenticate ourselves! */ reply = xt_new_node( "query", NULL, NULL ); xt_add_attr( reply, "xmlns", XMLNS_AUTH ); xt_add_child( reply, xt_new_node( "username", jd->username, NULL ) ); xt_add_child( reply, xt_new_node( "resource", set_getstr( &ic->acc->set, "resource" ), NULL ) ); if( xt_find_node( query->children, "digest" ) && ( s = xt_find_attr( jd->xt->root, "id" ) ) ) { /* We can do digest authentication, it seems, and of course we prefer that. */ sha1_state_t sha; char hash_hex[41]; unsigned char hash[20]; int i; sha1_init( &sha ); sha1_append( &sha, (unsigned char*) s, strlen( s ) ); sha1_append( &sha, (unsigned char*) ic->acc->pass, strlen( ic->acc->pass ) ); sha1_finish( &sha, hash ); for( i = 0; i < 20; i ++ ) sprintf( hash_hex + i * 2, "%02x", hash[i] ); xt_add_child( reply, xt_new_node( "digest", hash_hex, NULL ) ); } else if( xt_find_node( query->children, "password" ) ) { /* We'll have to stick with plaintext. Let's hope we're using SSL/TLS... */ xt_add_child( reply, xt_new_node( "password", ic->acc->pass, NULL ) ); } else { xt_free_node( reply ); imcb_error( ic, "Can't find suitable authentication method" ); imc_logout( ic, FALSE ); return XT_ABORT; } reply = jabber_make_packet( "iq", "set", NULL, reply ); jabber_cache_add( ic, reply, jabber_finish_iq_auth ); st = jabber_write_packet( ic, reply ); return st ? XT_HANDLED : XT_ABORT; } static xt_status jabber_finish_iq_auth( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct jabber_data *jd = ic->proto_data; char *type; if( !( type = xt_find_attr( node, "type" ) ) ) { imcb_log( ic, "Warning: Received incomplete IQ packet while authenticating" ); imc_logout( ic, FALSE ); return XT_HANDLED; } if( strcmp( type, "error" ) == 0 ) { imcb_error( ic, "Authentication failure" ); imc_logout( ic, FALSE ); return XT_ABORT; } else if( strcmp( type, "result" ) == 0 ) { /* This happens when we just successfully authenticated the old (non-SASL) way. */ jd->flags |= JFLAG_AUTHENTICATED; if( !jabber_get_roster( ic ) ) return XT_ABORT; if( !jabber_iq_disco_server( ic ) ) return XT_ABORT; } return XT_HANDLED; } xt_status jabber_pkt_bind_sess( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct jabber_data *jd = ic->proto_data; struct xt_node *c, *reply = NULL; char *s; if( node && ( c = xt_find_node( node->children, "bind" ) ) ) { c = xt_find_node( c->children, "jid" ); if( !c || !c->text ) { /* Server is crap, but this is no disaster. */ } else if( strncmp( jd->me, c->text, strlen( jd->me ) ) != 0 ) { s = strchr( c->text, '/' ); if( s ) *s = '\0'; jabber_set_me( ic, c->text ); imcb_log( ic, "Server claims your JID is `%s' instead of `%s'. " "This mismatch may cause problems with groupchats " "and possibly other things.", c->text, ic->acc->user ); if( s ) *s = '/'; } else if( c && c->text_len && ( s = strchr( c->text, '/' ) ) && strcmp( s + 1, set_getstr( &ic->acc->set, "resource" ) ) != 0 ) imcb_log( ic, "Server changed session resource string to `%s'", s + 1 ); } if( jd->flags & JFLAG_WANT_BIND ) { reply = xt_new_node( "bind", NULL, xt_new_node( "resource", set_getstr( &ic->acc->set, "resource" ), NULL ) ); xt_add_attr( reply, "xmlns", XMLNS_BIND ); jd->flags &= ~JFLAG_WANT_BIND; } else if( jd->flags & JFLAG_WANT_SESSION ) { reply = xt_new_node( "session", NULL, NULL ); xt_add_attr( reply, "xmlns", XMLNS_SESSION ); jd->flags &= ~JFLAG_WANT_SESSION; } if( reply != NULL ) { reply = jabber_make_packet( "iq", "set", NULL, reply ); jabber_cache_add( ic, reply, jabber_pkt_bind_sess ); if( !jabber_write_packet( ic, reply ) ) return XT_ABORT; } else if( ( jd->flags & ( JFLAG_WANT_BIND | JFLAG_WANT_SESSION ) ) == 0 ) { if( !jabber_get_roster( ic ) ) return XT_ABORT; if( !jabber_iq_disco_server( ic ) ) return XT_ABORT; } return XT_HANDLED; } int jabber_get_roster( struct im_connection *ic ) { struct xt_node *node; int st; imcb_log( ic, "Authenticated, requesting buddy list" ); node = xt_new_node( "query", NULL, NULL ); xt_add_attr( node, "xmlns", XMLNS_ROSTER ); node = jabber_make_packet( "iq", "get", NULL, node ); jabber_cache_add( ic, node, jabber_parse_roster ); st = jabber_write_packet( ic, node ); return st; } static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct xt_node *query, *c; int initial = ( orig != NULL ); if( !( query = xt_find_node( node->children, "query" ) ) ) { imcb_log( ic, "Warning: Received NULL roster packet" ); return XT_HANDLED; } c = query->children; while( ( c = xt_find_node( c, "item" ) ) ) { struct xt_node *group = xt_find_node( c->children, "group" ); char *jid = xt_find_attr( c, "jid" ); char *name = xt_find_attr( c, "name" ); char *sub = xt_find_attr( c, "subscription" ); if( jid && sub ) { if( ( strcmp( sub, "both" ) == 0 || strcmp( sub, "to" ) == 0 ) ) { imcb_add_buddy( ic, jid, ( group && group->text_len ) ? group->text : NULL ); if( name ) imcb_rename_buddy( ic, jid, name ); } else if( strcmp( sub, "remove" ) == 0 ) { jabber_buddy_remove_bare( ic, jid ); imcb_remove_buddy( ic, jid, NULL ); } } c = c->next; } if( initial ) imcb_connected( ic ); return XT_HANDLED; } int jabber_get_vcard( struct im_connection *ic, char *bare_jid ) { struct xt_node *node; if( strchr( bare_jid, '/' ) ) return 1; /* This was an error, but return 0 should only be done if the connection died... */ node = xt_new_node( "vCard", NULL, NULL ); xt_add_attr( node, "xmlns", XMLNS_VCARD ); node = jabber_make_packet( "iq", "get", bare_jid, node ); jabber_cache_add( ic, node, jabber_iq_display_vcard ); return jabber_write_packet( ic, node ); } static xt_status jabber_iq_display_vcard( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct xt_node *vc, *c, *sc; /* subchild, ic is already in use ;-) */ GString *reply; char *s; if( ( s = xt_find_attr( node, "type" ) ) == NULL || strcmp( s, "result" ) != 0 || ( vc = xt_find_node( node->children, "vCard" ) ) == NULL ) { s = xt_find_attr( orig, "to" ); /* If this returns NULL something's wrong.. */ imcb_log( ic, "Could not retrieve vCard of %s", s ? s : "(NULL)" ); return XT_HANDLED; } s = xt_find_attr( orig, "to" ); reply = g_string_new( "vCard information for " ); reply = g_string_append( reply, s ? s : "(NULL)" ); reply = g_string_append( reply, ":\n" ); /* I hate this format, I really do... */ if( ( c = xt_find_node( vc->children, "FN" ) ) && c->text_len ) g_string_append_printf( reply, "Name: %s\n", c->text ); if( ( c = xt_find_node( vc->children, "N" ) ) && c->children ) { reply = g_string_append( reply, "Full name:" ); if( ( sc = xt_find_node( c->children, "PREFIX" ) ) && sc->text_len ) g_string_append_printf( reply, " %s", sc->text ); if( ( sc = xt_find_node( c->children, "GIVEN" ) ) && sc->text_len ) g_string_append_printf( reply, " %s", sc->text ); if( ( sc = xt_find_node( c->children, "MIDDLE" ) ) && sc->text_len ) g_string_append_printf( reply, " %s", sc->text ); if( ( sc = xt_find_node( c->children, "FAMILY" ) ) && sc->text_len ) g_string_append_printf( reply, " %s", sc->text ); if( ( sc = xt_find_node( c->children, "SUFFIX" ) ) && sc->text_len ) g_string_append_printf( reply, " %s", sc->text ); reply = g_string_append_c( reply, '\n' ); } if( ( c = xt_find_node( vc->children, "NICKNAME" ) ) && c->text_len ) g_string_append_printf( reply, "Nickname: %s\n", c->text ); if( ( c = xt_find_node( vc->children, "BDAY" ) ) && c->text_len ) g_string_append_printf( reply, "Date of birth: %s\n", c->text ); /* Slightly alternative use of for... ;-) */ for( c = vc->children; ( c = xt_find_node( c, "EMAIL" ) ); c = c->next ) { if( ( sc = xt_find_node( c->children, "USERID" ) ) == NULL || sc->text_len == 0 ) continue; if( xt_find_node( c->children, "HOME" ) ) s = "Home"; else if( xt_find_node( c->children, "WORK" ) ) s = "Work"; else s = "Misc."; g_string_append_printf( reply, "%s e-mail address: %s\n", s, sc->text ); } if( ( c = xt_find_node( vc->children, "URL" ) ) && c->text_len ) g_string_append_printf( reply, "Homepage: %s\n", c->text ); /* Slightly alternative use of for... ;-) */ for( c = vc->children; ( c = xt_find_node( c, "ADR" ) ); c = c->next ) { if( xt_find_node( c->children, "HOME" ) ) s = "Home"; else if( xt_find_node( c->children, "WORK" ) ) s = "Work"; else s = "Misc."; g_string_append_printf( reply, "%s address: ", s ); if( ( sc = xt_find_node( c->children, "STREET" ) ) && sc->text_len ) g_string_append_printf( reply, "%s ", sc->text ); if( ( sc = xt_find_node( c->children, "EXTADR" ) ) && sc->text_len ) g_string_append_printf( reply, "%s, ", sc->text ); if( ( sc = xt_find_node( c->children, "PCODE" ) ) && sc->text_len ) g_string_append_printf( reply, "%s, ", sc->text ); if( ( sc = xt_find_node( c->children, "LOCALITY" ) ) && sc->text_len ) g_string_append_printf( reply, "%s, ", sc->text ); if( ( sc = xt_find_node( c->children, "REGION" ) ) && sc->text_len ) g_string_append_printf( reply, "%s, ", sc->text ); if( ( sc = xt_find_node( c->children, "CTRY" ) ) && sc->text_len ) g_string_append_printf( reply, "%s", sc->text ); if( reply->str[reply->len-2] == ',' ) reply = g_string_truncate( reply, reply->len-2 ); reply = g_string_append_c( reply, '\n' ); } for( c = vc->children; ( c = xt_find_node( c, "TEL" ) ); c = c->next ) { if( ( sc = xt_find_node( c->children, "NUMBER" ) ) == NULL || sc->text_len == 0 ) continue; if( xt_find_node( c->children, "HOME" ) ) s = "Home"; else if( xt_find_node( c->children, "WORK" ) ) s = "Work"; else s = "Misc."; g_string_append_printf( reply, "%s phone number: %s\n", s, sc->text ); } if( ( c = xt_find_node( vc->children, "DESC" ) ) && c->text_len ) g_string_append_printf( reply, "Other information:\n%s", c->text ); /* *sigh* */ imcb_log( ic, "%s", reply->str ); g_string_free( reply, TRUE ); return XT_HANDLED; } static xt_status jabber_add_to_roster_callback( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); int jabber_add_to_roster( struct im_connection *ic, const char *handle, const char *name, const char *group ) { struct xt_node *node; int st; /* Build the item entry */ node = xt_new_node( "item", NULL, NULL ); xt_add_attr( node, "jid", handle ); if( name ) xt_add_attr( node, "name", name ); if( group ) xt_add_child( node, xt_new_node( "group", group, NULL ) ); /* And pack it into a roster-add packet */ node = xt_new_node( "query", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_ROSTER ); node = jabber_make_packet( "iq", "set", NULL, node ); jabber_cache_add( ic, node, jabber_add_to_roster_callback ); st = jabber_write_packet( ic, node ); return st; } static xt_status jabber_add_to_roster_callback( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { char *s, *jid = NULL; struct xt_node *c; if( ( c = xt_find_node( orig->children, "query" ) ) && ( c = xt_find_node( c->children, "item" ) ) && ( jid = xt_find_attr( c, "jid" ) ) && ( s = xt_find_attr( node, "type" ) ) && strcmp( s, "result" ) == 0 ) { if( bee_user_by_handle( ic->bee, ic, jid ) == NULL ) imcb_add_buddy( ic, jid, NULL ); } else { imcb_log( ic, "Error while adding `%s' to your contact list.", jid ? jid : "(unknown handle)" ); } return XT_HANDLED; } int jabber_remove_from_roster( struct im_connection *ic, char *handle ) { struct xt_node *node; int st; /* Build the item entry */ node = xt_new_node( "item", NULL, NULL ); xt_add_attr( node, "jid", handle ); xt_add_attr( node, "subscription", "remove" ); /* And pack it into a roster-add packet */ node = xt_new_node( "query", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_ROSTER ); node = jabber_make_packet( "iq", "set", NULL, node ); st = jabber_write_packet( ic, node ); xt_free_node( node ); return st; } xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); xt_status jabber_iq_query_features( struct im_connection *ic, char *bare_jid ) { struct xt_node *node, *query; struct jabber_buddy *bud; if( ( bud = jabber_buddy_by_jid( ic, bare_jid , 0 ) ) == NULL ) { /* Who cares about the unknown... */ imcb_log( ic, "Couldn't find buddy: %s", bare_jid); return XT_HANDLED; } if( bud->features ) /* been here already */ return XT_HANDLED; node = xt_new_node( "query", NULL, NULL ); xt_add_attr( node, "xmlns", XMLNS_DISCO_INFO ); if( !( query = jabber_make_packet( "iq", "get", bare_jid, node ) ) ) { imcb_log( ic, "WARNING: Couldn't generate feature query" ); xt_free_node( node ); return XT_HANDLED; } jabber_cache_add( ic, query, jabber_iq_parse_features ); return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT; } xt_status jabber_iq_parse_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct xt_node *c; struct jabber_buddy *bud; char *feature, *xmlns, *from; if( !( from = xt_find_attr( node, "from" ) ) || !( c = xt_find_node( node->children, "query" ) ) || !( xmlns = xt_find_attr( c, "xmlns" ) ) || !( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 ) ) { imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); return XT_HANDLED; } if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) == NULL ) { /* Who cares about the unknown... */ imcb_log( ic, "Couldn't find buddy: %s", from ); return XT_HANDLED; } c = c->children; while( ( c = xt_find_node( c, "feature" ) ) ) { feature = xt_find_attr( c, "var" ); if( feature ) bud->features = g_slist_append( bud->features, g_strdup( feature ) ); c = c->next; } return XT_HANDLED; } xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); xt_status jabber_iq_query_server( struct im_connection *ic, char *jid, char *xmlns ) { struct xt_node *node, *query; struct jabber_data *jd = ic->proto_data; node = xt_new_node( "query", NULL, NULL ); xt_add_attr( node, "xmlns", xmlns ); if( !( query = jabber_make_packet( "iq", "get", jid, node ) ) ) { imcb_log( ic, "WARNING: Couldn't generate server query" ); xt_free_node( node ); } jd->have_streamhosts--; jabber_cache_add( ic, query, jabber_iq_parse_server_features ); return jabber_write_packet( ic, query ) ? XT_HANDLED : XT_ABORT; } /* * Query the server for "items", query each "item" for identities, query each "item" that's a proxy for it's bytestream info */ xt_status jabber_iq_parse_server_features( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct xt_node *c; struct jabber_data *jd = ic->proto_data; char *xmlns, *from; if( !( c = xt_find_node( node->children, "query" ) ) || !( from = xt_find_attr( node, "from" ) ) || !( xmlns = xt_find_attr( c, "xmlns" ) ) ) { imcb_log( ic, "WARNING: Received incomplete IQ-result packet for discover" ); return XT_HANDLED; } jd->have_streamhosts++; if( strcmp( xmlns, XMLNS_DISCO_ITEMS ) == 0 ) { char *itemjid; /* answer from server */ c = c->children; while( ( c = xt_find_node( c, "item" ) ) ) { itemjid = xt_find_attr( c, "jid" ); if( itemjid ) jabber_iq_query_server( ic, itemjid, XMLNS_DISCO_INFO ); c = c->next; } } else if( strcmp( xmlns, XMLNS_DISCO_INFO ) == 0 ) { char *category, *type; /* answer from potential proxy */ c = c->children; while( ( c = xt_find_node( c, "identity" ) ) ) { category = xt_find_attr( c, "category" ); type = xt_find_attr( c, "type" ); if( type && ( strcmp( type, "bytestreams" ) == 0 ) && category && ( strcmp( category, "proxy" ) == 0 ) ) jabber_iq_query_server( ic, from, XMLNS_BYTESTREAMS ); c = c->next; } } else if( strcmp( xmlns, XMLNS_BYTESTREAMS ) == 0 ) { char *host, *jid, *port_s; int port; /* answer from proxy */ if( ( c = xt_find_node( c->children, "streamhost" ) ) && ( host = xt_find_attr( c, "host" ) ) && ( port_s = xt_find_attr( c, "port" ) ) && ( sscanf( port_s, "%d", &port ) == 1 ) && ( jid = xt_find_attr( c, "jid" ) ) ) { jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 ); sh->jid = g_strdup( jid ); sh->host = g_strdup( host ); g_snprintf( sh->port, sizeof( sh->port ), "%u", port ); imcb_log( ic, "Proxy found: jid %s host %s port %u", jid, host, port ); jd->streamhosts = g_slist_append( jd->streamhosts, sh ); } } if( jd->have_streamhosts == 0 ) jd->have_streamhosts++; return XT_HANDLED; } static xt_status jabber_iq_version_response( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); void jabber_iq_version_send( struct im_connection *ic, struct jabber_buddy *bud, void *data ) { struct xt_node *node, *query; node = xt_new_node( "query", NULL, NULL ); xt_add_attr( node, "xmlns", XMLNS_VERSION ); query = jabber_make_packet( "iq", "get", bud->full_jid, node ); jabber_cache_add( ic, query, jabber_iq_version_response ); jabber_write_packet( ic, query ); } static xt_status jabber_iq_version_response( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct xt_node *query; GString *rets; char *s; char *ret[2] = {}; bee_user_t *bu; struct jabber_buddy *bud = NULL; if( ( s = xt_find_attr( node, "from" ) ) && ( bud = jabber_buddy_by_jid( ic, s, 0 ) ) && ( query = xt_find_node( node->children, "query" ) ) && ( bu = bee_user_by_handle( ic->bee, ic, bud->bare_jid ) ) ) { rets = g_string_new( "Resource " ); g_string_append( rets, bud->resource ); } else return XT_HANDLED; for( query = query->children; query; query = query->next ) if( query->text_len > 0 ) g_string_append_printf( rets, " %s: %s,", query->name, query->text ); g_string_truncate( rets, rets->len - 1 ); ret[0] = rets->str; imcb_buddy_action_response( bu, "VERSION", ret, NULL ); g_string_free( rets, TRUE ); return XT_HANDLED; } static xt_status jabber_iq_disco_server_response( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ); static int jabber_iq_disco_server( struct im_connection *ic ) { struct xt_node *node, *iq; struct jabber_data *jd = ic->proto_data; node = xt_new_node( "query", NULL, NULL ); xt_add_attr( node, "xmlns", XMLNS_DISCO_INFO ); iq = jabber_make_packet( "iq", "get", jd->server, node ); jabber_cache_add( ic, iq, jabber_iq_disco_server_response ); return jabber_write_packet( ic, iq ); } static xt_status jabber_iq_disco_server_response( struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct jabber_data *jd = ic->proto_data; struct xt_node *id; if( ( id = xt_find_path( node, "query/identity" ) ) ) { char *cat, *type, *name; if( !( cat = xt_find_attr( id, "category" ) ) || !( type = xt_find_attr( id, "type" ) ) || !( name = xt_find_attr( id, "name" ) ) ) return XT_HANDLED; if( strcmp( cat, "server" ) == 0 && strcmp( type, "im" ) == 0 && strstr( name, "Google" ) != NULL ) jd->flags |= JFLAG_GTALK; } return XT_HANDLED; } bitlbee-3.2.1/protocols/jabber/si.c0000644000175000017500000004173312245474076016556 0ustar wilmerwilmer/***************************************************************************\ * * * BitlBee - An IRC to IM gateway * * Jabber module - SI packets * * * * Copyright 2007 Uli Meis * * * * 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 along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * * \***************************************************************************/ #include "jabber.h" #include "sha1.h" void jabber_si_answer_request( file_transfer_t *ft ); int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ); /* file_transfer free() callback */ void jabber_si_free_transfer( file_transfer_t *ft) { struct jabber_transfer *tf = ft->data; struct jabber_data *jd = tf->ic->proto_data; if ( tf->watch_in ) b_event_remove( tf->watch_in ); jd->filetransfers = g_slist_remove( jd->filetransfers, tf ); if( tf->fd != -1 ) { closesocket( tf->fd ); tf->fd = -1; } if( tf->disco_timeout ) b_event_remove( tf->disco_timeout ); g_free( tf->ini_jid ); g_free( tf->tgt_jid ); g_free( tf->iq_id ); g_free( tf->sid ); g_free( tf ); } /* file_transfer canceled() callback */ void jabber_si_canceled( file_transfer_t *ft, char *reason ) { struct jabber_transfer *tf = ft->data; struct xt_node *reply, *iqnode; if( tf->accepted ) return; iqnode = jabber_make_packet( "iq", "error", tf->ini_jid, NULL ); xt_add_attr( iqnode, "id", tf->iq_id ); reply = jabber_make_error_packet( iqnode, "forbidden", "cancel", "403" ); xt_free_node( iqnode ); if( !jabber_write_packet( tf->ic, reply ) ) imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); xt_free_node( reply ); } int jabber_si_check_features( struct jabber_transfer *tf, GSList *features ) { int foundft = FALSE, foundbt = FALSE, foundsi = FALSE; while ( features ) { if( !strcmp( features->data, XMLNS_FILETRANSFER ) ) foundft = TRUE; if( !strcmp( features->data, XMLNS_BYTESTREAMS ) ) foundbt = TRUE; if( !strcmp( features->data, XMLNS_SI ) ) foundsi = TRUE; features = g_slist_next(features); } if( !foundft ) imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature file transfers" ); else if( !foundbt ) imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature byte streams (required)" ); else if( !foundsi ) imcb_file_canceled( tf->ic, tf->ft, "Buddy's client doesn't feature stream initiation (required)" ); return foundft && foundbt && foundsi; } void jabber_si_transfer_start( struct jabber_transfer *tf ) { if( !jabber_si_check_features( tf, tf->bud->features ) ) return; /* send the request to our buddy */ jabber_si_send_request( tf->ic, tf->bud->full_jid, tf ); /* and start the receive logic */ imcb_file_recv_start( tf->ic, tf->ft ); } gboolean jabber_si_waitfor_disco( gpointer data, gint fd, b_input_condition cond ) { struct jabber_transfer *tf = data; struct jabber_data *jd = tf->ic->proto_data; tf->disco_timeout_fired++; if( tf->bud->features && jd->have_streamhosts==1 ) { tf->disco_timeout = 0; jabber_si_transfer_start( tf ); return FALSE; } /* 8 seconds should be enough for server and buddy to respond */ if ( tf->disco_timeout_fired < 16 ) return TRUE; if( !tf->bud->features && jd->have_streamhosts!=1 ) imcb_log( tf->ic, "Couldn't get buddy's features nor discover all services of the server" ); else if( !tf->bud->features ) imcb_log( tf->ic, "Couldn't get buddy's features" ); else imcb_log( tf->ic, "Couldn't discover some of the server's services" ); tf->disco_timeout = 0; jabber_si_transfer_start( tf ); return FALSE; } void jabber_si_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ) { struct jabber_transfer *tf; struct jabber_data *jd = ic->proto_data; struct jabber_buddy *bud; char *server = jd->server, *s; if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) bud = jabber_buddy_by_ext_jid( ic, who, 0 ); else bud = jabber_buddy_by_jid( ic, who, 0 ); if( bud == NULL ) { imcb_file_canceled( ic, ft, "Couldn't find buddy (BUG?)" ); return; } imcb_log( ic, "Trying to send %s(%zd bytes) to %s", ft->file_name, ft->file_size, who ); tf = g_new0( struct jabber_transfer, 1 ); tf->ic = ic; tf->ft = ft; tf->fd = -1; tf->ft->data = tf; tf->ft->free = jabber_si_free_transfer; tf->bud = bud; ft->write = jabber_bs_send_write; jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); /* query buddy's features and server's streaming proxies if neccessary */ if( !tf->bud->features ) jabber_iq_query_features( ic, bud->full_jid ); /* If is not set don't check for proxies */ if( ( jd->have_streamhosts!=1 ) && ( jd->streamhosts==NULL ) && ( strstr( set_getstr( &ic->acc->set, "proxy" ), "" ) != NULL ) ) { jd->have_streamhosts = 0; jabber_iq_query_server( ic, server, XMLNS_DISCO_ITEMS ); } else if ( jd->streamhosts!=NULL ) jd->have_streamhosts = 1; /* if we had to do a query, wait for the result. * Otherwise fire away. */ if( !tf->bud->features || jd->have_streamhosts!=1 ) tf->disco_timeout = b_timeout_add( 500, jabber_si_waitfor_disco, tf ); else jabber_si_transfer_start( tf ); } /* * First function that gets called when a file transfer request comes in. * A lot to parse. * * We choose a stream type from the options given by the initiator. * Then we wait for imcb to call the accept or cancel callbacks. */ int jabber_si_handle_request( struct im_connection *ic, struct xt_node *node, struct xt_node *sinode) { struct xt_node *c, *d, *reply; char *sid, *ini_jid, *tgt_jid, *iq_id, *s, *ext_jid, *size_s; struct jabber_buddy *bud; int requestok = FALSE; char *name, *cmp; size_t size; struct jabber_transfer *tf; struct jabber_data *jd = ic->proto_data; file_transfer_t *ft; /* All this means we expect something like this: ( I think ) * * * * * * * */ if( !( ini_jid = xt_find_attr( node, "from" ) ) || !( tgt_jid = xt_find_attr( node, "to" ) ) || !( iq_id = xt_find_attr( node, "id" ) ) || !( sid = xt_find_attr( sinode, "id" ) ) || !( cmp = xt_find_attr( sinode, "profile" ) ) || !( 0 == strcmp( cmp, XMLNS_FILETRANSFER ) ) || !( d = xt_find_node( sinode->children, "file" ) ) || !( cmp = xt_find_attr( d, "xmlns" ) ) || !( 0 == strcmp( cmp, XMLNS_FILETRANSFER ) ) || !( name = xt_find_attr( d, "name" ) ) || !( size_s = xt_find_attr( d, "size" ) ) || !( 1 == sscanf( size_s, "%zd", &size ) ) || !( d = xt_find_node( sinode->children, "feature" ) ) || !( cmp = xt_find_attr( d, "xmlns" ) ) || !( 0 == strcmp( cmp, XMLNS_FEATURE ) ) || !( d = xt_find_node( d->children, "x" ) ) || !( cmp = xt_find_attr( d, "xmlns" ) ) || !( 0 == strcmp( cmp, XMLNS_XDATA ) ) || !( cmp = xt_find_attr( d, "type" ) ) || !( 0 == strcmp( cmp, "form" ) ) || !( d = xt_find_node( d->children, "field" ) ) || !( cmp = xt_find_attr( d, "var" ) ) || !( 0 == strcmp( cmp, "stream-method" ) ) ) { imcb_log( ic, "WARNING: Received incomplete Stream Initiation request" ); } else { /* Check if we support one of the options */ c = d->children; while( ( c = xt_find_node( c, "option" ) ) ) if( ( d = xt_find_node( c->children, "value" ) ) && ( d->text != NULL ) && ( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) { requestok = TRUE; break; } else { c = c->next; } if ( !requestok ) imcb_log( ic, "WARNING: Unsupported file transfer request from %s", ini_jid); } if( requestok ) { /* Figure out who the transfer should come frome... */ ext_jid = ini_jid; if( ( s = strchr( ini_jid, '/' ) ) ) { if( ( bud = jabber_buddy_by_jid( ic, ini_jid, GET_BUDDY_EXACT ) ) ) { bud->last_msg = time( NULL ); ext_jid = bud->ext_jid ? : bud->bare_jid; } else *s = 0; /* We need to generate a bare JID now. */ } if( !( ft = imcb_file_send_start( ic, ext_jid, name, size ) ) ) { imcb_log( ic, "WARNING: Error handling transfer request from %s", ini_jid); requestok = FALSE; } if( s ) *s = '/'; } if( !requestok ) { reply = jabber_make_error_packet( node, "item-not-found", "cancel", NULL ); if (!jabber_write_packet( ic, reply )) imcb_log( ic, "WARNING: Error generating reply to file transfer request" ); xt_free_node( reply ); return XT_HANDLED; } /* Request is fine. */ tf = g_new0( struct jabber_transfer, 1 ); tf->ini_jid = g_strdup( ini_jid ); tf->tgt_jid = g_strdup( tgt_jid ); tf->iq_id = g_strdup( iq_id ); tf->sid = g_strdup( sid ); tf->ic = ic; tf->ft = ft; tf->fd = -1; tf->ft->data = tf; tf->ft->accept = jabber_si_answer_request; tf->ft->free = jabber_si_free_transfer; tf->ft->canceled = jabber_si_canceled; jd->filetransfers = g_slist_prepend( jd->filetransfers, tf ); return XT_HANDLED; } /* * imc called the accept callback which probably means that the user accepted this file transfer. * We send our response to the initiator. * In the next step, the initiator will send us a request for the given stream type. * (currently that can only be a SOCKS5 bytestream) */ void jabber_si_answer_request( file_transfer_t *ft ) { struct jabber_transfer *tf = ft->data; struct xt_node *node, *sinode, *reply; /* generate response, start with the SI tag */ sinode = xt_new_node( "si", NULL, NULL ); xt_add_attr( sinode, "xmlns", XMLNS_SI ); xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); xt_add_attr( sinode, "id", tf->sid ); /* now the file tag */ node = xt_new_node( "file", NULL, NULL ); xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); xt_add_child( sinode, node ); /* and finally the feature tag */ node = xt_new_node( "field", NULL, NULL ); xt_add_attr( node, "var", "stream-method" ); xt_add_attr( node, "type", "list-single" ); /* Currently all we can do. One could also implement in-band (IBB) */ xt_add_child( node, xt_new_node( "value", XMLNS_BYTESTREAMS, NULL ) ); node = xt_new_node( "x", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_XDATA ); xt_add_attr( node, "type", "submit" ); node = xt_new_node( "feature", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_FEATURE ); xt_add_child( sinode, node ); reply = jabber_make_packet( "iq", "result", tf->ini_jid, sinode ); xt_add_attr( reply, "id", tf->iq_id ); if( !jabber_write_packet( tf->ic, reply ) ) imcb_log( tf->ic, "WARNING: Error generating reply to file transfer request" ); else tf->accepted = TRUE; xt_free_node( reply ); } static xt_status jabber_si_handle_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) { struct xt_node *c, *d; char *ini_jid = NULL, *tgt_jid, *iq_id, *cmp; GSList *tflist; struct jabber_transfer *tf=NULL; struct jabber_data *jd = ic->proto_data; if( !( tgt_jid = xt_find_attr( node, "from" ) ) || !( ini_jid = xt_find_attr( node, "to" ) ) ) { imcb_log( ic, "Invalid SI response from=%s to=%s", tgt_jid, ini_jid ); return XT_HANDLED; } /* All this means we expect something like this: ( I think ) * * * [ ] <-- not neccessary * * * * */ if( !( tgt_jid = xt_find_attr( node, "from" ) ) || !( ini_jid = xt_find_attr( node, "to" ) ) || !( iq_id = xt_find_attr( node, "id" ) ) || !( c = xt_find_node( node->children, "si" ) ) || !( cmp = xt_find_attr( c, "xmlns" ) ) || !( strcmp( cmp, XMLNS_SI ) == 0 ) || !( d = xt_find_node( c->children, "feature" ) ) || !( cmp = xt_find_attr( d, "xmlns" ) ) || !( strcmp( cmp, XMLNS_FEATURE ) == 0 ) || !( d = xt_find_node( d->children, "x" ) ) || !( cmp = xt_find_attr( d, "xmlns" ) ) || !( strcmp( cmp, XMLNS_XDATA ) == 0 ) || !( cmp = xt_find_attr( d, "type" ) ) || !( strcmp( cmp, "submit" ) == 0 ) || !( d = xt_find_node( d->children, "field" ) ) || !( cmp = xt_find_attr( d, "var" ) ) || !( strcmp( cmp, "stream-method" ) == 0 ) || !( d = xt_find_node( d->children, "value" ) ) ) { imcb_log( ic, "WARNING: Received incomplete Stream Initiation response" ); return XT_HANDLED; } if( !( strcmp( d->text, XMLNS_BYTESTREAMS ) == 0 ) ) { /* since we should only have advertised what we can do and the peer should * only have chosen what we offered, this should never happen */ imcb_log( ic, "WARNING: Received invalid Stream Initiation response, method %s", d->text ); return XT_HANDLED; } /* Let's see if we can find out what this bytestream should be for... */ for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) ) { struct jabber_transfer *tft = tflist->data; if( ( strcmp( tft->iq_id, iq_id ) == 0 ) ) { tf = tft; break; } } if (!tf) { imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid ); return XT_HANDLED; } tf->ini_jid = g_strdup( ini_jid ); tf->tgt_jid = g_strdup( tgt_jid ); imcb_log( ic, "File %s: %s accepted the transfer!", tf->ft->file_name, tgt_jid ); jabber_bs_send_start( tf ); return XT_HANDLED; } int jabber_si_send_request(struct im_connection *ic, char *who, struct jabber_transfer *tf ) { struct xt_node *node, *sinode; struct jabber_buddy *bud; /* who knows how many bits the future holds :) */ char filesizestr[ 1 + ( int ) ( 0.301029995663981198f * sizeof( size_t ) * 8 ) ]; const char *methods[] = { XMLNS_BYTESTREAMS, //XMLNS_IBB, NULL }; const char **m; char *s; /* Maybe we should hash this? */ tf->sid = g_strdup_printf( "BitlBeeJabberSID%d", tf->ft->local_id ); if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) bud = jabber_buddy_by_ext_jid( ic, who, 0 ); else bud = jabber_buddy_by_jid( ic, who, 0 ); /* start with the SI tag */ sinode = xt_new_node( "si", NULL, NULL ); xt_add_attr( sinode, "xmlns", XMLNS_SI ); xt_add_attr( sinode, "profile", XMLNS_FILETRANSFER ); xt_add_attr( sinode, "id", tf->sid ); /* if( mimetype ) xt_add_attr( node, "mime-type", mimetype ); */ /* now the file tag */ /* if( desc ) node = xt_new_node( "desc", descr, NULL ); */ node = xt_new_node( "range", NULL, NULL ); sprintf( filesizestr, "%zd", tf->ft->file_size ); node = xt_new_node( "file", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_FILETRANSFER ); xt_add_attr( node, "name", tf->ft->file_name ); xt_add_attr( node, "size", filesizestr ); /* if (hash) xt_add_attr( node, "hash", hash ); if (date) xt_add_attr( node, "date", date ); */ xt_add_child( sinode, node ); /* and finally the feature tag */ node = xt_new_node( "field", NULL, NULL ); xt_add_attr( node, "var", "stream-method" ); xt_add_attr( node, "type", "list-single" ); for ( m = methods ; *m ; m ++ ) xt_add_child( node, xt_new_node( "option", NULL, xt_new_node( "value", (char *)*m, NULL ) ) ); node = xt_new_node( "x", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_XDATA ); xt_add_attr( node, "type", "form" ); node = xt_new_node( "feature", NULL, node ); xt_add_attr( node, "xmlns", XMLNS_FEATURE ); xt_add_child( sinode, node ); /* and we are there... */ node = jabber_make_packet( "iq", "set", bud ? bud->full_jid : who, sinode ); jabber_cache_add( ic, node, jabber_si_handle_response ); tf->iq_id = g_strdup( xt_find_attr( node, "id" ) ); return jabber_write_packet( ic, node ); } bitlbee-3.2.1/protocols/nogaim.c0000644000175000017500000004317312245474076016170 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* * nogaim * * Gaim without gaim - for BitlBee * * This file contains functions called by the Gaim IM-modules. It's written * from scratch for BitlBee and doesn't contain any code from Gaim anymore * (except for the function names). */ /* 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 */ #define BITLBEE_CORE #include #include "nogaim.h" GSList *connections; #ifdef WITH_PLUGINS gboolean load_plugin(char *path) { void (*init_function) (void); GModule *mod = g_module_open(path, G_MODULE_BIND_LAZY); if(!mod) { log_message(LOGLVL_ERROR, "Can't find `%s', not loading (%s)\n", path, g_module_error()); return FALSE; } if(!g_module_symbol(mod,"init_plugin",(gpointer *) &init_function)) { log_message(LOGLVL_WARNING, "Can't find function `init_plugin' in `%s'\n", path); return FALSE; } init_function(); return TRUE; } void load_plugins(void) { GDir *dir; GError *error = NULL; dir = g_dir_open(global.conf->plugindir, 0, &error); if (dir) { const gchar *entry; char *path; while ((entry = g_dir_read_name(dir))) { path = g_build_filename(global.conf->plugindir, entry, NULL); if(!path) { log_message(LOGLVL_WARNING, "Can't build path for %s\n", entry); continue; } load_plugin(path); g_free(path); } g_dir_close(dir); } } #endif GList *protocols = NULL; void register_protocol (struct prpl *p) { int i; gboolean refused = global.conf->protocols != NULL; for (i = 0; global.conf->protocols && global.conf->protocols[i]; i++) { if (g_strcasecmp(p->name, global.conf->protocols[i]) == 0) refused = FALSE; } if (refused) log_message(LOGLVL_WARNING, "Protocol %s disabled\n", p->name); else protocols = g_list_append(protocols, p); } struct prpl *find_protocol(const char *name) { GList *gl; for( gl = protocols; gl; gl = gl->next ) { struct prpl *proto = gl->data; if( g_strcasecmp( proto->name, name ) == 0 ) return proto; } return NULL; } void nogaim_init() { extern void msn_initmodule(); extern void oscar_initmodule(); extern void byahoo_initmodule(); extern void jabber_initmodule(); extern void twitter_initmodule(); extern void purple_initmodule(); #ifdef WITH_MSN msn_initmodule(); #endif #ifdef WITH_OSCAR oscar_initmodule(); #endif #ifdef WITH_YAHOO byahoo_initmodule(); #endif #ifdef WITH_JABBER jabber_initmodule(); #endif #ifdef WITH_TWITTER twitter_initmodule(); #endif #ifdef WITH_PURPLE purple_initmodule(); #endif #ifdef WITH_PLUGINS load_plugins(); #endif } GSList *get_connections() { return connections; } struct im_connection *imcb_new( account_t *acc ) { struct im_connection *ic; ic = g_new0( struct im_connection, 1 ); ic->bee = acc->bee; ic->acc = acc; acc->ic = ic; connections = g_slist_append( connections, ic ); return( ic ); } void imc_free( struct im_connection *ic ) { account_t *a; /* Destroy the pointer to this connection from the account list */ for( a = ic->bee->accounts; a; a = a->next ) if( a->ic == ic ) { a->ic = NULL; break; } connections = g_slist_remove( connections, ic ); g_free( ic ); } static void serv_got_crap( struct im_connection *ic, char *format, ... ) { va_list params; char *text; account_t *a; va_start( params, format ); text = g_strdup_vprintf( format, params ); va_end( params ); if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) || ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) ) strip_html( text ); /* Try to find a different connection on the same protocol. */ for( a = ic->bee->accounts; a; a = a->next ) if( a->prpl == ic->acc->prpl && a->ic != ic ) break; /* If we found one, include the screenname in the message. */ if( a ) /* FIXME(wilmer): ui_log callback or so */ irc_rootmsg( ic->bee->ui_data, "%s - %s", ic->acc->tag, text ); else irc_rootmsg( ic->bee->ui_data, "%s - %s", ic->acc->prpl->name, text ); g_free( text ); } void imcb_log( struct im_connection *ic, char *format, ... ) { va_list params; char *text; va_start( params, format ); text = g_strdup_vprintf( format, params ); va_end( params ); if( ic->flags & OPT_LOGGED_IN ) serv_got_crap( ic, "%s", text ); else serv_got_crap( ic, "Logging in: %s", text ); g_free( text ); } void imcb_error( struct im_connection *ic, char *format, ... ) { va_list params; char *text; va_start( params, format ); text = g_strdup_vprintf( format, params ); va_end( params ); if( ic->flags & OPT_LOGGED_IN ) serv_got_crap( ic, "Error: %s", text ); else serv_got_crap( ic, "Login error: %s", text ); g_free( text ); } static gboolean send_keepalive( gpointer d, gint fd, b_input_condition cond ) { struct im_connection *ic = d; if( ( ic->flags & OPT_PONGS ) && !( ic->flags & OPT_PONGED ) ) { /* This protocol is expected to ack keepalives and hasn't since the last time we were here. */ imcb_error( ic, "Connection timeout" ); imc_logout( ic, TRUE ); return FALSE; } ic->flags &= ~OPT_PONGED; if( ic->acc->prpl->keepalive ) ic->acc->prpl->keepalive( ic ); return TRUE; } void start_keepalives( struct im_connection *ic, int interval ) { b_event_remove( ic->keepalive ); ic->keepalive = b_timeout_add( interval, send_keepalive, ic ); /* Connecting successfully counts as a first successful pong. */ if( ic->flags & OPT_PONGS ) ic->flags |= OPT_PONGED; } void imcb_connected( struct im_connection *ic ) { /* MSN servers sometimes redirect you to a different server and do the whole login sequence again, so these "late" calls to this function should be handled correctly. (IOW, ignored) */ if( ic->flags & OPT_LOGGED_IN ) return; imcb_log( ic, "Logged in" ); ic->flags |= OPT_LOGGED_IN; start_keepalives( ic, 60000 ); /* Necessary to send initial presence status, even if we're not away. */ imc_away_send_update( ic ); /* Apparently we're connected successfully, so reset the exponential backoff timer. */ ic->acc->auto_reconnect_delay = 0; if( ic->bee->ui->imc_connected ) ic->bee->ui->imc_connected( ic ); } gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond ) { account_t *a = data; a->reconnect = 0; account_on( a->bee, a ); return( FALSE ); /* Only have to run the timeout once */ } void cancel_auto_reconnect( account_t *a ) { b_event_remove( a->reconnect ); a->reconnect = 0; } void imc_logout( struct im_connection *ic, int allow_reconnect ) { bee_t *bee = ic->bee; account_t *a; GSList *l; int delay; /* Nested calls might happen sometimes, this is probably the best place to catch them. */ if( ic->flags & OPT_LOGGING_OUT ) return; else ic->flags |= OPT_LOGGING_OUT; if( ic->bee->ui->imc_disconnected ) ic->bee->ui->imc_disconnected( ic ); imcb_log( ic, "Signing off.." ); /* TBH I don't remember anymore why I didn't just use ic->acc... */ for( a = bee->accounts; a; a = a->next ) if( a->ic == ic ) break; if( a && !allow_reconnect && !( ic->flags & OPT_LOGGED_IN ) && set_getbool( &a->set, "oauth" ) ) { /* If this account supports OAuth, we're not logged in yet and not allowed to retry, assume there were auth issues. Give a helpful message on what might be necessary to fix this. */ imcb_log( ic, "If you're having problems logging in, try re-requesting " "an OAuth token: account %s set password \"\"", a->tag ); } for( l = bee->users; l; ) { bee_user_t *bu = l->data; GSList *next = l->next; if( bu->ic == ic ) bee_user_free( bee, bu ); l = next; } b_event_remove( ic->keepalive ); ic->keepalive = 0; ic->acc->prpl->logout( ic ); b_event_remove( ic->inpa ); g_free( ic->away ); ic->away = NULL; query_del_by_conn( (irc_t*) ic->bee->ui_data, ic ); if( !a ) { /* Uhm... This is very sick. */ } else if( allow_reconnect && set_getbool( &bee->set, "auto_reconnect" ) && set_getbool( &a->set, "auto_reconnect" ) && ( delay = account_reconnect_delay( a ) ) > 0 ) { imcb_log( ic, "Reconnecting in %d seconds..", delay ); a->reconnect = b_timeout_add( delay * 1000, auto_reconnect, a ); } imc_free( ic ); } void imcb_ask( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont ) { query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, g_free, data ); } void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont, query_callback myfree ) { query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, myfree, data ); } void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group ) { bee_user_t *bu; bee_t *bee = ic->bee; bee_group_t *oldg; if( !( bu = bee_user_by_handle( bee, ic, handle ) ) ) bu = bee_user_new( bee, ic, handle, 0 ); oldg = bu->group; bu->group = bee_group_by_name( bee, group, TRUE ); if( bee->ui->user_group && bu->group != oldg ) bee->ui->user_group( bee, bu ); } void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *fullname ) { bee_t *bee = ic->bee; bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); if( !bu || !fullname ) return; if( !bu->fullname || strcmp( bu->fullname, fullname ) != 0 ) { g_free( bu->fullname ); bu->fullname = g_strdup( fullname ); if( bee->ui->user_fullname ) bee->ui->user_fullname( bee, bu ); } } void imcb_remove_buddy( struct im_connection *ic, const char *handle, char *group ) { bee_user_free( ic->bee, bee_user_by_handle( ic->bee, ic, handle ) ); } /* Mainly meant for ICQ (and now also for Jabber conferences) to allow IM modules to suggest a nickname for a handle. */ void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick ) { bee_t *bee = ic->bee; bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); if( !bu || !nick ) return; g_free( bu->nick ); bu->nick = g_strdup( nick ); if( bee->ui->user_nick_hint ) bee->ui->user_nick_hint( bee, bu, nick ); } struct imcb_ask_cb_data { struct im_connection *ic; char *handle; }; static void imcb_ask_auth_cb_no( void *data ) { struct imcb_ask_cb_data *cbd = data; cbd->ic->acc->prpl->auth_deny( cbd->ic, cbd->handle ); g_free( cbd->handle ); g_free( cbd ); } static void imcb_ask_auth_cb_yes( void *data ) { struct imcb_ask_cb_data *cbd = data; cbd->ic->acc->prpl->auth_allow( cbd->ic, cbd->handle ); g_free( cbd->handle ); g_free( cbd ); } void imcb_ask_auth( struct im_connection *ic, const char *handle, const char *realname ) { struct imcb_ask_cb_data *data = g_new0( struct imcb_ask_cb_data, 1 ); char *s, *realname_ = NULL; if( realname != NULL ) realname_ = g_strdup_printf( " (%s)", realname ); s = g_strdup_printf( "The user %s%s wants to add you to his/her buddy list.", handle, realname_ ? realname_ : "" ); g_free( realname_ ); data->ic = ic; data->handle = g_strdup( handle ); query_add( (irc_t *) ic->bee->ui_data, ic, s, imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, g_free, data ); } static void imcb_ask_add_cb_no( void *data ) { g_free( ((struct imcb_ask_cb_data*)data)->handle ); g_free( data ); } static void imcb_ask_add_cb_yes( void *data ) { struct imcb_ask_cb_data *cbd = data; cbd->ic->acc->prpl->add_buddy( cbd->ic, cbd->handle, NULL ); imcb_ask_add_cb_no( data ); } void imcb_ask_add( struct im_connection *ic, const char *handle, const char *realname ) { struct imcb_ask_cb_data *data = g_new0( struct imcb_ask_cb_data, 1 ); char *s; /* TODO: Make a setting for this! */ if( bee_user_by_handle( ic->bee, ic, handle ) != NULL ) return; s = g_strdup_printf( "The user %s is not in your buddy list yet. Do you want to add him/her now?", handle ); data->ic = ic; data->handle = g_strdup( handle ); query_add( (irc_t *) ic->bee->ui_data, ic, s, imcb_ask_add_cb_yes, imcb_ask_add_cb_no, g_free, data ); } struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle ) { return bee_user_by_handle( ic->bee, ic, handle ); } /* The plan is to not allow straight calls to prpl functions anymore, but do them all from some wrappers. We'll start to define some down here: */ int imc_chat_msg( struct groupchat *c, char *msg, int flags ) { char *buf = NULL; if( ( c->ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "", 6 ) != 0 ) ) { buf = escape_html( msg ); msg = buf; } c->ic->acc->prpl->chat_msg( c, msg, flags ); g_free( buf ); return 1; } static char *imc_away_state_find( GList *gcm, char *away, char **message ); int imc_away_send_update( struct im_connection *ic ) { char *away, *msg = NULL; if( ic->acc->prpl->away_states == NULL || ic->acc->prpl->set_away == NULL ) return 0; away = set_getstr( &ic->acc->set, "away" ) ? : set_getstr( &ic->bee->set, "away" ); if( away && *away ) { GList *m = ic->acc->prpl->away_states( ic ); msg = ic->acc->flags & ACC_FLAG_AWAY_MESSAGE ? away : NULL; away = imc_away_state_find( m, away, &msg ) ? : m->data; } else if( ic->acc->flags & ACC_FLAG_STATUS_MESSAGE ) { away = NULL; msg = set_getstr( &ic->acc->set, "status" ) ? : set_getstr( &ic->bee->set, "status" ); } ic->acc->prpl->set_away( ic, away, msg ); return 1; } static char *imc_away_alias_list[8][5] = { { "Away from computer", "Away", "Extended away", NULL }, { "NA", "N/A", "Not available", NULL }, { "Busy", "Do not disturb", "DND", "Occupied", NULL }, { "Be right back", "BRB", NULL }, { "On the phone", "Phone", "On phone", NULL }, { "Out to lunch", "Lunch", "Food", NULL }, { "Invisible", "Hidden" }, { NULL } }; static char *imc_away_state_find( GList *gcm, char *away, char **message ) { GList *m; int i, j; for( m = gcm; m; m = m->next ) if( g_strncasecmp( m->data, away, strlen( m->data ) ) == 0 ) { /* At least the Yahoo! module works better if message contains no data unless it adds something to what we have in state already. */ if( strlen( m->data ) == strlen( away ) ) *message = NULL; return m->data; } for( i = 0; *imc_away_alias_list[i]; i ++ ) { int keep_message; for( j = 0; imc_away_alias_list[i][j]; j ++ ) if( g_strncasecmp( away, imc_away_alias_list[i][j], strlen( imc_away_alias_list[i][j] ) ) == 0 ) { keep_message = strlen( away ) != strlen( imc_away_alias_list[i][j] ); break; } if( !imc_away_alias_list[i][j] ) /* If we reach the end, this row */ continue; /* is not what we want. Next! */ /* Now find an entry in this row which exists in gcm */ for( j = 0; imc_away_alias_list[i][j]; j ++ ) { for( m = gcm; m; m = m->next ) if( g_strcasecmp( imc_away_alias_list[i][j], m->data ) == 0 ) { if( !keep_message ) *message = NULL; return imc_away_alias_list[i][j]; } } /* No need to look further, apparently this state doesn't have any good alias for this protocol. */ break; } return NULL; } void imc_add_allow( struct im_connection *ic, char *handle ) { if( g_slist_find_custom( ic->permit, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) == NULL ) { ic->permit = g_slist_prepend( ic->permit, g_strdup( handle ) ); } ic->acc->prpl->add_permit( ic, handle ); } void imc_rem_allow( struct im_connection *ic, char *handle ) { GSList *l; if( ( l = g_slist_find_custom( ic->permit, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) ) { g_free( l->data ); ic->permit = g_slist_delete_link( ic->permit, l ); } ic->acc->prpl->rem_permit( ic, handle ); } void imc_add_block( struct im_connection *ic, char *handle ) { if( g_slist_find_custom( ic->deny, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) == NULL ) { ic->deny = g_slist_prepend( ic->deny, g_strdup( handle ) ); } ic->acc->prpl->add_deny( ic, handle ); } void imc_rem_block( struct im_connection *ic, char *handle ) { GSList *l; if( ( l = g_slist_find_custom( ic->deny, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) ) { g_free( l->data ); ic->deny = g_slist_delete_link( ic->deny, l ); } ic->acc->prpl->rem_deny( ic, handle ); } void imcb_clean_handle( struct im_connection *ic, char *handle ) { /* Accepts a handle and does whatever is necessary to make it BitlBee-friendly. Currently this means removing everything outside 33-127 (ASCII printable excl spaces), @ (only one is allowed) and ! and : */ char out[strlen(handle)+1]; int s, d; s = d = 0; while( handle[s] ) { if( handle[s] > ' ' && handle[s] != '!' && handle[s] != ':' && ( handle[s] & 0x80 ) == 0 ) { if( handle[s] == '@' ) { /* See if we got an @ already? */ out[d] = 0; if( strchr( out, '@' ) ) continue; } out[d++] = handle[s]; } s ++; } out[d] = handle[s]; strcpy( handle, out ); } bitlbee-3.2.1/protocols/bee.c0000644000175000017500000000564112245474076015447 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2010 Wilmer van der Gaast and others * \********************************************************************/ /* Some IM-core stuff */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" static char *set_eval_away_status( set_t *set, char *value ); bee_t *bee_new() { bee_t *b = g_new0( bee_t, 1 ); set_t *s; s = set_add( &b->set, "auto_connect", "true", set_eval_bool, b ); s = set_add( &b->set, "auto_reconnect", "true", set_eval_bool, b ); s = set_add( &b->set, "auto_reconnect_delay", "5*3<900", set_eval_account_reconnect_delay, b ); s = set_add( &b->set, "away", NULL, set_eval_away_status, b ); s->flags |= SET_NULL_OK | SET_HIDDEN; s = set_add( &b->set, "debug", "false", set_eval_bool, b ); s = set_add( &b->set, "mobile_is_away", "false", set_eval_bool, b ); s = set_add( &b->set, "save_on_quit", "true", set_eval_bool, b ); s = set_add( &b->set, "status", NULL, set_eval_away_status, b ); s->flags |= SET_NULL_OK; s = set_add( &b->set, "strip_html", "true", NULL, b ); b->user = g_malloc( 1 ); return b; } void bee_free( bee_t *b ) { while( b->accounts ) { if( b->accounts->ic ) imc_logout( b->accounts->ic, FALSE ); else if( b->accounts->reconnect ) cancel_auto_reconnect( b->accounts ); if( b->accounts->ic == NULL ) account_del( b, b->accounts ); else /* Nasty hack, but account_del() doesn't work in this case and we don't want infinite loops, do we? ;-) */ b->accounts = b->accounts->next; } while( b->set ) set_del( &b->set, b->set->key ); bee_group_free( b ); g_free( b->user ); g_free( b ); } static char *set_eval_away_status( set_t *set, char *value ) { bee_t *bee = set->data; account_t *a; g_free( set->value ); set->value = g_strdup( value ); for( a = bee->accounts; a; a = a->next ) { struct im_connection *ic = a->ic; if( ic && ic->flags & OPT_LOGGED_IN ) imc_away_send_update( ic ); } return value; } bitlbee-3.2.1/conf.h0000644000175000017500000000362012245474076013615 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ /* Configuration reading code */ /* 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 __CONF_H #define __CONF_H typedef enum runmode { RUNMODE_DAEMON, RUNMODE_FORKDAEMON, RUNMODE_INETD } runmode_t; typedef enum authmode { AUTHMODE_OPEN, AUTHMODE_CLOSED, AUTHMODE_REGISTERED } authmode_t; typedef struct conf { char *iface_in, *iface_out; char *port; int nofork; int verbose; runmode_t runmode; authmode_t authmode; char *auth_pass; char *oper_pass; char *hostname; char *configdir; char *plugindir; char *pidfile; char *motdfile; char *primary_storage; char **migrate_storage; int ping_interval; int ping_timeout; char *user; size_t ft_max_size; int ft_max_kbps; char *ft_listen; char **protocols; char *cafile; } conf_t; G_GNUC_MALLOC conf_t *conf_load( int argc, char *argv[] ); void conf_loaddefaults( irc_t *irc ); #endif bitlbee-3.2.1/otr.c0000644000175000017500000014517412245474076013502 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* OTR support (cf. http://www.cypherpunks.ca/otr/) (c) 2008-2011 Sven Moritz Hallberg (c) 2008 funded by stonedcoder.org files used to store OTR data: /.otr_keys /.otr_fprints top-level todos: (search for TODO for more ;-)) integrate otr_load/otr_save with existing storage backends per-account policy settings per-user policy settings */ /* 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 "irc.h" #include "otr.h" #include #include #include #include #include /** OTR interface routines for the OtrlMessageAppOps struct: **/ OtrlPolicy op_policy(void *opdata, ConnContext *context); void op_create_privkey(void *opdata, const char *accountname, const char *protocol); int op_is_logged_in(void *opdata, const char *accountname, const char *protocol, const char *recipient); void op_inject_message(void *opdata, const char *accountname, const char *protocol, const char *recipient, const char *message); int op_display_otr_message(void *opdata, const char *accountname, const char *protocol, const char *username, const char *msg); void op_new_fingerprint(void *opdata, OtrlUserState us, const char *accountname, const char *protocol, const char *username, unsigned char fingerprint[20]); void op_write_fingerprints(void *opdata); void op_gone_secure(void *opdata, ConnContext *context); void op_gone_insecure(void *opdata, ConnContext *context); void op_still_secure(void *opdata, ConnContext *context, int is_reply); void op_log_message(void *opdata, const char *message); int op_max_message_size(void *opdata, ConnContext *context); const char *op_account_name(void *opdata, const char *account, const char *protocol); /** otr sub-command handlers: **/ static void cmd_otr(irc_t *irc, char **args); void cmd_otr_connect(irc_t *irc, char **args); void cmd_otr_disconnect(irc_t *irc, char **args); void cmd_otr_reconnect(irc_t *irc, char **args); void cmd_otr_smp(irc_t *irc, char **args); void cmd_otr_smpq(irc_t *irc, char **args); void cmd_otr_trust(irc_t *irc, char **args); void cmd_otr_info(irc_t *irc, char **args); void cmd_otr_keygen(irc_t *irc, char **args); void cmd_otr_forget(irc_t *irc, char **args); const command_t otr_commands[] = { { "connect", 1, &cmd_otr_connect, 0 }, { "disconnect", 1, &cmd_otr_disconnect, 0 }, { "reconnect", 1, &cmd_otr_reconnect, 0 }, { "smp", 2, &cmd_otr_smp, 0 }, { "smpq", 3, &cmd_otr_smpq, 0 }, { "trust", 6, &cmd_otr_trust, 0 }, { "info", 0, &cmd_otr_info, 0 }, { "keygen", 1, &cmd_otr_keygen, 0 }, { "forget", 2, &cmd_otr_forget, 0 }, { NULL } }; typedef struct { void *fst; void *snd; } pair_t; static OtrlMessageAppOps otr_ops; /* collects interface functions required by OTR */ /** misc. helpers/subroutines: **/ /* check whether we are already generating a key for a given account */ int keygen_in_progress(irc_t *irc, const char *handle, const char *protocol); /* start background process to generate a (new) key for a given account */ void otr_keygen(irc_t *irc, const char *handle, const char *protocol); /* main function for the forked keygen slave */ void keygen_child_main(OtrlUserState us, int infd, int outfd); /* mainloop handler for when a keygen finishes */ gboolean keygen_finish_handler(gpointer data, gint fd, b_input_condition cond); /* copy the contents of file a to file b, overwriting it if it exists */ void copyfile(const char *a, const char *b); /* read one line of input from a stream, excluding trailing newline */ void myfgets(char *s, int size, FILE *stream); /* some yes/no handlers */ void yes_keygen(void *data); void yes_forget_fingerprint(void *data); void yes_forget_context(void *data); void yes_forget_key(void *data); /* helper to make sure accountname and protocol match the incoming "opdata" */ struct im_connection *check_imc(void *opdata, const char *accountname, const char *protocol); /* determine the nick for a given handle/protocol pair returns "handle/protocol" if not found */ const char *peernick(irc_t *irc, const char *handle, const char *protocol); /* turn a hexadecimal digit into its numerical value */ int hexval(char a); /* determine the irc_user_t for a given handle/protocol pair returns NULL if not found */ irc_user_t *peeruser(irc_t *irc, const char *handle, const char *protocol); /* handle SMP TLVs from a received message */ void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs); /* combined handler for the 'otr smp' and 'otr smpq' commands */ void otr_smp_or_smpq(irc_t *irc, const char *nick, const char *question, const char *secret); /* update flags within the irc_user structure to reflect OTR status of context */ void otr_update_uflags(ConnContext *context, irc_user_t *u); /* update op/voice flag of given user according to encryption state and settings returns 0 if neither op_buddies nor voice_buddies is set to "encrypted", i.e. msgstate should be announced seperately */ int otr_update_modeflags(irc_t *irc, irc_user_t *u); /* show general info about the OTR subsystem; called by 'otr info' */ void show_general_otr_info(irc_t *irc); /* show info about a given OTR context */ void show_otr_context_info(irc_t *irc, ConnContext *ctx); /* show the list of fingerprints associated with a given context */ void show_fingerprints(irc_t *irc, ConnContext *ctx); /* find a fingerprint by prefix (given as any number of hex strings) */ Fingerprint *match_fingerprint(irc_t *irc, ConnContext *ctx, const char **args); /* find a private key by fingerprint prefix (given as any number of hex strings) */ OtrlPrivKey *match_privkey(irc_t *irc, const char **args); /* check whether a string is safe to use in a path component */ int strsane(const char *s); /* functions to be called for certain events */ static const struct irc_plugin otr_plugin; /*** routines declared in otr.h: ***/ #ifdef OTR_BI #define init_plugin otr_init #endif void init_plugin(void) { OTRL_INIT; /* fill global OtrlMessageAppOps */ otr_ops.policy = &op_policy; otr_ops.create_privkey = &op_create_privkey; otr_ops.is_logged_in = &op_is_logged_in; otr_ops.inject_message = &op_inject_message; otr_ops.notify = NULL; otr_ops.display_otr_message = &op_display_otr_message; otr_ops.update_context_list = NULL; otr_ops.protocol_name = NULL; otr_ops.protocol_name_free = NULL; otr_ops.new_fingerprint = &op_new_fingerprint; otr_ops.write_fingerprints = &op_write_fingerprints; otr_ops.gone_secure = &op_gone_secure; otr_ops.gone_insecure = &op_gone_insecure; otr_ops.still_secure = &op_still_secure; otr_ops.log_message = &op_log_message; otr_ops.max_message_size = &op_max_message_size; otr_ops.account_name = &op_account_name; otr_ops.account_name_free = NULL; root_command_add( "otr", 1, cmd_otr, 0 ); register_irc_plugin( &otr_plugin ); } gboolean otr_irc_new(irc_t *irc) { set_t *s; GSList *l; irc->otr = g_new0(otr_t, 1); irc->otr->us = otrl_userstate_create(); s = set_add( &irc->b->set, "otr_color_encrypted", "true", set_eval_bool, irc ); s = set_add( &irc->b->set, "otr_policy", "opportunistic", set_eval_list, irc ); l = g_slist_prepend( NULL, "never" ); l = g_slist_prepend( l, "opportunistic" ); l = g_slist_prepend( l, "manual" ); l = g_slist_prepend( l, "always" ); s->eval_data = l; s = set_add( &irc->b->set, "otr_does_html", "true", set_eval_bool, irc ); return TRUE; } void otr_irc_free(irc_t *irc) { otr_t *otr = irc->otr; otrl_userstate_free(otr->us); if(otr->keygen) { kill(otr->keygen, SIGTERM); waitpid(otr->keygen, NULL, 0); /* TODO: remove stale keygen tempfiles */ } if(otr->to) fclose(otr->to); if(otr->from) fclose(otr->from); while(otr->todo) { kg_t *p = otr->todo; otr->todo = p->next; g_free(p); } g_free(otr); } void otr_load(irc_t *irc) { char s[512]; account_t *a; gcry_error_t e; gcry_error_t enoent = gcry_error_from_errno(ENOENT); int kg=0; if(strsane(irc->user->nick)) { g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, irc->user->nick); e = otrl_privkey_read(irc->otr->us, s); if(e && e!=enoent) { irc_rootmsg(irc, "otr load: %s: %s", s, gcry_strerror(e)); } g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, irc->user->nick); e = otrl_privkey_read_fingerprints(irc->otr->us, s, NULL, NULL); if(e && e!=enoent) { irc_rootmsg(irc, "otr load: %s: %s", s, gcry_strerror(e)); } } /* check for otr keys on all accounts */ for(a=irc->b->accounts; a; a=a->next) { kg = otr_check_for_key(a) || kg; } if(kg) { irc_rootmsg(irc, "Notice: " "The accounts above do not have OTR encryption keys associated with them, yet. " "These keys are now being generated in the background. " "You will be notified as they are completed. " "It is not necessary to wait; " "BitlBee can be used normally during key generation. " "You may safely ignore this message if you don't know what OTR is. ;)"); } } void otr_save(irc_t *irc) { char s[512]; gcry_error_t e; if(strsane(irc->user->nick)) { g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, irc->user->nick); e = otrl_privkey_write_fingerprints(irc->otr->us, s); if(e) { irc_rootmsg(irc, "otr save: %s: %s", s, gcry_strerror(e)); } chmod(s, 0600); } } void otr_remove(const char *nick) { char s[512]; if(strsane(nick)) { g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, nick); unlink(s); g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, nick); unlink(s); } } void otr_rename(const char *onick, const char *nnick) { char s[512], t[512]; if(strsane(nnick) && strsane(onick)) { g_snprintf(s, 511, "%s%s.otr_keys", global.conf->configdir, onick); g_snprintf(t, 511, "%s%s.otr_keys", global.conf->configdir, nnick); rename(s,t); g_snprintf(s, 511, "%s%s.otr_fprints", global.conf->configdir, onick); g_snprintf(t, 511, "%s%s.otr_fprints", global.conf->configdir, nnick); rename(s,t); } } int otr_check_for_key(account_t *a) { irc_t *irc = a->bee->ui_data; OtrlPrivKey *k; /* don't do OTR on certain (not classic IM) protocols, e.g. twitter */ if(a->prpl->options & OPT_NOOTR) { return 0; } k = otrl_privkey_find(irc->otr->us, a->user, a->prpl->name); if(k) { irc_rootmsg(irc, "otr: %s/%s ready", a->user, a->prpl->name); return 0; } if(keygen_in_progress(irc, a->user, a->prpl->name)) { irc_rootmsg(irc, "otr: keygen for %s/%s already in progress", a->user, a->prpl->name); return 0; } else { irc_rootmsg(irc, "otr: starting background keygen for %s/%s", a->user, a->prpl->name); otr_keygen(irc, a->user, a->prpl->name); return 1; } } char *otr_filter_msg_in(irc_user_t *iu, char *msg, int flags) { int ignore_msg; char *newmsg = NULL; OtrlTLV *tlvs = NULL; irc_t *irc = iu->irc; struct im_connection *ic = iu->bu->ic; /* don't do OTR on certain (not classic IM) protocols, e.g. twitter */ if(ic->acc->prpl->options & OPT_NOOTR) { return msg; } ignore_msg = otrl_message_receiving(irc->otr->us, &otr_ops, ic, ic->acc->user, ic->acc->prpl->name, iu->bu->handle, msg, &newmsg, &tlvs, NULL, NULL); otr_handle_smp(ic, iu->bu->handle, tlvs); if(ignore_msg) { /* this was an internal OTR protocol message */ return NULL; } else if(!newmsg) { /* this was a non-OTR message */ return msg; } else { /* OTR has processed this message */ ConnContext *context = otrl_context_find(irc->otr->us, iu->bu->handle, ic->acc->user, ic->acc->prpl->name, 0, NULL, NULL, NULL); /* we're done with the original msg, which will be caller-freed. */ /* NB: must not change the newmsg pointer, since we free it. */ msg = newmsg; if(context && context->msgstate == OTRL_MSGSTATE_ENCRYPTED) { /* HTML decoding */ /* perform any necessary stripping that the top level would miss */ if(set_getbool(&ic->bee->set, "otr_does_html") && !(ic->flags & OPT_DOES_HTML) && set_getbool(&ic->bee->set, "strip_html")) { strip_html(msg); } /* coloring */ if(set_getbool(&ic->bee->set, "otr_color_encrypted")) { int color; /* color according to f'print trust */ char *pre="", *sep=""; /* optional parts */ const char *trust = context->active_fingerprint->trust; if(trust && trust[0] != '\0') color=3; /* green */ else color=5; /* red */ /* in a query window, keep "/me " uncolored at the beginning */ if(g_strncasecmp(msg, "/me ", 4) == 0 && irc_user_msgdest(iu) == irc->user->nick) { msg += 4; /* skip */ pre = "/me "; } /* comma in first place could mess with the color code */ if(msg[0] == ',') { /* insert a space between color spec and message */ sep = " "; } msg = g_strdup_printf("%s\x03%.2d%s%s\x0F", pre, color, sep, msg); } } if(msg == newmsg) { msg = g_strdup(newmsg); } otrl_message_free(newmsg); return msg; } } char *otr_filter_msg_out(irc_user_t *iu, char *msg, int flags) { int st; char *otrmsg = NULL; char *emsg = msg; /* the message as we hand it to libotr */ ConnContext *ctx = NULL; irc_t *irc = iu->irc; struct im_connection *ic = iu->bu->ic; /* don't do OTR on certain (not classic IM) protocols, e.g. twitter */ if(ic->acc->prpl->options & OPT_NOOTR) { return msg; } ctx = otrl_context_find(irc->otr->us, iu->bu->handle, ic->acc->user, ic->acc->prpl->name, 1, NULL, NULL, NULL); /* HTML encoding */ /* consider OTR plaintext to be HTML if otr_does_html is set */ if(ctx && ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED && set_getbool(&ic->bee->set, "otr_does_html") && (g_strncasecmp(msg, "", 6) != 0)) { emsg = escape_html(msg); } st = otrl_message_sending(irc->otr->us, &otr_ops, ic, ic->acc->user, ic->acc->prpl->name, iu->bu->handle, emsg, NULL, &otrmsg, NULL, NULL); if(emsg != msg) { g_free(emsg); /* we're done with this one */ } if(st) { return NULL; } if(otrmsg) { if(!ctx) { otrl_message_free(otrmsg); return NULL; } st = otrl_message_fragment_and_send(&otr_ops, ic, ctx, otrmsg, OTRL_FRAGMENT_SEND_ALL, NULL); otrl_message_free(otrmsg); } else { /* note: otrl_message_sending handles policy, so that if REQUIRE_ENCRYPTION is set, this case does not occur */ return msg; } /* TODO: Error reporting should be done here now (if st!=0), probably. */ return NULL; } static const struct irc_plugin otr_plugin = { otr_irc_new, otr_irc_free, otr_filter_msg_out, otr_filter_msg_in, otr_load, otr_save, otr_remove, }; static void cmd_otr(irc_t *irc, char **args) { const command_t *cmd; if(!args[0]) return; if(!args[1]) return; for(cmd=otr_commands; cmd->command; cmd++) { if(strcmp(cmd->command, args[1]) == 0) break; } if(!cmd->command) { irc_rootmsg(irc, "%s: unknown subcommand \"%s\", see \x02help otr\x02", args[0], args[1]); return; } if(!args[cmd->required_parameters+1]) { irc_rootmsg(irc, "%s %s: not enough arguments (%d req.)", args[0], args[1], cmd->required_parameters); return; } cmd->execute(irc, args+1); } /*** OTR "MessageAppOps" callbacks for global.otr_ui: ***/ OtrlPolicy op_policy(void *opdata, ConnContext *context) { struct im_connection *ic = check_imc(opdata, context->accountname, context->protocol); irc_t *irc = ic->bee->ui_data; const char *p; /* policy override during keygen: if we're missing the key for context but are currently generating it, then that's as much as we can do. => temporarily return NEVER. */ if(keygen_in_progress(irc, context->accountname, context->protocol) && !otrl_privkey_find(irc->otr->us, context->accountname, context->protocol)) return OTRL_POLICY_NEVER; p = set_getstr(&ic->bee->set, "otr_policy"); if(!strcmp(p, "never")) return OTRL_POLICY_NEVER; if(!strcmp(p, "opportunistic")) return OTRL_POLICY_OPPORTUNISTIC; if(!strcmp(p, "manual")) return OTRL_POLICY_MANUAL; if(!strcmp(p, "always")) return OTRL_POLICY_ALWAYS; return OTRL_POLICY_OPPORTUNISTIC; } void op_create_privkey(void *opdata, const char *accountname, const char *protocol) { struct im_connection *ic = check_imc(opdata, accountname, protocol); irc_t *irc = ic->bee->ui_data; /* will fail silently if keygen already in progress */ otr_keygen(irc, accountname, protocol); } int op_is_logged_in(void *opdata, const char *accountname, const char *protocol, const char *recipient) { struct im_connection *ic = check_imc(opdata, accountname, protocol); bee_user_t *bu; /* lookup the irc_user_t for the given recipient */ bu = bee_user_by_handle(ic->bee, ic, recipient); if(bu) { if(bu->flags & BEE_USER_ONLINE) return 1; else return 0; } else { return -1; } } void op_inject_message(void *opdata, const char *accountname, const char *protocol, const char *recipient, const char *message) { struct im_connection *ic = check_imc(opdata, accountname, protocol); irc_t *irc = ic->bee->ui_data; if (strcmp(accountname, recipient) == 0) { /* huh? injecting messages to myself? */ irc_rootmsg(irc, "note to self: %s", message); } else { /* need to drop some consts here :-( */ /* TODO: get flags into op_inject_message?! */ ic->acc->prpl->buddy_msg(ic, (char *)recipient, (char *)message, 0); /* ignoring return value :-/ */ } } int op_display_otr_message(void *opdata, const char *accountname, const char *protocol, const char *username, const char *message) { struct im_connection *ic = check_imc(opdata, accountname, protocol); char *msg = g_strdup(message); irc_t *irc = ic->bee->ui_data; irc_user_t *u = peeruser(irc, username, protocol); strip_html(msg); if(u) { /* display as a notice from this particular user */ irc_usernotice(u, "%s", msg); } else { irc_rootmsg(irc, "[otr] %s", msg); } g_free(msg); return 0; } void op_new_fingerprint(void *opdata, OtrlUserState us, const char *accountname, const char *protocol, const char *username, unsigned char fingerprint[20]) { struct im_connection *ic = check_imc(opdata, accountname, protocol); irc_t *irc = ic->bee->ui_data; irc_user_t *u = peeruser(irc, username, protocol); char hunam[45]; /* anybody looking? ;-) */ otrl_privkey_hash_to_human(hunam, fingerprint); if(u) { irc_usernotice(u, "new fingerprint: %s", hunam); } else { /* this case shouldn't normally happen */ irc_rootmsg(irc, "new fingerprint for %s/%s: %s", username, protocol, hunam); } } void op_write_fingerprints(void *opdata) { struct im_connection *ic = (struct im_connection *)opdata; irc_t *irc = ic->bee->ui_data; otr_save(irc); } void op_gone_secure(void *opdata, ConnContext *context) { struct im_connection *ic = check_imc(opdata, context->accountname, context->protocol); irc_user_t *u; irc_t *irc = ic->bee->ui_data; u = peeruser(irc, context->username, context->protocol); if(!u) { log_message(LOGLVL_ERROR, "BUG: otr.c: op_gone_secure: irc_user_t for %s/%s/%s not found!", context->username, context->protocol, context->accountname); return; } otr_update_uflags(context, u); if(!otr_update_modeflags(irc, u)) { char *trust = u->flags & IRC_USER_OTR_TRUSTED ? "trusted" : "untrusted!"; irc_usernotice(u, "conversation is now off the record (%s)", trust); } } void op_gone_insecure(void *opdata, ConnContext *context) { struct im_connection *ic = check_imc(opdata, context->accountname, context->protocol); irc_t *irc = ic->bee->ui_data; irc_user_t *u; u = peeruser(irc, context->username, context->protocol); if(!u) { log_message(LOGLVL_ERROR, "BUG: otr.c: op_gone_insecure: irc_user_t for %s/%s/%s not found!", context->username, context->protocol, context->accountname); return; } otr_update_uflags(context, u); if(!otr_update_modeflags(irc, u)) irc_usernotice(u, "conversation is now in cleartext"); } void op_still_secure(void *opdata, ConnContext *context, int is_reply) { struct im_connection *ic = check_imc(opdata, context->accountname, context->protocol); irc_t *irc = ic->bee->ui_data; irc_user_t *u; u = peeruser(irc, context->username, context->protocol); if(!u) { log_message(LOGLVL_ERROR, "BUG: otr.c: op_still_secure: irc_user_t for %s/%s/%s not found!", context->username, context->protocol, context->accountname); return; } otr_update_uflags(context, u); if(!otr_update_modeflags(irc, u)) { char *trust = u->flags & IRC_USER_OTR_TRUSTED ? "trusted" : "untrusted!"; irc_usernotice(u, "otr connection has been refreshed (%s)", trust); } } void op_log_message(void *opdata, const char *message) { char *msg = g_strdup(message); strip_html(msg); log_message(LOGLVL_INFO, "otr: %s", msg); g_free(msg); } int op_max_message_size(void *opdata, ConnContext *context) { struct im_connection *ic = check_imc(opdata, context->accountname, context->protocol); return ic->acc->prpl->mms; } const char *op_account_name(void *opdata, const char *account, const char *protocol) { struct im_connection *ic = (struct im_connection *)opdata; irc_t *irc = ic->bee->ui_data; return peernick(irc, account, protocol); } /*** OTR sub-command handlers ***/ void cmd_otr_reconnect(irc_t *irc, char **args) { cmd_otr_disconnect(irc, args); cmd_otr_connect(irc, args); } void cmd_otr_disconnect(irc_t *irc, char **args) { irc_user_t *u; u = irc_user_by_name(irc, args[1]); if(!u || !u->bu || !u->bu->ic) { irc_rootmsg(irc, "%s: unknown user", args[1]); return; } otrl_message_disconnect(irc->otr->us, &otr_ops, u->bu->ic, u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, u->bu->handle); /* for some reason, libotr (3.1.0) doesn't do this itself: */ if(u->flags & IRC_USER_OTR_ENCRYPTED) { ConnContext *ctx; ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); if(ctx) op_gone_insecure(u->bu->ic, ctx); else /* huh? */ u->flags &= ( IRC_USER_OTR_ENCRYPTED | IRC_USER_OTR_TRUSTED ); } } void cmd_otr_connect(irc_t *irc, char **args) { irc_user_t *u; u = irc_user_by_name(irc, args[1]); if(!u || !u->bu || !u->bu->ic) { irc_rootmsg(irc, "%s: unknown user", args[1]); return; } if(!(u->bu->flags & BEE_USER_ONLINE)) { irc_rootmsg(irc, "%s is offline", args[1]); return; } bee_user_msg(irc->b, u->bu, "?OTR?v2?", 0); } void cmd_otr_smp(irc_t *irc, char **args) { otr_smp_or_smpq(irc, args[1], NULL, args[2]); /* no question */ } void cmd_otr_smpq(irc_t *irc, char **args) { otr_smp_or_smpq(irc, args[1], args[2], args[3]); } void cmd_otr_trust(irc_t *irc, char **args) { irc_user_t *u; ConnContext *ctx; unsigned char raw[20]; Fingerprint *fp; int i,j; u = irc_user_by_name(irc, args[1]); if(!u || !u->bu || !u->bu->ic) { irc_rootmsg(irc, "%s: unknown user", args[1]); return; } ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); if(!ctx) { irc_rootmsg(irc, "%s: no otr context with user", args[1]); return; } /* convert given fingerprint to raw representation */ for(i=0; i<5; i++) { for(j=0; j<4; j++) { char *p = args[2+i]+(2*j); char *q = p+1; int x, y; if(!*p || !*q) { irc_rootmsg(irc, "failed: truncated fingerprint block %d", i+1); return; } x = hexval(*p); y = hexval(*q); if(x<0) { irc_rootmsg(irc, "failed: %d. hex digit of block %d out of range", 2*j+1, i+1); return; } if(y<0) { irc_rootmsg(irc, "failed: %d. hex digit of block %d out of range", 2*j+2, i+1); return; } raw[i*4+j] = x*16 + y; } } fp = otrl_context_find_fingerprint(ctx, raw, 0, NULL); if(!fp) { irc_rootmsg(irc, "failed: no such fingerprint for %s", args[1]); } else { char *trust = args[7] ? args[7] : "affirmed"; otrl_context_set_trust(fp, trust); irc_rootmsg(irc, "fingerprint match, trust set to \"%s\"", trust); if(u->flags & IRC_USER_OTR_ENCRYPTED) u->flags |= IRC_USER_OTR_TRUSTED; otr_update_modeflags(irc, u); } } void cmd_otr_info(irc_t *irc, char **args) { if(!args[1]) { show_general_otr_info(irc); } else { char *arg = g_strdup(args[1]); char *myhandle, *handle=NULL, *protocol; ConnContext *ctx; /* interpret arg as 'user/protocol/account' if possible */ protocol = strchr(arg, '/'); myhandle = NULL; if(protocol) { *(protocol++) = '\0'; myhandle = strchr(protocol, '/'); } if(protocol && myhandle) { *(myhandle++) = '\0'; handle = arg; ctx = otrl_context_find(irc->otr->us, handle, myhandle, protocol, 0, NULL, NULL, NULL); if(!ctx) { irc_rootmsg(irc, "no such context"); g_free(arg); return; } } else { irc_user_t *u = irc_user_by_name(irc, args[1]); if(!u || !u->bu || !u->bu->ic) { irc_rootmsg(irc, "%s: unknown user", args[1]); g_free(arg); return; } ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); if(!ctx) { irc_rootmsg(irc, "no otr context with %s", args[1]); g_free(arg); return; } } /* show how we resolved the (nick) argument, if we did */ if(handle!=arg) { irc_rootmsg(irc, "%s is %s/%s; we are %s/%s to them", args[1], ctx->username, ctx->protocol, ctx->accountname, ctx->protocol); } show_otr_context_info(irc, ctx); g_free(arg); } } void cmd_otr_keygen(irc_t *irc, char **args) { int i, n; account_t *a; n = atoi(args[1]); if(n<0 || (!n && strcmp(args[1], "0"))) { irc_rootmsg(irc, "%s: invalid account number", args[1]); return; } a = irc->b->accounts; for(i=0; inext); if(!a) { irc_rootmsg(irc, "%s: no such account", args[1]); return; } if(keygen_in_progress(irc, a->user, a->prpl->name)) { irc_rootmsg(irc, "keygen for account %d already in progress", n); return; } if(otrl_privkey_find(irc->otr->us, a->user, a->prpl->name)) { char *s = g_strdup_printf("account %d already has a key, replace it?", n); query_add(irc, NULL, s, yes_keygen, NULL, NULL, a); g_free(s); } else { otr_keygen(irc, a->user, a->prpl->name); } } void yes_forget_fingerprint(void *data) { pair_t *p = (pair_t *)data; irc_t *irc = (irc_t *)p->fst; Fingerprint *fp = (Fingerprint *)p->snd; g_free(p); if(fp == fp->context->active_fingerprint) { irc_rootmsg(irc, "that fingerprint is active, terminate otr connection first"); return; } otrl_context_forget_fingerprint(fp, 0); } void yes_forget_context(void *data) { pair_t *p = (pair_t *)data; irc_t *irc = (irc_t *)p->fst; ConnContext *ctx = (ConnContext *)p->snd; g_free(p); if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { irc_rootmsg(irc, "active otr connection with %s, terminate it first", peernick(irc, ctx->username, ctx->protocol)); return; } if(ctx->msgstate == OTRL_MSGSTATE_FINISHED) otrl_context_force_plaintext(ctx); otrl_context_forget(ctx); } void yes_forget_key(void *data) { OtrlPrivKey *key = (OtrlPrivKey *)data; otrl_privkey_forget(key); /* Hm, libotr doesn't seem to offer a function for explicitly /writing/ keyfiles. So the key will be back on the next load... */ /* TODO: Actually erase forgotten keys from storage? */ } void cmd_otr_forget(irc_t *irc, char **args) { if(!strcmp(args[1], "fingerprint")) { irc_user_t *u; ConnContext *ctx; Fingerprint *fp; char human[54]; char *s; pair_t *p; if(!args[3]) { irc_rootmsg(irc, "otr %s %s: not enough arguments (2 req.)", args[0], args[1]); return; } /* TODO: allow context specs ("user/proto/account") in 'otr forget fingerprint'? */ u = irc_user_by_name(irc, args[2]); if(!u || !u->bu || !u->bu->ic) { irc_rootmsg(irc, "%s: unknown user", args[2]); return; } ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); if(!ctx) { irc_rootmsg(irc, "no otr context with %s", args[2]); return; } fp = match_fingerprint(irc, ctx, ((const char **)args)+3); if(!fp) { /* match_fingerprint does error messages */ return; } if(fp == ctx->active_fingerprint) { irc_rootmsg(irc, "that fingerprint is active, terminate otr connection first"); return; } otrl_privkey_hash_to_human(human, fp->fingerprint); s = g_strdup_printf("about to forget fingerprint %s, are you sure?", human); p = g_malloc(sizeof(pair_t)); if(!p) return; p->fst = irc; p->snd = fp; query_add(irc, NULL, s, yes_forget_fingerprint, NULL, NULL, p); g_free(s); } else if(!strcmp(args[1], "context")) { irc_user_t *u; ConnContext *ctx; char *s; pair_t *p; /* TODO: allow context specs ("user/proto/account") in 'otr forget contex'? */ u = irc_user_by_name(irc, args[2]); if(!u || !u->bu || !u->bu->ic) { irc_rootmsg(irc, "%s: unknown user", args[2]); return; } ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); if(!ctx) { irc_rootmsg(irc, "no otr context with %s", args[2]); return; } if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { irc_rootmsg(irc, "active otr connection with %s, terminate it first", args[2]); return; } s = g_strdup_printf("about to forget otr data about %s, are you sure?", args[2]); p = g_malloc(sizeof(pair_t)); if(!p) return; p->fst = irc; p->snd = ctx; query_add(irc, NULL, s, yes_forget_context, NULL, NULL, p); g_free(s); } else if(!strcmp(args[1], "key")) { OtrlPrivKey *key; char *s; key = match_privkey(irc, ((const char **)args)+2); if(!key) { /* match_privkey does error messages */ return; } s = g_strdup_printf("about to forget the private key for %s/%s, are you sure?", key->accountname, key->protocol); query_add(irc, NULL, s, yes_forget_key, NULL, NULL, key); g_free(s); } else { irc_rootmsg(irc, "otr %s: unknown subcommand \"%s\", see \x02help otr forget\x02", args[0], args[1]); } } /*** local helpers / subroutines: ***/ /* Socialist Millionaires' Protocol */ void otr_handle_smp(struct im_connection *ic, const char *handle, OtrlTLV *tlvs) { irc_t *irc = ic->bee->ui_data; OtrlUserState us = irc->otr->us; OtrlMessageAppOps *ops = &otr_ops; OtrlTLV *tlv = NULL; ConnContext *context; NextExpectedSMP nextMsg; irc_user_t *u; bee_user_t *bu; bu = bee_user_by_handle(ic->bee, ic, handle); if(!bu || !(u = bu->ui_data)) return; context = otrl_context_find(us, handle, ic->acc->user, ic->acc->prpl->name, 1, NULL, NULL, NULL); if(!context) { /* huh? out of memory or what? */ irc_rootmsg(irc, "smp: failed to get otr context for %s", u->nick); otrl_message_abort_smp(us, ops, u->bu->ic, context); otrl_sm_state_free(context->smstate); return; } nextMsg = context->smstate->nextExpected; if (context->smstate->sm_prog_state == OTRL_SMP_PROG_CHEATED) { irc_rootmsg(irc, "smp %s: opponent violated protocol, aborting", u->nick); otrl_message_abort_smp(us, ops, u->bu->ic, context); otrl_sm_state_free(context->smstate); return; } tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1Q); if (tlv) { if (nextMsg != OTRL_SMP_EXPECT1) { irc_rootmsg(irc, "smp %s: spurious SMP1Q received, aborting", u->nick); otrl_message_abort_smp(us, ops, u->bu->ic, context); otrl_sm_state_free(context->smstate); } else { char *question = g_strndup((char *)tlv->data, tlv->len); irc_rootmsg(irc, "smp: initiated by %s with question: \x02\"%s\"\x02", u->nick, question); irc_rootmsg(irc, "smp: respond with \x02otr smp %s \x02", u->nick); g_free(question); /* smp stays in EXPECT1 until user responds */ } } tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1); if (tlv) { if (nextMsg != OTRL_SMP_EXPECT1) { irc_rootmsg(irc, "smp %s: spurious SMP1 received, aborting", u->nick); otrl_message_abort_smp(us, ops, u->bu->ic, context); otrl_sm_state_free(context->smstate); } else { irc_rootmsg(irc, "smp: initiated by %s" " - respond with \x02otr smp %s \x02", u->nick, u->nick); /* smp stays in EXPECT1 until user responds */ } } tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2); if (tlv) { if (nextMsg != OTRL_SMP_EXPECT2) { irc_rootmsg(irc, "smp %s: spurious SMP2 received, aborting", u->nick); otrl_message_abort_smp(us, ops, u->bu->ic, context); otrl_sm_state_free(context->smstate); } else { /* SMP2 received, otrl_message_receiving will have sent SMP3 */ context->smstate->nextExpected = OTRL_SMP_EXPECT4; } } tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3); if (tlv) { if (nextMsg != OTRL_SMP_EXPECT3) { irc_rootmsg(irc, "smp %s: spurious SMP3 received, aborting", u->nick); otrl_message_abort_smp(us, ops, u->bu->ic, context); otrl_sm_state_free(context->smstate); } else { /* SMP3 received, otrl_message_receiving will have sent SMP4 */ if(context->smstate->sm_prog_state == OTRL_SMP_PROG_SUCCEEDED) { if(context->smstate->received_question) { irc_rootmsg(irc, "smp %s: correct answer, you are trusted", u->nick); } else { irc_rootmsg(irc, "smp %s: secrets proved equal, fingerprint trusted", u->nick); } } else { if(context->smstate->received_question) { irc_rootmsg(irc, "smp %s: wrong answer, you are not trusted", u->nick); } else { irc_rootmsg(irc, "smp %s: secrets did not match, fingerprint not trusted", u->nick); } } otrl_sm_state_free(context->smstate); /* smp is in back in EXPECT1 */ } } tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4); if (tlv) { if (nextMsg != OTRL_SMP_EXPECT4) { irc_rootmsg(irc, "smp %s: spurious SMP4 received, aborting", u->nick); otrl_message_abort_smp(us, ops, u->bu->ic, context); otrl_sm_state_free(context->smstate); } else { /* SMP4 received, otrl_message_receiving will have set fp trust */ if(context->smstate->sm_prog_state == OTRL_SMP_PROG_SUCCEEDED) { irc_rootmsg(irc, "smp %s: secrets proved equal, fingerprint trusted", u->nick); } else { irc_rootmsg(irc, "smp %s: secrets did not match, fingerprint not trusted", u->nick); } otrl_sm_state_free(context->smstate); /* smp is in back in EXPECT1 */ } } tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT); if (tlv) { irc_rootmsg(irc, "smp: received abort from %s", u->nick); otrl_sm_state_free(context->smstate); /* smp is in back in EXPECT1 */ } } /* combined handler for the 'otr smp' and 'otr smpq' commands */ void otr_smp_or_smpq(irc_t *irc, const char *nick, const char *question, const char *secret) { irc_user_t *u; ConnContext *ctx; u = irc_user_by_name(irc, nick); if(!u || !u->bu || !u->bu->ic) { irc_rootmsg(irc, "%s: unknown user", nick); return; } if(!(u->bu->flags & BEE_USER_ONLINE)) { irc_rootmsg(irc, "%s is offline", nick); return; } ctx = otrl_context_find(irc->otr->us, u->bu->handle, u->bu->ic->acc->user, u->bu->ic->acc->prpl->name, 0, NULL, NULL, NULL); if(!ctx || ctx->msgstate != OTRL_MSGSTATE_ENCRYPTED) { irc_rootmsg(irc, "smp: otr inactive with %s, try \x02otr connect" " %s\x02", nick, nick); return; } if(ctx->smstate->nextExpected != OTRL_SMP_EXPECT1) { log_message(LOGLVL_INFO, "SMP already in phase %d, sending abort before reinitiating", ctx->smstate->nextExpected+1); otrl_message_abort_smp(irc->otr->us, &otr_ops, u->bu->ic, ctx); otrl_sm_state_free(ctx->smstate); } if(question) { /* this was 'otr smpq', just initiate */ irc_rootmsg(irc, "smp: initiating with %s...", u->nick); otrl_message_initiate_smp_q(irc->otr->us, &otr_ops, u->bu->ic, ctx, question, (unsigned char *)secret, strlen(secret)); /* smp is now in EXPECT2 */ } else { /* this was 'otr smp', initiate or reply */ /* warning: the following assumes that smstates are cleared whenever an SMP is completed or aborted! */ if(ctx->smstate->secret == NULL) { irc_rootmsg(irc, "smp: initiating with %s...", u->nick); otrl_message_initiate_smp(irc->otr->us, &otr_ops, u->bu->ic, ctx, (unsigned char *)secret, strlen(secret)); /* smp is now in EXPECT2 */ } else { /* if we're still in EXPECT1 but smstate is initialized, we must have received the SMP1, so let's issue a response */ irc_rootmsg(irc, "smp: responding to %s...", u->nick); otrl_message_respond_smp(irc->otr->us, &otr_ops, u->bu->ic, ctx, (unsigned char *)secret, strlen(secret)); /* smp is now in EXPECT3 */ } } } /* helper to assert that account and protocol names given to ops below always match the im_connection passed through as opdata */ struct im_connection *check_imc(void *opdata, const char *accountname, const char *protocol) { struct im_connection *ic = (struct im_connection *)opdata; if (strcmp(accountname, ic->acc->user) != 0) { log_message(LOGLVL_WARNING, "otr: internal account name mismatch: '%s' vs '%s'", accountname, ic->acc->user); } if (strcmp(protocol, ic->acc->prpl->name) != 0) { log_message(LOGLVL_WARNING, "otr: internal protocol name mismatch: '%s' vs '%s'", protocol, ic->acc->prpl->name); } return ic; } irc_user_t *peeruser(irc_t *irc, const char *handle, const char *protocol) { GSList *l; for(l=irc->b->users; l; l = l->next) { bee_user_t *bu = l->data; struct prpl *prpl; if(!bu->ui_data || !bu->ic || !bu->handle) continue; prpl = bu->ic->acc->prpl; if(strcmp(prpl->name, protocol) == 0 && prpl->handle_cmp(bu->handle, handle) == 0) { return bu->ui_data; } } return NULL; } int hexval(char a) { int x=tolower(a); if(x>='a' && x<='f') x = x - 'a' + 10; else if(x>='0' && x<='9') x = x - '0'; else return -1; return x; } const char *peernick(irc_t *irc, const char *handle, const char *protocol) { static char fallback[512]; irc_user_t *u = peeruser(irc, handle, protocol); if(u) { return u->nick; } else { g_snprintf(fallback, 511, "%s/%s", handle, protocol); return fallback; } } void otr_update_uflags(ConnContext *context, irc_user_t *u) { const char *trust; if(context->active_fingerprint) { u->flags |= IRC_USER_OTR_ENCRYPTED; trust = context->active_fingerprint->trust; if(trust && trust[0]) u->flags |= IRC_USER_OTR_TRUSTED; else u->flags &= ~IRC_USER_OTR_TRUSTED; } else { u->flags &= ~IRC_USER_OTR_ENCRYPTED; } } int otr_update_modeflags(irc_t *irc, irc_user_t *u) { return 0; } void show_fingerprints(irc_t *irc, ConnContext *ctx) { char human[45]; Fingerprint *fp; const char *trust; int count=0; for(fp=&ctx->fingerprint_root; fp; fp=fp->next) { if(!fp->fingerprint) continue; count++; otrl_privkey_hash_to_human(human, fp->fingerprint); if(!fp->trust || fp->trust[0] == '\0') { trust="untrusted"; } else { trust=fp->trust; } if(fp == ctx->active_fingerprint) { irc_rootmsg(irc, " \x02%s (%s)\x02", human, trust); } else { irc_rootmsg(irc, " %s (%s)", human, trust); } } if(count==0) irc_rootmsg(irc, " (none)"); } Fingerprint *match_fingerprint(irc_t *irc, ConnContext *ctx, const char **args) { Fingerprint *fp, *fp2; char human[45]; char prefix[45], *p; int n; int i,j; /* assemble the args into a prefix in standard "human" form */ n=0; p=prefix; for(i=0; args[i]; i++) { for(j=0; args[i][j]; j++) { char c = toupper(args[i][j]); if(n>=40) { irc_rootmsg(irc, "too many fingerprint digits given, expected at most 40"); return NULL; } if( (c>='A' && c<='F') || (c>='0' && c<='9') ) { *(p++) = c; } else { irc_rootmsg(irc, "invalid hex digit '%c' in block %d", args[i][j], i+1); return NULL; } n++; if(n%8 == 0) *(p++) = ' '; } } *p = '\0'; /* find first fingerprint with the given prefix */ n = strlen(prefix); for(fp=&ctx->fingerprint_root; fp; fp=fp->next) { if(!fp->fingerprint) continue; otrl_privkey_hash_to_human(human, fp->fingerprint); if(!strncmp(prefix, human, n)) break; } if(!fp) { irc_rootmsg(irc, "%s: no match", prefix); return NULL; } /* make sure the match, if any, is unique */ for(fp2=fp->next; fp2; fp2=fp2->next) { if(!fp2->fingerprint) continue; otrl_privkey_hash_to_human(human, fp2->fingerprint); if(!strncmp(prefix, human, n)) break; } if(fp2) { irc_rootmsg(irc, "%s: multiple matches", prefix); return NULL; } return fp; } OtrlPrivKey *match_privkey(irc_t *irc, const char **args) { OtrlPrivKey *k, *k2; char human[45]; char prefix[45], *p; int n; int i,j; /* assemble the args into a prefix in standard "human" form */ n=0; p=prefix; for(i=0; args[i]; i++) { for(j=0; args[i][j]; j++) { char c = toupper(args[i][j]); if(n>=40) { irc_rootmsg(irc, "too many fingerprint digits given, expected at most 40"); return NULL; } if( (c>='A' && c<='F') || (c>='0' && c<='9') ) { *(p++) = c; } else { irc_rootmsg(irc, "invalid hex digit '%c' in block %d", args[i][j], i+1); return NULL; } n++; if(n%8 == 0) *(p++) = ' '; } } *p = '\0'; /* find first key which matches the given prefix */ n = strlen(prefix); for(k=irc->otr->us->privkey_root; k; k=k->next) { p = otrl_privkey_fingerprint(irc->otr->us, human, k->accountname, k->protocol); if(!p) /* gah! :-P */ continue; if(!strncmp(prefix, human, n)) break; } if(!k) { irc_rootmsg(irc, "%s: no match", prefix); return NULL; } /* make sure the match, if any, is unique */ for(k2=k->next; k2; k2=k2->next) { p = otrl_privkey_fingerprint(irc->otr->us, human, k2->accountname, k2->protocol); if(!p) /* gah! :-P */ continue; if(!strncmp(prefix, human, n)) break; } if(k2) { irc_rootmsg(irc, "%s: multiple matches", prefix); return NULL; } return k; } void show_general_otr_info(irc_t *irc) { ConnContext *ctx; OtrlPrivKey *key; char human[45]; kg_t *kg; /* list all privkeys (including ones being generated) */ irc_rootmsg(irc, "\x1fprivate keys:\x1f"); for(key=irc->otr->us->privkey_root; key; key=key->next) { const char *hash; switch(key->pubkey_type) { case OTRL_PUBKEY_TYPE_DSA: irc_rootmsg(irc, " %s/%s - DSA", key->accountname, key->protocol); break; default: irc_rootmsg(irc, " %s/%s - type %d", key->accountname, key->protocol, key->pubkey_type); } /* No, it doesn't make much sense to search for the privkey again by account/protocol, but libotr currently doesn't provide a direct routine for hashing a given 'OtrlPrivKey'... */ hash = otrl_privkey_fingerprint(irc->otr->us, human, key->accountname, key->protocol); if(hash) /* should always succeed */ irc_rootmsg(irc, " %s", human); } if(irc->otr->sent_accountname) { irc_rootmsg(irc, " %s/%s - DSA", irc->otr->sent_accountname, irc->otr->sent_protocol); irc_rootmsg(irc, " (being generated)"); } for(kg=irc->otr->todo; kg; kg=kg->next) { irc_rootmsg(irc, " %s/%s - DSA", kg->accountname, kg->protocol); irc_rootmsg(irc, " (queued)"); } if(key == irc->otr->us->privkey_root && !irc->otr->sent_accountname && kg == irc->otr->todo) irc_rootmsg(irc, " (none)"); /* list all contexts */ irc_rootmsg(irc, "%s", ""); irc_rootmsg(irc, "\x1f" "connection contexts:\x1f (bold=currently encrypted)"); for(ctx=irc->otr->us->context_root; ctx; ctx=ctx->next) {\ irc_user_t *u; char *userstring; u = peeruser(irc, ctx->username, ctx->protocol); if(u) userstring = g_strdup_printf("%s/%s/%s (%s)", ctx->username, ctx->protocol, ctx->accountname, u->nick); else userstring = g_strdup_printf("%s/%s/%s", ctx->username, ctx->protocol, ctx->accountname); if(ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED) { irc_rootmsg(irc, " \x02%s\x02", userstring); } else { irc_rootmsg(irc, " %s", userstring); } g_free(userstring); } if(ctx == irc->otr->us->context_root) irc_rootmsg(irc, " (none)"); } void show_otr_context_info(irc_t *irc, ConnContext *ctx) { switch(ctx->otr_offer) { case OFFER_NOT: irc_rootmsg(irc, " otr offer status: none sent"); break; case OFFER_SENT: irc_rootmsg(irc, " otr offer status: awaiting reply"); break; case OFFER_ACCEPTED: irc_rootmsg(irc, " otr offer status: accepted our offer"); break; case OFFER_REJECTED: irc_rootmsg(irc, " otr offer status: ignored our offer"); break; default: irc_rootmsg(irc, " otr offer status: %d", ctx->otr_offer); } switch(ctx->msgstate) { case OTRL_MSGSTATE_PLAINTEXT: irc_rootmsg(irc, " connection state: cleartext"); break; case OTRL_MSGSTATE_ENCRYPTED: irc_rootmsg(irc, " connection state: encrypted (v%d)", ctx->protocol_version); break; case OTRL_MSGSTATE_FINISHED: irc_rootmsg(irc, " connection state: shut down"); break; default: irc_rootmsg(irc, " connection state: %d", ctx->msgstate); } irc_rootmsg(irc, " fingerprints: (bold=active)"); show_fingerprints(irc, ctx); } int keygen_in_progress(irc_t *irc, const char *handle, const char *protocol) { kg_t *kg; if(!irc->otr->sent_accountname || !irc->otr->sent_protocol) return 0; /* are we currently working on this key? */ if(!strcmp(handle, irc->otr->sent_accountname) && !strcmp(protocol, irc->otr->sent_protocol)) return 1; /* do we have it queued for later? */ for(kg=irc->otr->todo; kg; kg=kg->next) { if(!strcmp(handle, kg->accountname) && !strcmp(protocol, kg->protocol)) return 1; } return 0; } void otr_keygen(irc_t *irc, const char *handle, const char *protocol) { /* do nothing if a key for the requested account is already being generated */ if(keygen_in_progress(irc, handle, protocol)) return; /* see if we already have a keygen child running. if not, start one and put a handler on its output. */ if(!irc->otr->keygen || waitpid(irc->otr->keygen, NULL, WNOHANG)) { pid_t p; int to[2], from[2]; FILE *tof, *fromf; if(pipe(to) < 0 || pipe(from) < 0) { irc_rootmsg(irc, "otr keygen: couldn't create pipe: %s", strerror(errno)); return; } tof = fdopen(to[1], "w"); fromf = fdopen(from[0], "r"); if(!tof || !fromf) { irc_rootmsg(irc, "otr keygen: couldn't streamify pipe: %s", strerror(errno)); return; } p = fork(); if(p<0) { irc_rootmsg(irc, "otr keygen: couldn't fork: %s", strerror(errno)); return; } if(!p) { /* child process */ signal(SIGTERM, exit); keygen_child_main(irc->otr->us, to[0], from[1]); exit(0); } irc->otr->keygen = p; irc->otr->to = tof; irc->otr->from = fromf; irc->otr->sent_accountname = NULL; irc->otr->sent_protocol = NULL; irc->otr->todo = NULL; b_input_add(from[0], B_EV_IO_READ, keygen_finish_handler, irc); } /* is the keygen slave currently working? */ if(irc->otr->sent_accountname) { /* enqueue our job for later transmission */ kg_t **kg = &irc->otr->todo; while(*kg) kg=&((*kg)->next); *kg = g_new0(kg_t, 1); (*kg)->accountname = g_strdup(handle); (*kg)->protocol = g_strdup(protocol); } else { /* send our job over and remember it */ fprintf(irc->otr->to, "%s\n%s\n", handle, protocol); fflush(irc->otr->to); irc->otr->sent_accountname = g_strdup(handle); irc->otr->sent_protocol = g_strdup(protocol); } } void keygen_child_main(OtrlUserState us, int infd, int outfd) { FILE *input, *output; char filename[128], accountname[512], protocol[512]; gcry_error_t e; int tempfd; input = fdopen(infd, "r"); output = fdopen(outfd, "w"); while(!feof(input) && !ferror(input) && !feof(output) && !ferror(output)) { myfgets(accountname, 512, input); myfgets(protocol, 512, input); strncpy(filename, "/tmp/bitlbee-XXXXXX", 128); tempfd = mkstemp(filename); close(tempfd); e = otrl_privkey_generate(us, filename, accountname, protocol); if(e) { fprintf(output, "\n"); /* this means failure */ fprintf(output, "otr keygen: %s\n", gcry_strerror(e)); unlink(filename); } else { fprintf(output, "%s\n", filename); fprintf(output, "otr keygen for %s/%s complete\n", accountname, protocol); } fflush(output); } fclose(input); fclose(output); } gboolean keygen_finish_handler(gpointer data, gint fd, b_input_condition cond) { irc_t *irc = (irc_t *)data; char filename[512], msg[512]; myfgets(filename, 512, irc->otr->from); myfgets(msg, 512, irc->otr->from); irc_rootmsg(irc, "%s", msg); if(filename[0]) { if(strsane(irc->user->nick)) { char *kf = g_strdup_printf("%s%s.otr_keys", global.conf->configdir, irc->user->nick); char *tmp = g_strdup_printf("%s.new", kf); copyfile(filename, tmp); unlink(filename); rename(tmp,kf); otrl_privkey_read(irc->otr->us, kf); g_free(kf); g_free(tmp); } else { otrl_privkey_read(irc->otr->us, filename); unlink(filename); } } /* forget this job */ g_free(irc->otr->sent_accountname); g_free(irc->otr->sent_protocol); irc->otr->sent_accountname = NULL; irc->otr->sent_protocol = NULL; /* see if there are any more in the queue */ if(irc->otr->todo) { kg_t *p = irc->otr->todo; /* send the next one over */ fprintf(irc->otr->to, "%s\n%s\n", p->accountname, p->protocol); fflush(irc->otr->to); irc->otr->sent_accountname = p->accountname; irc->otr->sent_protocol = p->protocol; irc->otr->todo = p->next; g_free(p); return TRUE; /* keep watching */ } else { /* okay, the slave is idle now, so kill him */ fclose(irc->otr->from); fclose(irc->otr->to); irc->otr->from = irc->otr->to = NULL; kill(irc->otr->keygen, SIGTERM); waitpid(irc->otr->keygen, NULL, 0); irc->otr->keygen = 0; return FALSE; /* unregister ourselves */ } } void copyfile(const char *a, const char *b) { int fda, fdb; int n; char buf[1024]; fda = open(a, O_RDONLY); fdb = open(b, O_WRONLY | O_CREAT | O_TRUNC, 0600); while((n=read(fda, buf, 1024)) > 0) write(fdb, buf, n); close(fda); close(fdb); } void myfgets(char *s, int size, FILE *stream) { if(!fgets(s, size, stream)) { s[0] = '\0'; } else { int n = strlen(s); if(n>0 && s[n-1] == '\n') s[n-1] = '\0'; } } void yes_keygen(void *data) { account_t *acc = (account_t *)data; irc_t *irc = acc->bee->ui_data; if(keygen_in_progress(irc, acc->user, acc->prpl->name)) { irc_rootmsg(irc, "keygen for %s/%s already in progress", acc->user, acc->prpl->name); } else { irc_rootmsg(irc, "starting background keygen for %s/%s", acc->user, acc->prpl->name); irc_rootmsg(irc, "you will be notified when it completes"); otr_keygen(irc, acc->user, acc->prpl->name); } } /* check whether a string is safe to use in a path component */ int strsane(const char *s) { return strpbrk(s, "/\\") == NULL; } /* vim: set noet ts=4 sw=4: */ bitlbee-3.2.1/bitlbee.h0000644000175000017500000001346012245474076014301 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 Wilmer van der Gaast and others * \********************************************************************/ /* Main file */ /* 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 _BITLBEE_H #define _BITLBEE_H #ifdef __cplusplus extern "C" { #endif #ifndef _GNU_SOURCE #define _GNU_SOURCE /* Stupid GNU :-P */ #endif /* Depend on Windows 2000 for now since we need getaddrinfo() */ #define _WIN32_WINNT 0x0501 #define PACKAGE "BitlBee" #define BITLBEE_VERSION "3.2.1" #define VERSION BITLBEE_VERSION #define BITLBEE_VER(a,b,c) (((a) << 16) + ((b) << 8) + (c)) #define BITLBEE_VERSION_CODE BITLBEE_VER(3, 2, 0) #define MAX_STRING 511 #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #endif #include #include /* The following functions should not be used if we want to maintain Windows compatibility... */ #undef free #define free __PLEASE_USE_THE_GLIB_MEMORY_ALLOCATION_SYSTEM__ #undef malloc #define malloc __PLEASE_USE_THE_GLIB_MEMORY_ALLOCATION_SYSTEM__ #undef calloc #define calloc __PLEASE_USE_THE_GLIB_MEMORY_ALLOCATION_SYSTEM__ #undef realloc #define realloc __PLEASE_USE_THE_GLIB_MEMORY_ALLOCATION_SYSTEM__ #undef strdup #define strdup __PLEASE_USE_THE_GLIB_STRDUP_FUNCTIONS_SYSTEM__ #undef strndup #define strndup __PLEASE_USE_THE_GLIB_STRDUP_FUNCTIONS_SYSTEM__ #undef snprintf #define snprintf __PLEASE_USE_G_SNPRINTF__ #undef strcasecmp #define strcasecmp __PLEASE_USE_G_STRCASECMP__ #undef strncasecmp #define strncasecmp __PLEASE_USE_G_STRNCASECMP__ /* And the following functions shouldn't be used anymore to keep compatibility with other event handling libs than GLib. */ #undef g_timeout_add #define g_timeout_add __PLEASE_USE_B_TIMEOUT_ADD__ #undef g_timeout_add_full #define g_timeout_add_full __PLEASE_USE_B_TIMEOUT_ADD__ #undef g_io_add_watch #define g_io_add_watch __PLEASE_USE_B_INPUT_ADD__ #undef g_io_add_watch_full #define g_io_add_watch_full __PLEASE_USE_B_INPUT_ADD__ #undef g_source_remove #define g_source_remove __PLEASE_USE_B_EVENT_REMOVE__ #undef g_source_remove_by_user_data #define g_source_remove_by_user_data __PLEASE_USE_B_SOURCE_REMOVE_BY_USER_DATA__ #undef g_main_run #define g_main_run __PLEASE_USE_B_MAIN_RUN__ #undef g_main_quit #define g_main_quit __PLEASE_USE_B_MAIN_QUIT__ /* And now, because GLib folks think everyone loves typing ridiculously long function names ... no I don't or I'd write BitlBee in Java, ffs. */ #define g_strcasecmp g_ascii_strcasecmp #define g_strncasecmp g_ascii_strncasecmp #ifndef G_GNUC_MALLOC /* Doesn't exist in GLib <=2.4 while everything else in BitlBee should work with it, so let's fake this one. */ #define G_GNUC_MALLOC #endif #define _( x ) x #define ROOT_NICK "root" #define ROOT_CHAN "&bitlbee" #define ROOT_FN "User manager" #define NS_NICK "NickServ" #define DEFAULT_AWAY "Away from computer" #define CONTROL_TOPIC "Welcome to the control channel. Type \2help\2 for help information." #define IRCD_INFO PACKAGE " " #define MAX_NICK_LENGTH 24 #define HELP_FILE VARDIR "help.txt" #define CONF_FILE_DEF ETCDIR "bitlbee.conf" /* Hack to give a little bit more password security on IRC: If an account has this password set, use /OPER to change it. */ #define PASSWORD_PENDING "\r\rchangeme\r\r" #include "bee.h" #include "irc.h" #include "storage.h" #include "set.h" #include "nogaim.h" #include "commands.h" #include "account.h" #include "nick.h" #include "conf.h" #include "log.h" #include "ini.h" #include "query.h" #include "sock.h" #include "misc.h" #include "proxy.h" typedef struct global { /* In forked mode, child processes store the fd of the IPC socket here. */ int listen_socket; gint listen_watch_source_id; struct help *help; char *conf_file; conf_t *conf; GList *storage; /* The first backend in the list will be used for saving */ char *helpfile; int restart; } global_t; int bitlbee_daemon_init( void ); int bitlbee_inetd_init( void ); gboolean bitlbee_io_current_client_read( gpointer data, gint source, b_input_condition cond ); gboolean bitlbee_io_current_client_write( gpointer data, gint source, b_input_condition cond ); void root_command_string( irc_t *irc, char *command ); void root_command( irc_t *irc, char *command[] ); gboolean root_command_add( const char *command, int params, void (*func)(irc_t *, char **args), int flags ); gboolean cmd_identify_finish( gpointer data, gint fd, b_input_condition cond ); gboolean bitlbee_shutdown( gpointer data, gint fd, b_input_condition cond ); char *set_eval_root_nick( set_t *set, char *new_nick ); char *set_eval_control_channel( set_t *set, char *new_name ); extern global_t global; #ifdef __cplusplus } #endif #endif bitlbee-3.2.1/bitlbee.conf0000644000175000017500000001155512245474076015002 0ustar wilmerwilmer## BitlBee default configuration file ## ## Comments are marked like this. The rest of the file is INI-style. The ## comments should tell you enough about what all settings mean. ## [settings] ## RunMode: ## ## Inetd -- Run from inetd (default) ## Daemon -- Run as a stand-alone daemon, serving all users from one process. ## This saves memory if there are more users, the downside is that when one ## user hits a crash-bug, all other users will also lose their connection. ## ForkDaemon -- Run as a stand-alone daemon, but keep all clients in separate ## child processes. This should be pretty safe and reliable to use instead ## of inetd mode. ## # RunMode = Inetd ## User: ## ## If BitlBee is started by root as a daemon, it can drop root privileges, ## and change to the specified user. ## # User = bitlbee ## DaemonPort/DaemonInterface: ## ## For daemon mode, you can specify on what interface and port the daemon ## should be listening for connections. ## # DaemonInterface = 0.0.0.0 # DaemonPort = 6667 ## ClientInterface: ## ## If for any reason, you want BitlBee to use a specific address/interface ## for outgoing traffic (IM connections, HTTP(S), etc.), set it here. ## # ClientInterface = 0.0.0.0 ## AuthMode ## ## Open -- Accept connections from anyone, use NickServ for user authentication. ## (default) ## Closed -- Require authorization (using the PASS command during login) before ## allowing the user to connect at all. ## Registered -- Only allow registered users to use this server; this disables ## the register- and the account command until the user identifies itself. ## # AuthMode = Open ## AuthPassword ## ## Password the user should enter when logging into a closed BitlBee server. ## You can also have a BitlBee-style MD5 hash here. Format: "md5:", followed ## by a hash as generated by "bitlbee -x hash ". ## # AuthPassword = ItllBeBitlBee ## Heh.. Our slogan. ;-) ## or # AuthPassword = md5:gzkK0Ox/1xh+1XTsQjXxBJ571Vgl ## OperPassword ## ## Password that unlocks access to special operator commands. ## # OperPassword = ChangeMe! ## or # OperPassword = md5:I0mnZbn1t4R731zzRdDN2/pK7lRX ## HostName ## ## Normally, BitlBee gets a hostname using getsockname(). If you have a nicer ## alias for your BitlBee daemon, you can set it here and BitlBee will identify ## itself with that name instead. ## # HostName = localhost ## MotdFile ## ## Specify an alternative MOTD (Message Of The Day) file. Default value depends ## on the --etcdir argument to configure. ## # MotdFile = /etc/bitlbee/motd.txt ## ConfigDir ## ## Specify an alternative directory to store all the per-user configuration ## files. (.nicks/.accounts) ## # ConfigDir = /var/lib/bitlbee ## Ping settings ## ## BitlBee can send PING requests to the client to check whether it's still ## alive. This is not very useful on local servers, but it does make sense ## when most clients connect to the server over a real network interface. ## (Public servers) Pinging the client will make sure lost clients are ## detected and cleaned up sooner. ## ## PING requests are sent every PingInterval seconds. If no PONG reply has ## been received for PingTimeOut seconds, BitlBee aborts the connection. ## ## To disable the pinging, set at least one of these to 0. ## # PingInterval = 180 # PingTimeOut = 300 ## Using proxy servers for outgoing connections ## ## If you're running BitlBee on a host which is behind a restrictive firewall ## and a proxy server, you can tell BitlBee to use that proxy server here. ## The setting has to be a URL, formatted like one of these examples: ## ## (Obviously, the username and password are optional) ## # Proxy = http://john:doe@proxy.localnet.com:8080 # Proxy = socks4://socksproxy.localnet.com # Proxy = socks5://socksproxy.localnet.com ## Protocols offered by bitlbee ## ## As recompiling may be quite unpractical for some people, this option ## allows to remove the support of protocol, even if compiled in. If ## nothing is given, there are no restrictions. ## # Protocols = jabber yahoo ## Trusted CAs ## ## Path to a file containing a list of trusted certificate authorities used in ## the verification of server certificates. ## ## Uncomment this and make sure the file actually exists and contains all ## certificate authorities you're willing to accept (default value should ## work on at least Debian/Ubuntu systems with the "ca-certificates" package ## installed). As long as the line is commented out, SSL certificate ## verification is completely disabled. ## ## The location of this file may be different on other distros/OSes. For ## example, try /etc/ssl/ca-bundle.pem on OpenSUSE. ## # CAfile = /etc/ssl/certs/ca-certificates.crt [defaults] ## Here you can override the defaults for some per-user settings. Users are ## still able to override your defaults, so this is not a way to restrict ## your users... ## To enable private mode by default, for example: ## private = 1 bitlbee-3.2.1/irc_user.c0000644000175000017500000001546312245474076014506 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* Stuff to handle, save and search IRC buddies */ /* 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 "ipc.h" irc_user_t *irc_user_new( irc_t *irc, const char *nick ) { irc_user_t *iu = g_new0( irc_user_t, 1 ); iu->irc = irc; iu->nick = g_strdup( nick ); iu->user = iu->host = iu->fullname = iu->nick; iu->key = g_strdup( nick ); nick_lc( irc, iu->key ); /* Using the hash table for speed and irc->users for easy iteration through the list (since the GLib API doesn't have anything sane for that.) */ g_hash_table_insert( irc->nick_user_hash, iu->key, iu ); irc->users = g_slist_insert_sorted( irc->users, iu, irc_user_cmp ); return iu; } int irc_user_free( irc_t *irc, irc_user_t *iu ) { static struct im_connection *last_ic; static char *msg; if( !iu ) return 0; if( iu->bu && ( iu->bu->ic->flags & OPT_LOGGING_OUT ) && iu->bu->ic != last_ic ) { char host_prefix[] = "bitlbee."; char *s; /* Irssi recognises netsplits by quitmsgs with two hostnames, where a hostname is a "word" with one of more dots. Mangle no-dot hostnames a bit. */ if( strchr( irc->root->host, '.' ) ) *host_prefix = '\0'; last_ic = iu->bu->ic; g_free( msg ); if( !set_getbool( &irc->b->set, "simulate_netsplit" ) ) msg = g_strdup( "Account off-line" ); else if( ( s = strchr( iu->bu->ic->acc->user, '@' ) ) ) msg = g_strdup_printf( "%s%s %s", host_prefix, irc->root->host, s + 1 ); else msg = g_strdup_printf( "%s%s %s.%s", host_prefix, irc->root->host, iu->bu->ic->acc->prpl->name, irc->root->host ); } else if( !iu->bu || !( iu->bu->ic->flags & OPT_LOGGING_OUT ) ) { g_free( msg ); msg = g_strdup( "Removed" ); last_ic = NULL; } irc_user_quit( iu, msg ); irc->users = g_slist_remove( irc->users, iu ); g_hash_table_remove( irc->nick_user_hash, iu->key ); g_free( iu->nick ); if( iu->nick != iu->user ) g_free( iu->user ); if( iu->nick != iu->host ) g_free( iu->host ); if( iu->nick != iu->fullname ) g_free( iu->fullname ); g_free( iu->pastebuf ); if( iu->pastebuf_timer ) b_event_remove( iu->pastebuf_timer ); g_free( iu->key ); g_free( iu ); return 1; } irc_user_t *irc_user_by_name( irc_t *irc, const char *nick ) { char key[strlen(nick)+1]; strcpy( key, nick ); if( nick_lc( irc, key ) ) return g_hash_table_lookup( irc->nick_user_hash, key ); else return NULL; } int irc_user_set_nick( irc_user_t *iu, const char *new ) { irc_t *irc = iu->irc; irc_user_t *new_iu; char key[strlen(new)+1]; GSList *cl; strcpy( key, new ); if( iu == NULL || !nick_lc( irc, key ) || ( ( new_iu = irc_user_by_name( irc, new ) ) && new_iu != iu ) ) return 0; for( cl = irc->channels; cl; cl = cl->next ) { irc_channel_t *ic = cl->data; /* Send a NICK update if we're renaming our user, or someone who's in the same channel like our user. */ if( iu == irc->user || ( ( ic->flags & IRC_CHANNEL_JOINED ) && irc_channel_has_user( ic, iu ) ) ) { irc_send_nick( iu, new ); break; } } irc->users = g_slist_remove( irc->users, iu ); g_hash_table_remove( irc->nick_user_hash, iu->key ); if( iu->nick == iu->user ) iu->user = NULL; if( iu->nick == iu->host ) iu->host = NULL; if( iu->nick == iu->fullname ) iu->fullname = NULL; g_free( iu->nick ); iu->nick = g_strdup( new ); if( iu->user == NULL ) iu->user = g_strdup( iu->nick ); if( iu->host == NULL ) iu->host = g_strdup( iu->nick ); if( iu->fullname == NULL ) iu->fullname = g_strdup( iu->nick ); g_free( iu->key ); iu->key = g_strdup( key ); g_hash_table_insert( irc->nick_user_hash, iu->key, iu ); irc->users = g_slist_insert_sorted( irc->users, iu, irc_user_cmp ); if( iu == irc->user ) ipc_to_master_str( "NICK :%s\r\n", new ); return 1; } gint irc_user_cmp( gconstpointer a_, gconstpointer b_ ) { const irc_user_t *a = a_, *b = b_; return strcmp( a->key, b->key ); } const char *irc_user_get_away( irc_user_t *iu ) { irc_t *irc = iu->irc; bee_user_t *bu = iu->bu; if( iu == irc->user ) return set_getstr( &irc->b->set, "away" ); else if( bu ) { if( !bu->flags & BEE_USER_ONLINE ) return "Offline"; else if( bu->flags & BEE_USER_AWAY ) { if( bu->status_msg ) { static char ret[MAX_STRING]; g_snprintf( ret, MAX_STRING - 1, "%s (%s)", bu->status ? : "Away", bu->status_msg ); return ret; } else return bu->status ? : "Away"; } } return NULL; } void irc_user_quit( irc_user_t *iu, const char *msg ) { GSList *l; gboolean send_quit = FALSE; if( !iu ) return; for( l = iu->irc->channels; l; l = l->next ) { irc_channel_t *ic = l->data; send_quit |= irc_channel_del_user( ic, iu, IRC_CDU_SILENT, NULL ) && ( ic->flags & IRC_CHANNEL_JOINED ); } if( send_quit ) irc_send_quit( iu, msg ); } /* User-type dependent functions, for root/NickServ: */ static gboolean root_privmsg( irc_user_t *iu, const char *msg ) { char cmd[strlen(msg)+1]; strcpy( cmd, msg ); root_command_string( iu->irc, cmd ); return TRUE; } static gboolean root_ctcp( irc_user_t *iu, char * const *ctcp ) { if( g_strcasecmp( ctcp[0], "VERSION" ) == 0 ) { irc_send_msg_f( iu, "NOTICE", iu->irc->user->nick, "\001%s %s\001", ctcp[0], PACKAGE " " BITLBEE_VERSION " " ARCH "/" CPU ); } else if( g_strcasecmp( ctcp[0], "PING" ) == 0 ) { irc_send_msg_f( iu, "NOTICE", iu->irc->user->nick, "\001%s %s\001", ctcp[0], ctcp[1] ? : "" ); } return TRUE; } const struct irc_user_funcs irc_user_root_funcs = { root_privmsg, root_ctcp, }; /* Echo to yourself: */ static gboolean self_privmsg( irc_user_t *iu, const char *msg ) { irc_send_msg( iu, "PRIVMSG", iu->nick, msg, NULL ); return TRUE; } const struct irc_user_funcs irc_user_self_funcs = { self_privmsg, }; bitlbee-3.2.1/log.c0000644000175000017500000001173012245474076013445 0ustar wilmerwilmer /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2005 Wilmer van der Gaast and others * \********************************************************************/ /* Logging services for the bee */ /* 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 */ #define BITLBEE_CORE #include "bitlbee.h" #include static log_t logoutput; static void log_null(int level, const char *logmessage); static void log_irc(int level, const char *logmessage); static void log_syslog(int level, const char *logmessage); static void log_console(int level, const char *logmessage); void log_init(void) { openlog("bitlbee", LOG_PID, LOG_DAEMON); logoutput.informational = &log_null; logoutput.warning = &log_null; logoutput.error = &log_null; #ifdef DEBUG logoutput.debug = &log_null; #endif return; } void log_link(int level, int output) { /* I know it's ugly, but it works and I didn't feel like messing with pointer to function pointers */ if(level == LOGLVL_INFO) { if(output == LOGOUTPUT_NULL) logoutput.informational = &log_null; else if(output == LOGOUTPUT_IRC) logoutput.informational = &log_irc; else if(output == LOGOUTPUT_SYSLOG) logoutput.informational = &log_syslog; else if(output == LOGOUTPUT_CONSOLE) logoutput.informational = &log_console; } else if(level == LOGLVL_WARNING) { if(output == LOGOUTPUT_NULL) logoutput.warning = &log_null; else if(output == LOGOUTPUT_IRC) logoutput.warning = &log_irc; else if(output == LOGOUTPUT_SYSLOG) logoutput.warning = &log_syslog; else if(output == LOGOUTPUT_CONSOLE) logoutput.warning = &log_console; } else if(level == LOGLVL_ERROR) { if(output == LOGOUTPUT_NULL) logoutput.error = &log_null; else if(output == LOGOUTPUT_IRC) logoutput.error = &log_irc; else if(output == LOGOUTPUT_SYSLOG) logoutput.error = &log_syslog; else if(output == LOGOUTPUT_CONSOLE) logoutput.error = &log_console; } #ifdef DEBUG else if(level == LOGLVL_DEBUG) { if(output == LOGOUTPUT_NULL) logoutput.debug = &log_null; else if(output == LOGOUTPUT_IRC) logoutput.debug = &log_irc; else if(output == LOGOUTPUT_SYSLOG) logoutput.debug = &log_syslog; else if(output == LOGOUTPUT_CONSOLE) logoutput.debug = &log_console; } #endif return; } void log_message(int level, const char *message, ... ) { va_list ap; char *msgstring; va_start(ap, message); msgstring = g_strdup_vprintf(message, ap); va_end(ap); if(level == LOGLVL_INFO) (*(logoutput.informational))(level, msgstring); if(level == LOGLVL_WARNING) (*(logoutput.warning))(level, msgstring); if(level == LOGLVL_ERROR) (*(logoutput.error))(level, msgstring); #ifdef DEBUG if(level == LOGLVL_DEBUG) (*(logoutput.debug))(level, msgstring); #endif g_free(msgstring); return; } void log_error(const char *functionname) { log_message(LOGLVL_ERROR, "%s: %s", functionname, strerror(errno)); return; } static void log_null(int level, const char *message) { return; } static void log_irc(int level, const char *message) { if(level == LOGLVL_ERROR) irc_write_all(1, "ERROR :Error: %s", message); if(level == LOGLVL_WARNING) irc_write_all(0, "ERROR :Warning: %s", message); if(level == LOGLVL_INFO) irc_write_all(0, "ERROR :Informational: %s", message); #ifdef DEBUG if(level == LOGLVL_DEBUG) irc_write_all(0, "ERROR :Debug: %s", message); #endif return; } static void log_syslog(int level, const char *message) { if(level == LOGLVL_ERROR) syslog(LOG_ERR, "%s", message); if(level == LOGLVL_WARNING) syslog(LOG_WARNING, "%s", message); if(level == LOGLVL_INFO) syslog(LOG_INFO, "%s", message); #ifdef DEBUG if(level == LOGLVL_DEBUG) syslog(LOG_DEBUG, "%s", message); #endif return; } static void log_console(int level, const char *message) { if(level == LOGLVL_ERROR) fprintf(stderr, "Error: %s\n", message); if(level == LOGLVL_WARNING) fprintf(stderr, "Warning: %s\n", message); if(level == LOGLVL_INFO) fprintf(stdout, "Informational: %s\n", message); #ifdef DEBUG if(level == LOGLVL_DEBUG) fprintf(stdout, "Debug: %s\n", message); #endif /* Always log stuff in syslogs too. */ log_syslog(level, message); return; }