diff options
128 files changed, 15426 insertions, 6632 deletions
@@ -9,8 +9,8 @@ -include Makefile.settings # Program variables -objects = account.o bitlbee.o chat.o crypting.o help.o ipc.o irc.o irc_commands.o nick.o query.o root_commands.o set.o storage.o $(STORAGE_OBJS) user.o -headers = account.h bitlbee.h commands.h conf.h config.h crypting.h help.h ipc.h irc.h log.h nick.h query.h set.h sock.h storage.h user.h lib/events.h lib/http_client.h lib/ini.h lib/md5.h lib/misc.h lib/proxy.h lib/sha1.h lib/ssl_client.h lib/url.h protocols/nogaim.h +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 query.o root_commands.o set.o storage.o $(STORAGE_OBJS) +headers = account.h 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 user.h lib/events.h lib/ftutil.h lib/http_client.h lib/ini.h lib/md5.h lib/misc.h lib/proxy.h lib/sha1.h lib/ssl_client.h lib/url.h protocols/ft.h protocols/nogaim.h subdirs = lib protocols ifeq ($(TARGET),i586-mingw32msvc) @@ -81,7 +81,8 @@ uninstall-bin: install-dev: mkdir -p $(DESTDIR)$(INCLUDEDIR) - install -m 0644 $(headers) $(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) @@ -92,8 +93,8 @@ uninstall-dev: install-etc: mkdir -p $(DESTDIR)$(ETCDIR) - install -m 0644 motd.txt $(DESTDIR)$(ETCDIR)/motd.txt - install -m 0644 bitlbee.conf $(DESTDIR)$(ETCDIR)/bitlbee.conf + 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 @@ -109,7 +110,7 @@ tar: $(subdirs): @$(MAKE) -C $@ $(MAKECMDGOALS) -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ @@ -120,7 +120,7 @@ int bitlbee_daemon_init() return( -1 ); } - global.listen_watch_source_id = b_input_add( global.listen_socket, GAIM_INPUT_READ, bitlbee_io_new_client, NULL ); + 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 ) @@ -317,10 +317,12 @@ static gboolean bitlbee_io_new_client( gpointer data, gint fd, b_input_condition { 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, GAIM_INPUT_READ, ipc_master_read, child ); + 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 ); @@ -348,7 +350,7 @@ static gboolean bitlbee_io_new_client( gpointer data, gint fd, b_input_condition /* We can store the IPC fd there now. */ global.listen_socket = fds[1]; - global.listen_watch_source_id = b_input_add( fds[1], GAIM_INPUT_READ, ipc_child_read, irc ); + global.listen_watch_source_id = b_input_add( fds[1], B_EV_IO_READ, ipc_child_read, irc ); close( fds[0] ); @@ -369,7 +371,8 @@ 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_free( irc_connection_list->data ); + irc_abort( irc_connection_list->data, FALSE, + "BitlBee server shutting down" ); /* We'll only reach this point when not running in inetd mode: */ b_main_quit(); @@ -42,7 +42,7 @@ #define MAX_STRING 511 #if HAVE_CONFIG_H -#include "config.h" +#include <config.h> #endif #include <fcntl.h> @@ -125,6 +125,7 @@ #define HELP_FILE VARDIR "help.txt" #define CONF_FILE_DEF ETCDIR "bitlbee.conf" +#include "bee.h" #include "irc.h" #include "storage.h" #include "set.h" @@ -159,8 +160,9 @@ 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, user_t *u, char *command, int flags ); +void root_command_string( irc_t *irc, char *command ); void root_command( irc_t *irc, char *command[] ); +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 ); diff --git a/chat.c b/chat.c deleted file mode 100644 index 8c5ce0bc..00000000 --- a/chat.c +++ /dev/null @@ -1,192 +0,0 @@ - /********************************************************************\ - * BitlBee -- An IRC to other IM-networks gateway * - * * - * Copyright 2002-2008 Wilmer van der Gaast and others * - \********************************************************************/ - -/* Keep track of chatrooms the user is interested in */ - -/* - 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 "chat.h" - -struct chat *chat_add( irc_t *irc, account_t *acc, char *handle, char *channel ) -{ - struct chat *c, *l; - set_t *s; - - if( acc->prpl->chat_join == NULL || !chat_chanok( channel ) || - chat_chancmp( channel, irc->channel ) == 0 ) - { - return NULL; - } - - for( c = irc->chatrooms; c; c = c->next ) - { - if( chat_chancmp( channel, c->channel ) == 0 ) - return NULL; - - if( acc == c->acc && g_strcasecmp( handle, c->handle ) == 0 ) - return NULL; - - l = c; - } - - if( irc->chatrooms == NULL ) - irc->chatrooms = c = g_new0( struct chat, 1 ); - else - l->next = c = g_new0( struct chat, 1 ); - - c->acc = acc; - c->handle = g_strdup( handle ); - c->channel = g_strdup( channel ); - - s = set_add( &c->set, "auto_join", "false", set_eval_bool, c ); - /* s = set_add( &c->set, "auto_rejoin", "false", set_eval_bool, c ); */ - s = set_add( &c->set, "nick", NULL, NULL, c ); - s->flags |= SET_NULL_OK; - - return c; -} - -struct chat *chat_byhandle( irc_t *irc, account_t *acc, char *handle ) -{ - struct chat *c; - - for( c = irc->chatrooms; c; c = c->next ) - { - if( acc == c->acc && g_strcasecmp( handle, c->handle ) == 0 ) - break; - } - - return c; -} - -struct chat *chat_bychannel( irc_t *irc, char *channel ) -{ - struct chat *c; - - for( c = irc->chatrooms; c; c = c->next ) - { - if( chat_chancmp( channel, c->channel ) == 0 ) - break; - } - - return c; -} - -struct chat *chat_get( irc_t *irc, char *id ) -{ - struct chat *c, *ret = NULL; - int nr; - - if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 ) - { - for( c = irc->chatrooms; c; c = c->next ) - if( ( nr-- ) == 0 ) - return c; - - return NULL; - } - - for( c = irc->chatrooms; c; c = c->next ) - { - if( strstr( c->handle, id ) ) - { - if( !ret ) - ret = c; - else - return NULL; - } - else if( strstr( c->channel, id ) ) - { - if( !ret ) - ret = c; - else - return NULL; - } - } - - return ret; -} - -int chat_del( irc_t *irc, struct chat *chat ) -{ - struct chat *c, *l = NULL; - - for( c = irc->chatrooms; c; c = (l=c)->next ) - if( c == chat ) - break; - - if( c == NULL ) - return 0; - else if( l == NULL ) - irc->chatrooms = c->next; - else - l->next = c->next; - - while( c->set ) - set_del( &c->set, c->set->key ); - - g_free( c->handle ); - g_free( c->channel ); - g_free( c ); - - return 1; -} - -int chat_chancmp( char *a, char *b ) -{ - if( !chat_chanok( a ) || !chat_chanok( b ) ) - return 0; - - if( a[0] == b[0] ) - return nick_cmp( a + 1, b + 1 ); - else - return -1; -} - -int chat_chanok( char *a ) -{ - if( strchr( CTYPES, a[0] ) != NULL ) - return nick_ok( a + 1 ); - else - return 0; -} - -int chat_join( irc_t *irc, struct chat *c, const char *password ) -{ - struct groupchat *gc; - char *nick = set_getstr( &c->set, "nick" ); - - if( c->acc->ic == NULL || c->acc->prpl->chat_join == NULL ) - return 0; - - if( nick == NULL ) - nick = irc->nick; - - if( ( gc = c->acc->prpl->chat_join( c->acc->ic, c->handle, nick, password ) ) ) - { - g_free( gc->channel ); - gc->channel = g_strdup( c->channel ); - return 1; - } - - return 0; -} diff --git a/chat.h b/chat.h deleted file mode 100644 index 7196aea8..00000000 --- a/chat.h +++ /dev/null @@ -1,51 +0,0 @@ - /********************************************************************\ - * BitlBee -- An IRC to other IM-networks gateway * - * * - * Copyright 2002-2008 Wilmer van der Gaast and others * - \********************************************************************/ - -/* Keep track of chatrooms the user is interested in */ - -/* - 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 _CHAT_H -#define _CHAT_H - -struct chat -{ - account_t *acc; - - char *handle; - char *channel; - set_t *set; - - struct chat *next; -}; - -struct chat *chat_add( irc_t *irc, account_t *acc, char *handle, char *channel ); -struct chat *chat_byhandle( irc_t *irc, account_t *acc, char *handle ); -struct chat *chat_bychannel( irc_t *irc, char *channel ); -struct chat *chat_get( irc_t *irc, char *id ); -int chat_del( irc_t *irc, struct chat *chat ); - -int chat_chancmp( char *a, char *b ); -int chat_chanok( char *a ); - -int chat_join( irc_t *irc, struct chat *c, const char *password ); - -#endif @@ -62,6 +62,9 @@ conf_t *conf_load( int argc, char *argv[] ) 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; proxytype = 0; @@ -314,6 +317,30 @@ static int conf_loadini( conf_t *conf, char *file ) 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 ); @@ -348,7 +375,7 @@ void conf_loaddefaults( irc_t *irc ) { if( g_strcasecmp( ini->section, "defaults" ) == 0 ) { - set_t *s = set_find( &irc->set, ini->key ); + set_t *s = set_find( &irc->b->set, ini->key ); if( s ) { @@ -49,6 +49,9 @@ typedef struct conf int ping_interval; int ping_timeout; char *user; + size_t ft_max_size; + int ft_max_kbps; + char *ft_listen; char **protocols; } conf_t; @@ -26,6 +26,8 @@ jabber=1 oscar=1 yahoo=1 twitter=1 +twitter=1 +purple=0 debug=0 strip=1 @@ -66,7 +68,9 @@ Option Description Default --jabber=0/1 Disable/enable Jabber part $jabber --oscar=0/1 Disable/enable Oscar part (ICQ, AIM) $oscar --yahoo=0/1 Disable/enable Yahoo part $yahoo ---twitter=0/1 Disable/enable Twitter part $twitter +--twitter=0/1 Disable/enable Twitter part $twitter + +--purple=0/1 Disable/enable libpurple support $purple --debug=0/1 Disable/enable debugging $debug --strip=0/1 Disable/enable binary stripping $strip @@ -120,6 +124,29 @@ LFLAGS= EFLAGS= EOF +srcdir="$(dirname $0)" +if [ "$srcdir" != "." ]; 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<<EOF>config.h /* BitlBee settings, generated by configure @@ -157,7 +184,7 @@ else fi echo CFLAGS=$CFLAGS >> Makefile.settings -echo CFLAGS+=-I`pwd` -I`pwd`/lib -I`pwd`/protocols -I. >> Makefile.settings +echo CFLAGS+=-I${srcdir} -I${srcdir}/lib -I${srcdir}/protocols -I. >> Makefile.settings echo CFLAGS+=-DHAVE_CONFIG_H >> Makefile.settings @@ -397,7 +424,7 @@ if detect_resolv_dynamic || detect_resolv_static; then echo '#define HAVE_RESOLV_A' >> config.h fi -STORAGES="text xml" +STORAGES="xml" if [ "$ldap" = "auto" ]; then detect_ldap @@ -507,6 +534,38 @@ 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<<EOF>>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 + twitter=0 + + if [ "$events" = "libevent" ]; then + echo + 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!' + fi +fi + if [ "$msn" = 0 ]; then echo '#undef WITH_MSN' >> config.h else diff --git a/crypting.c b/crypting.c deleted file mode 100644 index 0a5c937e..00000000 --- a/crypting.c +++ /dev/null @@ -1,133 +0,0 @@ - /********************************************************************\ - * BitlBee -- An IRC to other IM-networks gateway * - * * - * Copyright 2002-2004 Sjoerd Hemminga and others * - \********************************************************************/ - -/* A little bit of encryption for the users' passwords */ - -/* - 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 -*/ - -/* [WvG] This file can also be compiled into a stand-alone program - which can encode/decode BitlBee account files. The main() will be - included if CRYPTING_MAIN is defined. Or just do "make decode" and - the programs will be built. */ - -#include <bitlbee.h> -#include "md5.h" -#include "crypting.h" - -/*\ - * [SH] Do _not_ call this if it's not entirely sure that it will not cause - * harm to another users file, since this does not check the password for - * correctness. -\*/ - -int checkpass (const char *pass, const char *md5sum) -{ - md5_state_t md5state; - md5_byte_t digest[16]; - int i, j; - char digits[3]; - - md5_init (&md5state); - md5_append (&md5state, (unsigned char *)pass, strlen (pass)); - md5_finish (&md5state, digest); - - for (i = 0, j = 0; i < 16; i++, j += 2) { - /* Check password for correctness */ - g_snprintf (digits, sizeof (digits), "%02x\n", digest[i]); - - if (digits[0] != md5sum[j]) return (-1); - if (digits[1] != md5sum[j + 1]) return (-1); - } - - return( 0 ); -} - - -char *hashpass (const char *password) -{ - md5_state_t md5state; - md5_byte_t digest[16]; - int i; - char digits[3]; - char *rv; - - if (password == NULL) return (NULL); - - rv = g_new0 (char, 33); - - md5_init (&md5state); - md5_append (&md5state, (const unsigned char *)password, strlen (password)); - md5_finish (&md5state, digest); - - for (i = 0; i < 16; i++) { - /* Build a hash of the pass */ - g_snprintf (digits, sizeof (digits), "%02x", digest[i]); - strcat (rv, digits); - } - - return (rv); -} - -char *obfucrypt (char *line, const char *password) -{ - int i, j; - char *rv; - - if (password == NULL) return (NULL); - - rv = g_new0 (char, strlen (line) + 1); - - i = j = 0; - while (*line) { - /* Encrypt/obfuscate the line, using the password */ - if (*(signed char*)line < 0) *line = - (*line); - - rv[j] = *line + password[i]; /* Overflow intended */ - - line++; - if (!password[++i]) i = 0; - j++; - } - - return (rv); -} - -char *deobfucrypt (char *line, const char *password) -{ - int i, j; - char *rv; - - if (password == NULL) return (NULL); - - rv = g_new0 (char, strlen (line) + 1); - - i = j = 0; - while (*line) { - /* Decrypt/deobfuscate the line, using the pass */ - rv[j] = *line - password[i]; /* Overflow intended */ - - line++; - if (!password[++i]) i = 0; - j++; - } - - return (rv); -} diff --git a/crypting.h b/crypting.h deleted file mode 100644 index e13b0433..00000000 --- a/crypting.h +++ /dev/null @@ -1,29 +0,0 @@ - /********************************************************************\ - * BitlBee -- An IRC to other IM-networks gateway * - * * - * Copyright 2002-2004 Sjoerd Hemminga and others * - \********************************************************************/ - -/* A little bit of encryption for the users' passwords */ - -/* - 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 -*/ - -int checkpass (const char *password, const char *md5sum); -G_GNUC_MALLOC char *hashpass (const char *password); -G_GNUC_MALLOC char *obfucrypt (char *line, const char *password); -G_GNUC_MALLOC char *deobfucrypt (char *line, const char *password); @@ -0,0 +1,566 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com> * +\********************************************************************/ + +/* + 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 <netinet/tcp.h> +#include <regex.h> +#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, 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> <IP> <port> <filesize> + * + * 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; +} @@ -0,0 +1,105 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2006 Marijn Kruisselbrink and others * +* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com> * +\********************************************************************/ + +/* + 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 diff --git a/debian/config b/debian/bitlbee-common.config index 9bb78237..9bb78237 100755..100644 --- a/debian/config +++ b/debian/bitlbee-common.config diff --git a/debian/bitlbee-common.docs b/debian/bitlbee-common.docs new file mode 100644 index 00000000..72ff657c --- /dev/null +++ b/debian/bitlbee-common.docs @@ -0,0 +1,6 @@ +doc/user-guide/user-guide.txt +doc/user-guide/user-guide.html +doc/AUTHORS +doc/CREDITS +doc/FAQ +doc/README diff --git a/debian/bitlbee-common.examples b/debian/bitlbee-common.examples new file mode 100644 index 00000000..81562b9e --- /dev/null +++ b/debian/bitlbee-common.examples @@ -0,0 +1 @@ +utils/* diff --git a/debian/templates b/debian/bitlbee-common.templates index 0cd04426..0cd04426 100644 --- a/debian/templates +++ b/debian/bitlbee-common.templates diff --git a/debian/bitlbee.init b/debian/bitlbee.init index be1dcd66..be1dcd66 100755..100644 --- a/debian/bitlbee.init +++ b/debian/bitlbee.init diff --git a/debian/postinst b/debian/bitlbee.postinst index db541f6c..db541f6c 100755..100644 --- a/debian/postinst +++ b/debian/bitlbee.postinst diff --git a/debian/postrm b/debian/bitlbee.postrm index 5c3b4b2e..5c3b4b2e 100755..100644 --- a/debian/postrm +++ b/debian/bitlbee.postrm diff --git a/debian/prerm b/debian/bitlbee.prerm index 687c2cc1..687c2cc1 100755..100644 --- a/debian/prerm +++ b/debian/bitlbee.prerm diff --git a/debian/changelog b/debian/changelog index e720a1d4..70f1ed47 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +bitlbee (1.3-0) unstable; urgency=low + + * Setting some bogus version number, fix that later. + * Now using debhelper to improve maintainability. + * Added a bitlbee-libpurple package, and split off docs and stuff into + bitlbee-common. + + -- Wilmer van der Gaast <wilmer@gaast.net> Sat, 05 Jun 2010 15:16:38 +0100 + bitlbee (1.2.8-1) unstable; urgency=low * New upstream version. diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000..1e8b3149 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +6 diff --git a/debian/conffiles b/debian/conffiles deleted file mode 100644 index dcb4078e..00000000 --- a/debian/conffiles +++ /dev/null @@ -1,3 +0,0 @@ -/etc/bitlbee/motd.txt -/etc/bitlbee/bitlbee.conf -/etc/init.d/bitlbee diff --git a/debian/control b/debian/control index 25a90506..436bef6d 100644 --- a/debian/control +++ b/debian/control @@ -3,24 +3,56 @@ Section: net Priority: optional Maintainer: Wilmer van der Gaast <wilmer@gaast.net> Uploaders: Jelmer Vernooij <jelmer@samba.org> -Standards-Version: 3.8.0 -Build-Depends: libglib2.0-dev (>= 2.4), libevent-dev, libgnutls-dev | libnss-dev (>= 1.6), debconf-2.0, po-debconf +Standards-Version: 3.8.4 +Build-Depends: libglib2.0-dev (>= 2.4), libevent-dev, libgnutls-dev | libnss-dev (>= 1.6), po-debconf, libpurple-dev, debhelper (>= 6) Homepage: http://www.bitlbee.org/ Vcs-Bzr: http://code.bitlbee.org/bitlbee/ DM-Upload-Allowed: yes Package: bitlbee Architecture: any -Depends: ${shlibs:Depends}, adduser, net-tools, ${debconf-depends}, debianutils (>= 1.16) -Description: An IRC to other chat networks gateway +Depends: ${shlibs:Depends}, adduser, debianutils (>= 1.16), bitlbee-common (= ${bee:Version}) +Conflicts: bitlbee-libpurple +Replaces: bitlbee-libpurple +Description: An IRC to other chat networks gateway (default version) This program can be used as an IRC server which forwards everything you say to people on other chat networks: Jabber, ICQ, AIM, MSN, Yahoo! and Twitter. +Package: bitlbee-libpurple +Architecture: any +Depends: ${shlibs:Depends}, adduser, debianutils (>= 1.16), bitlbee-common (= ${bee:Version}) +Conflicts: bitlbee +Replaces: bitlbee +Description: An IRC to other chat networks gateway (using libpurple) + This program can be used as an IRC server which forwards everything you + say to people on other chat networks: Jabber, ICQ, AIM, MSN, Yahoo! and + Twitter. + . + This package contains a version of BitlBee that uses the libpurple instant + messaging library instead of built-in code, which adds support for more IM + protocols (all protocols supported by Pidgin/Finch) and features (like file + transfers), at the price of being less lightweight. + . + This variant may not be very suitable for BitlBee instances used by many + (tens or hundreds) of clients. + +Package: bitlbee-common +Architecture: all +Depends: ${misc:Depends}, net-tools +Replaces: bitlbee +Description: An IRC to other chat networks gateway (common files/docs) + This program can be used as an IRC server which forwards everything you + say to people on other chat networks: Jabber, ICQ, AIM, MSN, Yahoo! and + Twitter. + . + This package contains common files (mostly documentation) for bitlbee and + bitlbee-libpurple. + Package: bitlbee-dev Architecture: all -Depends: bitlbee (>= ${source:Version}), bitlbee (<< ${source:Version}.1~) -Description: An IRC to other chat networks gateway +Depends: ${misc:Depends}, bitlbee (>= ${bee:Version}), bitlbee (<< ${bee:Version}.1~) +Description: An IRC to other chat networks gateway (dev files) This program can be used as an IRC server which forwards everything you say to people on other chat networks: Jabber, ICQ, AIM, MSN, Yahoo! and Twitter. diff --git a/debian/patches/bitlbee.conf.diff b/debian/patches/bitlbee.conf.diff index c98fa546..339ccd4a 100644 --- a/debian/patches/bitlbee.conf.diff +++ b/debian/patches/bitlbee.conf.diff @@ -1,5 +1,5 @@ ---- debian/bitlbee/etc/bitlbee/bitlbee.conf 2009-06-01 00:20:24.000000000 +0100 -+++ debian/bitlbee/etc/bitlbee/bitlbee.conf 2009-06-07 21:16:19.000000000 +0100 +--- bitlbee.conf 2009-06-01 00:20:24.000000000 +0100 ++++ bitlbee.conf 2009-06-07 21:16:19.000000000 +0100 @@ -23,13 +23,18 @@ ## If BitlBee is started by root as a daemon, it can drop root privileges, ## and change to the specified user. diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in index cef83a34..8d2b570f 100644 --- a/debian/po/POTFILES.in +++ b/debian/po/POTFILES.in @@ -1 +1 @@ -[type: gettext/rfc822deb] templates +[type: gettext/rfc822deb] bitlbee-common.templates diff --git a/debian/rules b/debian/rules index ae6463fc..9736e078 100755 --- a/debian/rules +++ b/debian/rules @@ -1,113 +1,104 @@ #!/usr/bin/make -f - +# +# Finally switching to debhelper. +# +# Not using debhelper was an exercise suggested to me by my AM (Gergely +# Nagy). It was educating at the time but I finally decided that the +# exercise is over now. +# + +BITLBEE_CONFIGURE_FLAGS ?= DEBUG ?= 0 -ifdef BITLBEE_VERSION -BITLBEE_FORCE_VERSION=1 -else +ifndef BITLBEE_VERSION # Want to use the full package version number instead of just the release. -BITLBEE_VERSION ?= "$(shell dpkg-parsechangelog | grep ^Version: | awk '{print $$2}')" -export BITLBEE_VERSION +BITLBEE_CONFIGURE_VERSION ?= BITLBEE_VERSION=\"$(shell dpkg-parsechangelog | grep ^Version: | awk '{print $$2}')\" endif -build-arch: build-arch-stamp -build-arch-stamp: - [ -d debian ] - ./configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --events=libevent - $(MAKE) -# $(MAKE) -C doc/ all - touch build-arch-stamp +build: build-stamp +build-stamp: + dh_testdir -clean: - [ "`whoami`" = "root" -a -d debian ] - rm -rf build-arch-stamp debian/bitlbee debian/*.substvars debian/files debian/bitlbee-dev - $(MAKE) distclean -# -$(MAKE) -C doc/ clean - - -install-arch: build-arch - [ "`whoami`" = "root" -a -d debian ] - mkdir -p debian/bitlbee/DEBIAN/ - $(MAKE) install install-etc DESTDIR=`pwd`/debian/bitlbee - - mkdir -p debian/bitlbee/usr/share/doc/bitlbee/ - cp doc/user-guide/user-guide.txt debian/bitlbee/usr/share/doc/bitlbee/ - cp doc/user-guide/user-guide.html debian/bitlbee/usr/share/doc/bitlbee/ - -install-indep: install-arch - [ "`whoami`" = "root" -a -d debian ] - mkdir -p debian/bitlbee-dev/DEBIAN/ - $(MAKE) install-dev DESTDIR=`pwd`/debian/bitlbee-dev - - mkdir -p debian/bitlbee-dev/usr/share/doc/bitlbee-dev/ - -binary-arch: build-arch install-arch - [ "`whoami`" = "root" -a -d debian ] - - chmod 755 debian/post* debian/pre* debian/config debian/bitlbee.init - - mkdir -p debian/bitlbee/usr/share/doc/bitlbee/examples/ debian/bitlbee/etc/init.d/ - -cp doc/RELEASE-SPEECH* debian/bitlbee/usr/share/doc/bitlbee/ && gzip -9 debian/bitlbee/usr/share/doc/bitlbee/RELEASE-SPEECH* - cp doc/CREDITS doc/AUTHORS doc/README doc/FAQ debian/README.Debian debian/bitlbee/usr/share/doc/bitlbee/ - cp debian/changelog debian/bitlbee/usr/share/doc/bitlbee/changelog.Debian - cp debian/copyright debian/bitlbee/usr/share/doc/bitlbee/copyright - cp doc/CHANGES debian/bitlbee/usr/share/doc/bitlbee/changelog - cp utils/* debian/bitlbee/usr/share/doc/bitlbee/examples/ - cp debian/bitlbee.init debian/bitlbee/etc/init.d/bitlbee - patch -p0 < debian/patches/bitlbee.conf.diff - cd debian/bitlbee/usr/share/; \ - gzip -9 doc/bitlbee/changelog.Debian doc/bitlbee/changelog doc/bitlbee/user-guide.txt \ - doc/bitlbee/examples/* man/man8/bitlbee.8 man/man5/bitlbee.conf.5 - - chown -R root:root debian/bitlbee/ - find debian/bitlbee/usr/share/ -type d -exec chmod 755 {} \; - find debian/bitlbee/usr/share/ -type f -exec chmod 644 {} \; - - cp debian/prerm debian/bitlbee/DEBIAN/ - cp debian/postinst debian/bitlbee/DEBIAN/ - cp debian/postrm debian/bitlbee/DEBIAN/ - cp debian/config debian/bitlbee/DEBIAN/ - - po2debconf debian/templates > debian/bitlbee/DEBIAN/templates - cp debian/conffiles debian/bitlbee/DEBIAN/ - - if [ "$(DEBUG)" = "0" ]; then strip -R .comment -R .note debian/bitlbee/usr/sbin/bitlbee; fi - - cd debian/bitlbee; \ - find usr -type f -exec md5sum {} \; > DEBIAN/md5sums - dpkg-shlibdeps -Tdebian/bitlbee.substvars -dDepends debian/bitlbee/usr/sbin/bitlbee -ifdef BITLBEE_FORCE_VERSION - dpkg-gencontrol -ldebian/changelog -isp -pbitlbee -Tdebian/bitlbee.substvars -Pdebian/bitlbee -v1:$(BITLBEE_VERSION)-0 -V'debconf-depends=debconf (>= 1.2.0) | debconf-2.0' -else - dpkg-gencontrol -ldebian/changelog -isp -pbitlbee -Tdebian/bitlbee.substvars -Pdebian/bitlbee -V'debconf-depends=debconf (>= 1.2.0) | debconf-2.0' -endif + mkdir -p debian/build-native + ROOT=$$PWD; cd debian/build-native; $(BITLBEE_CONFIGURE_VERSION) $$ROOT/configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --events=libevent $(BITLBEE_CONFIGURE_FLAGS) + $(MAKE) -C debian/build-native - dpkg --build debian/bitlbee .. + mkdir -p debian/build-libpurple + ROOT=$$PWD; cd debian/build-libpurple; $(BITLBEE_CONFIGURE_VERSION) $$ROOT/configure --debug=$(DEBUG) --prefix=/usr --etcdir=/etc/bitlbee --purple=1 $(BITLBEE_CONFIGURE_FLAGS) + $(MAKE) -C debian/build-libpurple -binary-indep: install-indep - [ "`whoami`" = "root" -a -d debian ] + $(MAKE) -C doc - chown -R root.root debian/bitlbee-dev/ - find debian/bitlbee-dev/usr/share/ -type d -exec chmod 755 {} \; - find debian/bitlbee-dev/usr/share/ -type f -exec chmod 644 {} \; + touch build-stamp - cp debian/changelog debian/bitlbee-dev/usr/share/doc/bitlbee-dev/changelog.Debian - gzip -9 debian/bitlbee-dev/usr/share/doc/bitlbee-dev/changelog.Debian - cp debian/copyright debian/bitlbee-dev/usr/share/doc/bitlbee-dev/copyright +clean: + dh_testdir + dh_testroot + rm -f build-stamp - cd debian/bitlbee-dev; \ - find usr -type f -exec md5sum {} \; > DEBIAN/md5sums + rm -rf build-arch-stamp debian/build-* + $(MAKE) distclean -ifdef BITLBEE_FORCE_VERSION - dpkg-gencontrol -ldebian/changelog -isp -pbitlbee-dev -Pdebian/bitlbee-dev -v1:$(BITLBEE_VERSION)-0 + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + $(MAKE) -C debian/build-native install install-etc DESTDIR=`pwd`/debian/bitlbee + $(MAKE) -C debian/build-libpurple install install-etc DESTDIR=`pwd`/debian/bitlbee-libpurple + $(MAKE) -C debian/build-native install-dev DESTDIR=`pwd`/debian/bitlbee-dev + + patch debian/bitlbee/etc/bitlbee/bitlbee.conf debian/patches/bitlbee.conf.diff + patch debian/bitlbee-libpurple/etc/bitlbee/bitlbee.conf debian/patches/bitlbee.conf.diff + + mkdir -p debian/bitlbee-common/usr + mv debian/bitlbee/usr/share debian/bitlbee-common/usr + rm -rf debian/bitlbee-libpurple/usr/share + +binary-common: + dh_testdir + dh_testroot + + dh_installchangelogs doc/CHANGES + dh_installexamples + dh_installdocs #--link-doc=bitlbee-common + # TODO: Restore --link-doc up here and remove the hack below once + # Hardy and Lenny are deprecated. + for p in bitlbee bitlbee-libpurple bitlbee-dev; do rm -rf debian/$$p/usr/share/doc/$$p; ln -s bitlbee-common debian/$$p/usr/share/doc/$$p; done + dh_installdebconf + dh_installinit +ifeq ($(DH_OPTIONS),-a) + cp -a debian/bitlbee/etc debian/bitlbee-libpurple +endif + dh_installman + dh_strip + dh_link + dh_compress + dh_fixperms + dh_installdeb +ifeq ($(DH_OPTIONS),-a) + cp -a debian/bitlbee/DEBIAN/post* debian/bitlbee/DEBIAN/pre* debian/bitlbee-libpurple/DEBIAN +endif + dh_shlibdeps +ifdef BITLBEE_VERSION + dh_gencontrol -- -v1:$(BITLBEE_VERSION)-0 -Vbee:Version=1:$(BITLBEE_VERSION)-0 else - dpkg-gencontrol -ldebian/changelog -isp -pbitlbee-dev -Pdebian/bitlbee-dev + dh_gencontrol -- -Vbee:Version=$(shell dpkg-parsechangelog | grep ^Version: | awk '{print $$2}' | sed -e 's/+[^+]*$$//') endif + dh_md5sums + dh_builddeb + +binary-indep: build install + $(MAKE) -f debian/rules DH_OPTIONS=-i binary-common - dpkg --build debian/bitlbee-dev .. +binary-arch: build install + $(MAKE) -f debian/rules DH_OPTIONS=-a binary-common -binary: binary-arch binary-indep -build: build-arch -install: install-arch install-indep +binary-%: build install + make -f debian/rules binary-common DH_OPTIONS=-p$* -.PHONY: build-arch build clean binary-arch binary install-arch install binary-indep install-indep +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary-common binary install diff --git a/doc/CHANGES b/doc/CHANGES index 25db8a70..0ea6ebe0 100644 --- a/doc/CHANGES +++ b/doc/CHANGES @@ -3,6 +3,53 @@ found in the bzr commit logs, for example you can try: http://bugs.bitlbee.org/bitlbee/timeline?daysback=90&changeset=on +Version 1.3dev: +- Loads of new stuff, mostly ready for a new major release, but starting with + a dev snapshot. A few changes that were planned for a long time already: +- 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. + * 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. + * 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. + * When using this variant, type "help purple" to get a list of supported + protocols. +- 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. + +Finished ... 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 diff --git a/doc/Makefile b/doc/Makefile index 9b473df3..5f59879e 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -1,4 +1,7 @@ -include ../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)doc/ +endif all: # Only build the docs if this is a bzr checkout @@ -6,8 +9,8 @@ all: install: mkdir -p $(DESTDIR)$(MANDIR)/man8/ $(DESTDIR)$(MANDIR)/man5/ - install -m 0644 bitlbee.8 $(DESTDIR)$(MANDIR)/man8/ - install -m 0644 bitlbee.conf.5 $(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: diff --git a/doc/user-guide/Makefile b/doc/user-guide/Makefile index 9841de8d..2a80ea6c 100644 --- a/doc/user-guide/Makefile +++ b/doc/user-guide/Makefile @@ -1,4 +1,8 @@ -include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)doc/user-guide/ +endif + EXTRAPARANEWLINE = 1 # EXTRAPARANEWLINE = 0 @@ -37,7 +41,7 @@ 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 help.txt $(DESTDIR)$(DATADIR)/help.txt + install -m 0644 $(SRCDIR)help.txt $(DESTDIR)$(DATADIR)/help.txt uninstall: rm -f $(DESTDIR)$(DATADIR)/help.txt diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml index 0cef37ce..982e122e 100644 --- a/doc/user-guide/commands.xml +++ b/doc/user-guide/commands.xml @@ -5,7 +5,7 @@ <bitlbee-command name="account"> <short-description>IM-account list maintenance</short-description> - <syntax>account <action> [<arguments>]</syntax> + <syntax>account [<account id>] <action> [<arguments>]</syntax> <description> @@ -98,7 +98,7 @@ </bitlbee-command> <bitlbee-command name="del"> - <syntax>account del <account id></syntax> + <syntax>account <account id> del</syntax> <description> <para> @@ -107,13 +107,13 @@ <para> - The account ID can be a number (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection. + The account ID can be a number/tag (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection. </para> </description> </bitlbee-command> <bitlbee-command name="on"> - <syntax>account on [<account id>]</syntax> + <syntax>account [<account id>] on</syntax> <description> <para> @@ -121,14 +121,14 @@ </para> <para> - The account ID can be a number (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection. + The account ID can be a number/tag (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection. </para> </description> </bitlbee-command> <bitlbee-command name="off"> - <syntax>account off [<account id>]</syntax> + <syntax>account [<account id>] off</syntax> <description> <para> @@ -136,7 +136,7 @@ </para> <para> - The account ID can be a number (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection. + The account ID can be a number/tag (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection. </para> </description> </bitlbee-command> @@ -152,14 +152,14 @@ </bitlbee-command> <bitlbee-command name="set"> - <syntax>account set <account id></syntax> - <syntax>account set <account id>/<setting></syntax> - <syntax>account set <account id>/<setting> <value></syntax> - <syntax>account set -del <account id>/<setting></syntax> + <syntax>account <account id> set</syntax> + <syntax>account <account id> set <setting></syntax> + <syntax>account <account id> set <setting> <value></syntax> + <syntax>account <account id> set -del <setting></syntax> <description> <para> - 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 <emphasis>account set <account id></emphasis>. + 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 <emphasis>account <account id> set</emphasis>. </para> <para> @@ -167,94 +167,108 @@ </para> <para> - The account ID can be a number (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection. + The account ID can be a number/tag (see <emphasis>account list</emphasis>), the protocol name or (part of) the screenname, as long as it matches only one connection. </para> </description> </bitlbee-command> </bitlbee-command> - <bitlbee-command name="chat"> - <short-description>Chatroom list maintenance</short-description> - <syntax>chat <action> [<arguments>]</syntax> + <bitlbee-command name="channel"> + <short-description>Channel list maintenance</short-description> + <syntax>channel [<account id>] <action> [<arguments>]</syntax> <description> - <para> - Available actions: add, del, list, with and set. See <emphasis>help chat <action></emphasis> for more information. + Available actions: del, list, set. See <emphasis>help chat <action></emphasis> for more information. + </para> + + <para> + There is no <emphasis>channel add</emphasis> command. To create a new channel, just use the IRC <emphasis>/join</emphasis> command. See also <emphasis>help channels</emphasis> and <emphasis>help groupchats</emphasis>. </para> - </description> - <bitlbee-command name="add"> - <syntax>chat add <account> <room> [<channel>]</syntax> + <bitlbee-command name="del"> + <syntax>channel <channel id> del</syntax> <description> <para> - 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. + 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.) </para> + </description> - <para> - 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 <emphasis>chat set</emphasis>) - </para> + </bitlbee-command> + + <bitlbee-command name="list"> + <syntax>channel list</syntax> + <description> <para> - Password-protected rooms work exactly like on IRC, by passing the password as an extra argument to /join. + This command gives you a list of all the channels you configured. </para> </description> </bitlbee-command> - <bitlbee-command name="del"> - <syntax>chat del <chat id></syntax> + <bitlbee-command name="set"> + <syntax>channel [<channel id>] set</syntax> + <syntax>channel [<channel id>] set <setting></syntax> + <syntax>channel [<channel id>] set <setting> <value></syntax> + <syntax>channel [<channel id>] set -del <setting></syntax> <description> <para> - This commands deletes an chatroom from your list. + 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 <emphasis>channel <channel id> set</emphasis>. </para> - + + <para> + For more infomation about a setting, see <emphasis>help set <setting></emphasis>. + </para> + <para> - The room ID can be a number (see <emphasis>chat list</emphasis>), or (part of) the name of the room/channel. + The channel ID can be a number (see <emphasis>channel list</emphasis>), 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. </para> </description> </bitlbee-command> - <bitlbee-command name="list"> - <syntax>chat list</syntax> + </bitlbee-command> + + <bitlbee-command name="chat"> + <short-description>Chatroom list maintenance</short-description> + <syntax>chat <action> [<arguments>]</syntax> + + <description> + + <para> + Available actions: add, with. See <emphasis>help chat <action></emphasis> for more information. + </para> + + </description> + + <bitlbee-command name="add"> + <syntax>chat add <account id> <room> [<channel>]</syntax> <description> <para> - This command gives you a list of all the chatrooms known by BitlBee. + 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. </para> - </description> - </bitlbee-command> - <bitlbee-command name="with"> - <syntax>chat with <nickname></syntax> + <para> + 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 <emphasis>chat set</emphasis>) + </para> - <description> <para> - While most <emphasis>chat</emphasis> subcommands are about named chatrooms, this command can be used to open an unnamed groupchat with one or more persons. This command is what <emphasis>/join #nickname</emphasis> used to do in older BitlBee versions. + Password-protected rooms work exactly like on IRC, by passing the password as an extra argument to /join. </para> </description> + </bitlbee-command> - <bitlbee-command name="set"> - <syntax>chat set <chat id></syntax> - <syntax>chat set <chat id>/<setting></syntax> - <syntax>chat set <chat id>/<setting> <value></syntax> - <syntax>chat set -del <chat id>/<setting></syntax> + <bitlbee-command name="with"> + <syntax>chat with <nickname></syntax> <description> <para> - This command can be used to change various settings for chatrooms. - </para> - - <para> - For more infomation about a setting, see <emphasis>help set <setting></emphasis>. - </para> - - <para> - The room ID can be a number (see <emphasis>chat list</emphasis>), or (part of) the name of the room/channel. + While most <emphasis>chat</emphasis> subcommands are about named chatrooms, this command can be used to open an unnamed groupchat with one or more persons. This command is what <emphasis>/join #nickname</emphasis> used to do in older BitlBee versions. </para> </description> </bitlbee-command> @@ -262,8 +276,8 @@ <bitlbee-command name="add"> <short-description>Add a buddy to your contact list</short-description> - <syntax>add <connection> <handle> [<nick>]</syntax> - <syntax>add -tmp <connection> <handle> [<nick>]</syntax> + <syntax>add <account id> <handle> [<nick>]</syntax> + <syntax>add -tmp <account id> <handle> [<nick>]</syntax> <description> <para> @@ -273,6 +287,10 @@ <para> 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 <emphasis>set handle_unknown add</emphasis>). This allows you to talk to people who are not in your contact list. This normally won't show you any presence notifications. </para> + + <para> + 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. + </para> </description> <ircexample> @@ -397,6 +415,25 @@ </description> </bitlbee-command> + <bitlbee-setting name="account" type="string" scope="channel"> + + <description> + <para> + For control channels with <emphasis>fill_by</emphasis> set to <emphasis>account</emphasis>: 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. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="allow_takeover" type="boolean" scope="global"> + <default>true</default> + + <description> + <para> + 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. + </para> + </description> + </bitlbee-setting> + <bitlbee-setting name="auto_connect" type="boolean" scope="both"> <default>true</default> @@ -411,12 +448,12 @@ </description> </bitlbee-setting> - <bitlbee-setting name="auto_join" type="boolean" scope="chat"> + <bitlbee-setting name="auto_join" type="boolean" scope="channel"> <default>false</default> <description> <para> - With this option enabled, BitlBee will automatically join this chatroom when you log in. + With this option enabled, BitlBee will automatically join this channel when you log in. </para> </description> </bitlbee-setting> @@ -480,56 +517,41 @@ <para> 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. </para> - </description> - </bitlbee-setting> - - <bitlbee-setting name="base_url" type="string" scope="account"> - <default>http://twitter.com</default> - - <description> - <para> - There are more services that understand the Twitter API than just Twitter.com. BitlBee can connect to all Twitter API implementations. - </para> - - <para> - For example, set this setting to <emphasis>http://identi.ca/api</emphasis> to use Identi.ca. - </para> - + <para> - Keep two things in mind: When not using Twitter, you <emphasis>must</emphasis> also disable the <emphasis>oauth</emphasis> setting as it currently only works with Twitter. If you're still having issues, make sure there is <emphasis>no</emphasis> slash at the end of the URL you enter here. + Replaced with the <emphasis>show_users</emphasis> setting. See <emphasis>help show_users</emphasis>. </para> </description> </bitlbee-setting> - <bitlbee-setting name="buddy_sendbuffer" type="boolean" scope="global"> - <default>false</default> + <bitlbee-setting name="away_reply_timeout" type="integer" scope="global"> + <default>3600</default> <description> <para> - 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. + 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. </para> <para> - Using the <emphasis>buddy_sendbuffer_delay</emphasis> setting you can specify the number of seconds BitlBee should wait for more data before the complete message is sent. - </para> - - <para> - 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. + 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. </para> </description> </bitlbee-setting> - <bitlbee-setting name="buddy_sendbuffer_delay" type="integer" scope="global"> - <default>200</default> + <bitlbee-setting name="base_url" type="string" scope="account"> + <default>http://twitter.com</default> <description> + <para> + There are more services that understand the Twitter API than just Twitter.com. BitlBee can connect to all Twitter API implementations. + </para> <para> - 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. + For example, set this setting to <emphasis>http://identi.ca/api</emphasis> to use Identi.ca. </para> <para> - See also the <emphasis>buddy_sendbuffer</emphasis> setting. + Keep two things in mind: When not using Twitter, you <emphasis>must</emphasis> also disable the <emphasis>oauth</emphasis> setting as it currently only works with Twitter. If you're still having issues, make sure there is <emphasis>no</emphasis> slash at the end of the URL you enter here. </para> </description> </bitlbee-setting> @@ -560,6 +582,25 @@ </description> </bitlbee-setting> + <bitlbee-setting name="chat_type" type="string" scope="channel"> + <default>groupchat</default> + <possible-values>groupchat, room</possible-values> + + <description> + <para> + 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. + </para> + + <para> + BitlBee supports both types. With this setting set to <emphasis>groupchat</emphasis> (the default), you can just invite people into the room and start talking. + </para> + + <para> + For setting up named chatrooms, it's currently easier to just use the <emphasis>chat add</emphasis> command. + </para> + </description> + </bitlbee-setting> + <bitlbee-setting name="debug" type="boolean" scope="global"> <default>false</default> @@ -609,8 +650,40 @@ </description> </bitlbee-setting> + <bitlbee-setting name="fill_by" type="string" scope="channel"> + <default>all</default> + <possible-values>all, group, account, protocol</possible-values> + + <description> + <para> + For control channels only: This setting determines which contacts the channel gets populated with. + </para> + + <para> + By default, control channels will contain all your contacts. You instead select contacts by buddy group, IM account or IM protocol. + </para> + + <para> + Change this setting and the corresponding <emphasis>account</emphasis>/<emphasis>group</emphasis>/<emphasis>protocol</emphasis> setting to set up this selection. + </para> + + <para> + Note that, when creating a new channel, BitlBee will try to preconfigure the channel for you, based on the channel name. See <emphasis>help channels</emphasis>. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="group" type="string" scope="channel"> + + <description> + <para> + For control channels with <emphasis>fill_by</emphasis> set to <emphasis>group</emphasis>: Set this setting to the name of the group containing the contacts you want to see in this channel. + </para> + </description> + </bitlbee-setting> + <bitlbee-setting name="handle_unknown" type="string" scope="global"> - <default>root</default> + <default>add_channel</default> <possible-values>root, add, add_private, add_channel, ignore</possible-values> <description> @@ -716,7 +789,6 @@ </bitlbee-setting> <bitlbee-setting name="nick" type="string" scope="chat"> - <description> <para> 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. @@ -724,6 +796,36 @@ </description> </bitlbee-setting> + <bitlbee-setting name="nick_format" type="string" scope="both"> + <default>%-@nick</default> + + <description> + <para> + 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. + </para> + + <para> + 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. + </para> + + <para> + It's easier to describe this setting using a few examples: + </para> + + <para> + 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. + </para> + + <para> + [%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. + </para> + + <para> + See <emphasis>help nick_format</emphasis> for more information. + </para> + </description> + </bitlbee-setting> + <bitlbee-setting name="nick_source" type="string" scope="account"> <default>handle</default> <possible-values>handle, full_name, first_name</possible-values> @@ -788,6 +890,39 @@ </para> </description> </bitlbee-setting> + + <bitlbee-setting name="paste_buffer" type="boolean" scope="global"> + <default>false</default> + + <description> + <para> + 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. + </para> + + <para> + Using the <emphasis>paste_buffer_delay</emphasis> setting you can specify the number of seconds BitlBee should wait for more data before the complete message is sent. + </para> + + <para> + 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. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="paste_buffer_delay" type="integer" scope="global"> + <default>200</default> + + <description> + + <para> + 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. + </para> + + <para> + See also the <emphasis>paste_buffer</emphasis> setting. + </para> + </description> + </bitlbee-setting> <bitlbee-setting name="port" type="integer" scope="account"> <description> @@ -825,6 +960,15 @@ </description> </bitlbee-setting> + <bitlbee-setting name="protocol" type="string" scope="channel"> + + <description> + <para> + For control channels with <emphasis>fill_by</emphasis> set to <emphasis>protocol</emphasis>: Set this setting to the name of the IM protocol of all contacts you want to see in this channel. + </para> + </description> + </bitlbee-setting> + <bitlbee-setting name="query_order" type="string" scope="global"> <default>lifo</default> <possible-values>lifo, fifo</possible-values> @@ -900,6 +1044,32 @@ <para> 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. </para> + + <para> + Replaced with the <emphasis>show_users</emphasis> setting. See <emphasis>help show_users</emphasis>. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="show_users" type="string" scope="channel"> + <default>online+,away</default> + + <description> + <para> + Comma-separated list of statuses of users you want in the channel, + and any modes they should have. The following statuses are currently + recognised: <emphasis>online</emphasis> (i.e. available, not + away), <emphasis>away</emphasis>, and <emphasis>offline</emphasis>. + </para> + + <para> + If a status is followed by a valid channel mode character + (@, % or +), it will be given to users with that status. + For example, <emphasis>online@,away+,offline</emphasis> 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. + </para> </description> </bitlbee-setting> @@ -970,6 +1140,18 @@ </description> </bitlbee-setting> + <bitlbee-setting name="tag" type="string" scope="account"> + <description> + <para> + 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 <emphasis>account</emphasis>, <emphasis>add</emphasis>, etc. You can't have two accounts with one and the same account tag. + </para> + + <para> + 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. + </para> + </description> + </bitlbee-setting> + <bitlbee-setting name="timezone" type="string" scope="global"> <default>local</default> <possible-values>local, utc, gmt, timezone-spec</possible-values> @@ -1013,6 +1195,35 @@ </description> </bitlbee-setting> + <bitlbee-setting name="translate_to_nicks" type="boolean" scope="channel"> + <default>true</default> + + <description> + <para> + 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. + </para> + + <para> + 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. + </para> + </description> + </bitlbee-setting> + + <bitlbee-setting name="type" type="string" scope="channel"> + <default>control</default> + <possible-values>control, chat</possible-values> + + <description> + <para> + BitlBee supports two kinds of channels: control channels (usually with a name starting with a &) and chatroom channels (name usually starts with a #). + </para> + + <para> + See <emphasis>help channels</emphasis> for a full description of channel types in BitlBee. + </para> + </description> + </bitlbee-setting> + <bitlbee-setting name="typing_notice" type="boolean" scope="global"> <default>false</default> @@ -1145,7 +1356,7 @@ </bitlbee-command> <bitlbee-command name="identify"> - <syntax>identify <password></syntax> + <syntax>identify [-noload|-force] <password></syntax> <short-description>Identify yourself with your password</short-description> <description> @@ -1156,6 +1367,14 @@ <para> Once you're registered, you can change your password using <emphasis>set password <password></emphasis>. </para> + + <para> + The <emphasis>-noload</emphasis> and <emphasis>-force</emphasis> flags can be used to identify when you're logged into some IM accounts already. <emphasis>-force</emphasis> will let you identify yourself and load all saved accounts (and keep the accounts you're logged into already). + </para> + + <para> + <emphasis>-noload</emphasis> 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). + </para> </description> </bitlbee-command> @@ -1182,21 +1401,57 @@ </bitlbee-command> - <bitlbee-command name="nick"> - <short-description>Change friendly name, nick</short-description> - <syntax>nick <connection> [<new nick>]</syntax> - <syntax>nick <connection></syntax> + <bitlbee-command name="group"> + <short-description>Contact group management</short-description> + <syntax>group list</syntax> <description> <para> - Deprecated: Use the per-account <emphasis>display_name</emphasis> setting to read and change this information. + Only the <emphasis>group list</emphasis> command is supported at the moment, which shows a list of all groups defined so far. </para> </description> + </bitlbee-command> + + <bitlbee-command name="transfers"> + <short-description>Monitor, cancel, or reject file transfers</short-description> + <syntax>transfers [<cancel> id | <reject>]</syntax> + + <description> + <para> + Without parameters the currently pending file transfers and their status will be listed. Available actions are <emphasis>cancel</emphasis> and <emphasis>reject</emphasis>. See <emphasis>help transfers <action></emphasis> for more information. + </para> - <ircexample> - <ircline nick="wouter">account set 1/display_name "The majestik møøse"</ircline> - <ircline nick="root">display_name = `The majestik møøse'</ircline> - </ircexample> + <ircexample> + <ircline nick="ulim">transfers</ircline> + </ircexample> + </description> + + <bitlbee-command name="cancel"> + <short-description>Cancels the file transfer with the given id</short-description> + <syntax>transfers <cancel> id</syntax> + + <description> + <para>Cancels the file transfer with the given id</para> + </description> + + <ircexample> + <ircline nick="ulim">transfers cancel 1</ircline> + <ircline nick="root">Canceling file transfer for test</ircline> + </ircexample> + </bitlbee-command> + <bitlbee-command name="reject"> + <short-description>Rejects all incoming transfers</short-description> + <syntax>transfers <reject></syntax> + + <description> + <para>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?</para> + </description> + + <ircexample> + <ircline nick="ulim">transfers reject</ircline> + </ircexample> + </bitlbee-command> </bitlbee-command> + </chapter> diff --git a/doc/user-guide/help.xml b/doc/user-guide/help.xml index 7487a841..48ed8a48 100644 --- a/doc/user-guide/help.xml +++ b/doc/user-guide/help.xml @@ -13,9 +13,11 @@ These are the available help subjects: <variablelist> <varlistentry><term>quickstart</term><listitem><para>A short introduction into BitlBee</para></listitem></varlistentry> <varlistentry><term>commands</term><listitem><para>All available commands and settings</para></listitem></varlistentry> + <varlistentry><term>channels</term><listitem><para>About creating and customizing channels</para></listitem></varlistentry> <varlistentry><term>away</term><listitem><para>About setting away states</para></listitem></varlistentry> - <varlistentry><term>smileys</term><listitem><para>A summary of some non-standard smileys you might find and fail to understand</para></listitem></varlistentry> <varlistentry><term>groupchats</term><listitem><para>How to work with groupchats on BitlBee</para></listitem></varlistentry> + <varlistentry><term>nick_changes</term><listitem><para>Changing your nickname without losing any settings</para></listitem></varlistentry> + <varlistentry><term>smileys</term><listitem><para>A summary of some non-standard smileys you might find and fail to understand</para></listitem></varlistentry> </variablelist> <para> @@ -42,9 +44,11 @@ These are the available help subjects: <variablelist> <varlistentry><term>quickstart</term><listitem><para>A short introduction into BitlBee</para></listitem></varlistentry> <varlistentry><term>commands</term><listitem><para>All available commands and settings</para></listitem></varlistentry> + <varlistentry><term>channels</term><listitem><para>About creating and customizing channels</para></listitem></varlistentry> <varlistentry><term>away</term><listitem><para>About setting away states</para></listitem></varlistentry> - <varlistentry><term>smileys</term><listitem><para>A summary of some non-standard smileys you might find and fail to understand</para></listitem></varlistentry> <varlistentry><term>groupchats</term><listitem><para>How to work with groupchats on BitlBee</para></listitem></varlistentry> + <varlistentry><term>nick_changes</term><listitem><para>Changing your nickname without losing any settings</para></listitem></varlistentry> + <varlistentry><term>smileys</term><listitem><para>A summary of some non-standard smileys you might find and fail to understand</para></listitem></varlistentry> </variablelist> <para> diff --git a/doc/user-guide/misc.xml b/doc/user-guide/misc.xml index a926775a..330e18bb 100644 --- a/doc/user-guide/misc.xml +++ b/doc/user-guide/misc.xml @@ -116,4 +116,120 @@ If you want to set an away state for only one of your connections, you can use t </sect1> +<sect1 id="nick_changes"> +<title>Changing your nickname</title> + +<para> +BitlBee now allows you to change your nickname. So far this was not possible because it made managing saved accounts more complicated. +</para> + +<para> +The restriction no longer exists now though. When you change your nick (just using the <emphasis>/nick</emphasis> command), your logged-in status will be reset, which means any changes made to your settings/accounts will not be saved. +</para> + +<para> +To restore your logged-in status, you need to either use the <emphasis>register</emphasis> command to create an account under the new nickname, or use <emphasis>identify -noload</emphasis> to re-identify yourself under the new nickname. The <emphasis>-noload</emphasis> flag tells the command to verify your password and log you in, but not load any new settings. See <emphasis>help identify</emphasis> for more information. +</para> + +</sect1> + +<sect1 id="channels"> +<title>Dealing with channels</title> + +<para> +You can have as many channels in BitlBee as you want. You maintain your channel list using the <emphasis>channel</emphasis> command. You can create new channels by just joining them, like on regular IRC networks. +</para> + +<para> +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 #. +</para> + +<para> +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. +</para> + +<para> +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". +</para> + +<para> +Type <emphasis>help channels2</emphasis> to read more. +</para> + +</sect1> + +<sect1 id="channels2"> +<title>Creating a channel</title> + +<para> +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. +</para> + +<para> +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. +</para> + +<para> +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. +</para> + +<para> +If you want to configure your own channels, you can use the <emphasis>channel set</emphasis>. +</para> + +</sect1> + +<sect1 id="nick_format"> +<title>Nickname formatting</title> + +<para> +The <emphasis>nick_format</emphasis> setting can be set globally using +the <emphasis>set</emphasis> command, or per account using <emphasis>account +set</emphasis> (so that you can set a per-account suffix/prefix or have +nicknames generated from full names for certain accounts). +</para> + +<para> +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: +</para> + +<variablelist> + <varlistentry><term>%nick</term><listitem><para>Nickname suggested for this contact by the IM protocol, or just the handle if no nickname was suggested.</para></listitem></varlistentry> + <varlistentry><term>%handle</term><listitem><para>The handle/screenname of the contact.</para></listitem></varlistentry> + <varlistentry><term>%full_name</term><listitem><para>The full name of the contact.</para></listitem></varlistentry> + <varlistentry><term>%first_name</term><listitem><para>The first name of the contact (the full name up to the first space).</para></listitem></varlistentry> + <varlistentry><term>%group</term><listitem><para>The name of the group this contact is a member of</para></listitem></varlistentry> +</variablelist> + +<para> +Invalid characters (like spaces) will always be stripped. Depending on your +locale settings, characters with accents will be converted to ASCII. +</para> + +<para> +See <emphasis>set nick_format2</emphasis> for some more information. +</para> + +</sect1> + +<sect1 id="nick_format2"> +<title>Nickname formatting - modifiers</title> + +<para> +Two modifiers ares currently available: You can include only the first few +characters of a variable by putting a number right after the %. For +example, <emphasis>[%3group]%-@nick</emphasis> will include only the first +three characters of the group name in the nick. +</para> + +<para> +Also, you can truncate variables from a certain character using +the <emphasis>-</emphasis> modifier. For example, you may want to leave out +everything after the @. <emphasis>%-@handle</emphasis> will expand to +everything in the handle up to the first @. +</para> + +</sect1> + </chapter> @@ -1,7 +1,7 @@ /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * - * Copyright 2002-2005 Wilmer van der Gaast and others * + * Copyright 2002-2009 Wilmer van der Gaast and others * \********************************************************************/ /* Help file control */ @@ -168,3 +168,27 @@ char *help_get( help_t **help, char *title ) 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; +} @@ -45,5 +45,6 @@ typedef struct help 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_ ); #endif @@ -1,7 +1,7 @@ /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * - * Copyright 2002-2006 Wilmer van der Gaast and others * + * Copyright 2002-2010 Wilmer van der Gaast and others * \********************************************************************/ /* IPC - communication between BitlBee processes */ @@ -28,10 +28,15 @@ #include "ipc.h" #include "commands.h" #ifndef _WIN32 +#include <sys/uio.h> #include <sys/un.h> #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 ); static void ipc_master_cmd_client( irc_t *data, char **cmd ) { @@ -48,11 +53,23 @@ static void ipc_master_cmd_client( irc_t *data, char **cmd ) 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 ) @@ -111,9 +128,105 @@ void ipc_master_cmd_restart( irc_t *data, char **cmd ) 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( 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 }, @@ -122,6 +235,8 @@ static const command_t ipc_master_commands[] = { { "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 } }; @@ -137,7 +252,7 @@ static void ipc_child_cmd_wallops( irc_t *irc, char **cmd ) return; if( strchr( irc->umode, 'w' ) ) - irc_write( irc, ":%s WALLOPS :%s", irc->myhost, cmd[1] ); + irc_write( irc, ":%s WALLOPS :%s", irc->root->host, cmd[1] ); } static void ipc_child_cmd_wall( irc_t *irc, char **cmd ) @@ -146,7 +261,7 @@ static void ipc_child_cmd_wall( irc_t *irc, char **cmd ) return; if( strchr( irc->umode, 's' ) ) - irc_write( irc, ":%s NOTICE %s :%s", irc->myhost, irc->nick, cmd[1] ); + 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 ) @@ -155,7 +270,7 @@ static void ipc_child_cmd_opermsg( irc_t *irc, char **cmd ) return; if( strchr( irc->umode, 'o' ) ) - irc_write( irc, ":%s NOTICE %s :*** OperMsg *** %s", irc->myhost, irc->nick, cmd[1] ); + 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 ) @@ -175,10 +290,10 @@ static void ipc_child_cmd_kill( irc_t *irc, char **cmd ) if( !( irc->status & USTATUS_LOGGED_IN ) ) return; - if( nick_cmp( cmd[1], irc->nick ) != 0 ) + if( nick_cmp( cmd[1], irc->user->nick ) != 0 ) return; /* It's not for us. */ - irc_write( irc, ":%s!%s@%s KILL %s :%s", irc->mynick, irc->mynick, irc->myhost, irc->nick, cmd[2] ); + 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] ); } @@ -187,7 +302,134 @@ 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->host, irc->nick, irc->realname ); + 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_usermsg( 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_usermsg( 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_usermsg( 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->status |= USTATUS_SHUTDOWN; + 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[] = { @@ -198,9 +440,73 @@ static const command_t ipc_child_commands[] = { { "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 ) + { + 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 ); + + 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 ) { @@ -229,8 +535,12 @@ static void ipc_command_exec( void *data, char **cmd, const command_t *commands /* Return just one line. Returns NULL if something broke, an empty string on temporary "errors" (EAGAIN and friends). */ -static char *ipc_readline( int fd ) +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; @@ -252,22 +562,50 @@ static char *ipc_readline( int fd ) else size = eol - buf + 2; - if( recv( fd, buf, size, 0 ) != size ) + iov.iov_base = buf; + iov.iov_len = size; + + memset( &msg, 0, sizeof( msg ) ); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = ccmsg; + msg.msg_controllen = sizeof( ccmsg ); + + if( recvmsg( fd, &msg, 0 ) != size ) return NULL; - else - return g_strndup( buf, size - 2 ); + + 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 ); + */ + } + + /* + 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 ) ) ) + if( ( buf = ipc_readline( source, &child->to_fd ) ) ) { cmd = irc_parse_line( buf ); if( cmd ) { - ipc_command_exec( data, cmd, ipc_master_commands ); + ipc_command_exec( child, cmd, ipc_master_commands ); g_free( cmd ); } g_free( buf ); @@ -284,7 +622,7 @@ gboolean ipc_child_read( gpointer data, gint source, b_input_condition cond ) { char *buf, **cmd; - if( ( buf = ipc_readline( source ) ) ) + if( ( buf = ipc_readline( source, &ipc_child_recv_fd ) ) ) { cmd = irc_parse_line( buf ); if( cmd ) @@ -391,10 +729,7 @@ void ipc_to_children_str( char *format, ... ) next = l->next; if( write( c->ipc_fd, msg_buf, msg_len ) <= 0 ) - { ipc_master_free_one( c ); - child_list = g_slist_remove( child_list, c ); - } } } else if( global.conf->runmode == RUNMODE_DAEMON ) @@ -412,15 +747,57 @@ void ipc_to_children_str( char *format, ... ) 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; + + 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; + + 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 ) @@ -434,7 +811,6 @@ void ipc_master_free_fd( int fd ) if( c->ipc_fd == fd ) { ipc_master_free_one( c ); - child_list = g_slist_remove( child_list, c ); break; } } @@ -442,13 +818,8 @@ void ipc_master_free_fd( int fd ) void ipc_master_free_all() { - GSList *l; - - for( l = child_list; l; l = l->next ) - ipc_master_free_one( l->data ); - - g_slist_free( child_list ); - child_list = NULL; + while( child_list ) + ipc_master_free_one( child_list->data ); } void ipc_child_disable() @@ -505,17 +876,17 @@ static gboolean new_ipc_client( gpointer data, gint serversock, b_input_conditio { 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, GAIM_INPUT_READ, ipc_master_read, child ); + child->ipc_inpa = b_input_add( child->ipc_fd, B_EV_IO_READ, ipc_master_read, child ); - child_list = g_slist_append( child_list, child ); + child_list = g_slist_prepend( child_list, child ); return TRUE; } @@ -551,7 +922,7 @@ int ipc_master_listen_socket() return 0; } - b_input_add( serversock, GAIM_INPUT_READ, new_ipc_client, NULL ); + b_input_add( serversock, B_EV_IO_READ, new_ipc_client, NULL ); return 1; } @@ -596,9 +967,10 @@ int ipc_master_load_state( char *statefile ) fclose( fp ); return 0; } - child->ipc_inpa = b_input_add( child->ipc_fd, GAIM_INPUT_READ, ipc_master_read, child ); + 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 ); + child_list = g_slist_prepend( child_list, child ); } ipc_to_children_str( "HELLO\r\n" ); @@ -1,7 +1,7 @@ /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * - * Copyright 2002-2004 Wilmer van der Gaast and others * + * Copyright 2002-2010 Wilmer van der Gaast and others * \********************************************************************/ /* IPC - communication between BitlBee processes */ @@ -36,6 +36,12 @@ struct bitlbee_child char *host; char *nick; char *realname; + + char *password; + + /* For takeovers: */ + struct bitlbee_child *to_child; + int to_fd; }; @@ -48,6 +54,8 @@ 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 ); @@ -4,7 +4,7 @@ * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ -/* The big hairy IRCd part of the project */ +/* The IRC-based UI (for now the only one) */ /* This program is free software; you can redistribute it and/or modify @@ -23,125 +23,46 @@ Suite 330, Boston, MA 02111-1307 USA */ -#define BITLBEE_CORE #include "bitlbee.h" -#include "sock.h" -#include "crypting.h" #include "ipc.h" +#include "dcc.h" -static gboolean irc_userping( gpointer _irc, int fd, b_input_condition cond ); +GSList *irc_connection_list; -GSList *irc_connection_list = 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 *set_eval_charset( set_t *set, char *value ) -{ - irc_t *irc = 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_usermsg( 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; -} - -static char *set_eval_away_status( set_t *set, char *value ) -{ - irc_t *irc = set->data; - account_t *a; - - g_free( set->value ); - set->value = g_strdup( value ); - - for( a = irc->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; -} +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 ); 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; 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, GAIM_INPUT_READ, bitlbee_io_current_client_read, irc ); + 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->userhash = g_hash_table_new( g_str_hash, g_str_equal ); + 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 ); - strcpy( irc->umode, UMODE ); - irc->mynick = g_strdup( ROOT_NICK ); - irc->channel = g_strdup( ROOT_CHAN ); - irc->iconv = (GIConv) -1; irc->oconv = (GIConv) -1; if( global.conf->hostname ) { - irc->myhost = g_strdup( global.conf->hostname ); + myhost = g_strdup( global.conf->hostname ); } else if( getsockname( irc->fd, (struct sockaddr*) &sock, &socklen ) == 0 ) { @@ -150,7 +71,7 @@ irc_t *irc_new( int fd ) if( getnameinfo( (struct sockaddr *) &sock, socklen, buf, NI_MAXHOST, NULL, 0, 0 ) == 0 ) { - irc->myhost = g_strdup( ipv6_unwrap( buf ) ); + myhost = g_strdup( ipv6_unwrap( buf ) ); } } @@ -161,60 +82,77 @@ irc_t *irc_new( int fd ) if( getnameinfo( (struct sockaddr *)&sock, socklen, buf, NI_MAXHOST, NULL, 0, 0 ) == 0 ) { - irc->host = g_strdup( ipv6_unwrap( buf ) ); + host = g_strdup( ipv6_unwrap( buf ) ); } } - if( irc->host == NULL ) - irc->host = g_strdup( "localhost.localdomain" ); - if( irc->myhost == NULL ) - irc->myhost = g_strdup( "localhost.localdomain" ); + 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_write( irc, ":%s NOTICE AUTH :%s", irc->myhost, "BitlBee-IRCd initialized, please go on" ); irc_connection_list = g_slist_append( irc_connection_list, irc ); - s = set_add( &irc->set, "away", NULL, set_eval_away_status, irc ); - s->flags |= SET_NULL_OK; - s = set_add( &irc->set, "away_devoice", "true", set_eval_away_devoice, irc ); - s = set_add( &irc->set, "auto_connect", "true", set_eval_bool, irc ); - s = set_add( &irc->set, "auto_reconnect", "true", set_eval_bool, irc ); - s = set_add( &irc->set, "auto_reconnect_delay", "5*3<900", set_eval_account_reconnect_delay, irc ); - s = set_add( &irc->set, "buddy_sendbuffer", "false", set_eval_bool, irc ); - s = set_add( &irc->set, "buddy_sendbuffer_delay", "200", set_eval_int, irc ); - s = set_add( &irc->set, "charset", "utf-8", set_eval_charset, irc ); - s = set_add( &irc->set, "control_channel", irc->channel, set_eval_control_channel, irc ); - s = set_add( &irc->set, "debug", "false", set_eval_bool, irc ); - s = set_add( &irc->set, "default_target", "root", NULL, irc ); - s = set_add( &irc->set, "display_namechanges", "false", set_eval_bool, irc ); - s = set_add( &irc->set, "display_timestamps", "true", set_eval_bool, irc ); - s = set_add( &irc->set, "handle_unknown", "root", NULL, irc ); - s = set_add( &irc->set, "lcnicks", "true", set_eval_bool, irc ); - s = set_add( &irc->set, "ops", "both", set_eval_ops, irc ); - s = set_add( &irc->set, "password", NULL, set_eval_password, 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 = 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, "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; - s = set_add( &irc->set, "private", "true", set_eval_bool, irc ); - s = set_add( &irc->set, "query_order", "lifo", NULL, irc ); - s = set_add( &irc->set, "root_nick", irc->mynick, set_eval_root_nick, irc ); - s = set_add( &irc->set, "save_on_quit", "true", set_eval_bool, irc ); - s = set_add( &irc->set, "show_offline", "false", set_eval_bool, irc ); - s = set_add( &irc->set, "simulate_netsplit", "true", set_eval_bool, irc ); - s = set_add( &irc->set, "status", NULL, set_eval_away_status, irc ); - s->flags |= SET_NULL_OK; - s = set_add( &irc->set, "strip_html", "true", NULL, irc ); - s = set_add( &irc->set, "timezone", "local", set_eval_timezone, irc ); - s = set_add( &irc->set, "to_char", ": ", set_eval_to_char, irc ); - s = set_add( &irc->set, "typing_notice", "false", set_eval_bool, irc ); + 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 = set_add( &b->set, "show_offline", "false", set_eval_bw_compat, irc ); + 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 ); + + 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( &irc->set, "charset" ), set_getstr( &irc->set, "charset" ) ); + 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" ); - return( irc ); + g_free( myhost ); + g_free( host ); + + nogaim_init(); + + return irc; } /* immed=1 makes this function pretty much equal to irc_free(), except that @@ -235,7 +173,7 @@ void irc_abort( irc_t *irc, int immed, char *format, ... ) irc_write( irc, "ERROR :Closing link: %s", reason ); ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n", - irc->nick ? irc->nick : "(NONE)", irc->host, reason ); + irc->user->nick ? irc->user->nick : "(NONE)", irc->user->host, reason ); g_free( reason ); } @@ -245,7 +183,7 @@ void irc_abort( irc_t *irc, int immed, char *format, ... ) irc_write( irc, "ERROR :Closing link" ); ipc_to_master_str( "OPERMSG :Client exiting: %s@%s [%s]\r\n", - irc->nick ? irc->nick : "(NONE)", irc->host, "No reason given" ); + irc->user->nick ? irc->user->nick : "(NONE)", irc->user->host, "No reason given" ); } irc->status |= USTATUS_SHUTDOWN; @@ -266,65 +204,31 @@ void irc_abort( irc_t *irc, int immed, char *format, ... ) } } -static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data ) -{ - g_free( key ); - - return( TRUE ); -} +static gboolean irc_free_hashkey( gpointer key, gpointer value, gpointer data ); -/* Because we have no garbage collection, this is quite annoying */ void irc_free( irc_t * irc ) { - user_t *user, *usertmp; - log_message( LOGLVL_INFO, "Destroying connection with fd %d", irc->fd ); - if( irc->status & USTATUS_IDENTIFIED && set_getbool( &irc->set, "save_on_quit" ) ) + if( irc->status & USTATUS_IDENTIFIED && set_getbool( &irc->b->set, "save_on_quit" ) ) if( storage_save( irc, NULL, TRUE ) != STORAGE_OK ) - irc_usermsg( irc, "Error while saving settings!" ); + log_message( LOGLVL_WARNING, "Error while saving settings for user %s", irc->user->nick ); irc_connection_list = g_slist_remove( irc_connection_list, irc ); - while( irc->accounts ) - { - if( irc->accounts->ic ) - imc_logout( irc->accounts->ic, FALSE ); - else if( irc->accounts->reconnect ) - cancel_auto_reconnect( irc->accounts ); - - if( irc->accounts->ic == NULL ) - account_del( irc, irc->accounts ); - else - /* Nasty hack, but account_del() doesn't work in this - case and we don't want infinite loops, do we? ;-) */ - irc->accounts = irc->accounts->next; - } - while( irc->queries != NULL ) query_del( irc, irc->queries ); - while( irc->set ) - set_del( &irc->set, irc->set->key ); + /* 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 ); - if (irc->users != NULL) - { - user = irc->users; - while( user != NULL ) - { - g_free( user->nick ); - g_free( user->away ); - g_free( user->handle ); - if( user->user != user->nick ) g_free( user->user ); - if( user->host != user->nick ) g_free( user->host ); - if( user->realname != user->nick ) g_free( user->realname ); - b_event_remove( user->sendbuf_timer ); - - usertmp = user; - user = user->next; - g_free( usertmp ); - } - } + 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 ); @@ -336,8 +240,8 @@ void irc_free( irc_t * irc ) closesocket( irc->fd ); irc->fd = -1; - g_hash_table_foreach_remove( irc->userhash, irc_free_hashkey, NULL ); - g_hash_table_destroy( irc->userhash ); + 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 ); @@ -349,19 +253,8 @@ void irc_free( irc_t * irc ) g_free( irc->sendbuffer ); g_free( irc->readbuffer ); - - g_free( irc->nick ); - g_free( irc->user ); - g_free( irc->host ); - g_free( irc->realname ); g_free( irc->password ); - - g_free( irc->myhost ); - g_free( irc->mynick ); - - g_free( irc->channel ); - - g_free( irc->last_target ); + g_free( irc->last_root_cmd ); g_free( irc ); @@ -373,9 +266,16 @@ void irc_free( irc_t * irc ) 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) +void irc_setpass (irc_t *irc, const char *pass) { g_free (irc->password); @@ -386,6 +286,23 @@ void irc_setpass (irc_t *irc, const char *pass) } } +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; @@ -393,7 +310,7 @@ void irc_process( irc_t *irc ) if( irc->readbuffer != NULL ) { - lines = irc_tokenize( irc->readbuffer ); + lines = irc_splitlines( irc->readbuffer ); for( i = 0; *lines[i] != '\0'; i ++ ) { @@ -430,14 +347,14 @@ void irc_process( irc_t *irc ) "expect by changing the charset setting. See " "`help set charset' for more information. Your " "message was ignored.", - set_getstr( &irc->set, "charset" ) ); + set_getstr( &irc->b->set, "charset" ) ); g_free( conv ); conv = NULL; } else { - irc_write( irc, ":%s NOTICE AUTH :%s", irc->myhost, + irc_write( irc, ":%s NOTICE AUTH :%s", irc->root->host, "Warning: invalid characters received at login time." ); conv = g_strdup( lines[i] ); @@ -475,9 +392,11 @@ void irc_process( irc_t *irc ) } } -/* 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. */ -char **irc_tokenize( char *buffer ) +/* 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; @@ -608,46 +527,45 @@ char *irc_build_line( char **cmd ) return s; } -void irc_reply( irc_t *irc, int code, char *format, ... ) +void irc_write( irc_t *irc, char *format, ... ) { - char text[IRC_MAX_LINE]; va_list params; - + va_start( params, format ); - g_vsnprintf( text, IRC_MAX_LINE, format, params ); + irc_vawrite( irc, format, params ); va_end( params ); - irc_write( irc, ":%s %03d %s %s", irc->myhost, code, irc->nick?irc->nick:"*", text ); - + return; } -int irc_usermsg( irc_t *irc, char *format, ... ) +void irc_write_all( int now, char *format, ... ) { - char text[1024]; va_list params; - char is_private = 0; - user_t *u; - - u = user_find( irc, irc->mynick ); - is_private = u->is_private; + GSList *temp; va_start( params, format ); - g_vsnprintf( text, sizeof( text ), format, params ); - va_end( params ); - return( irc_msgfrom( irc, u->nick, text ) ); -} - -void irc_write( irc_t *irc, char *format, ... ) -{ - va_list params; - - va_start( params, format ); - irc_vawrite( irc, format, params ); + 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 ) { @@ -695,114 +613,152 @@ void irc_vawrite( irc_t *irc, char *format, va_list params ) 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, GAIM_INPUT_WRITE ) ) */ + 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, GAIM_INPUT_WRITE, bitlbee_io_current_client_write, irc ); + irc->w_watch_source_id = b_input_add( irc->fd, B_EV_IO_WRITE, bitlbee_io_current_client_write, irc ); } return; } -void irc_write_all( int now, char *format, ... ) +/* 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 ) { - va_list params; - GSList *temp; + ssize_t n; + size_t len; - va_start( params, format ); + if( irc->sendbuffer == NULL ) + return; - temp = irc_connection_list; - while( temp != NULL ) + len = strlen( irc->sendbuffer ); + if( ( n = send( irc->fd, irc->sendbuffer, len, 0 ) ) == len ) { - irc_t *irc = temp->data; + g_free( irc->sendbuffer ); + irc->sendbuffer = NULL; - 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, GAIM_INPUT_WRITE ); - } - temp = temp->next; + b_event_remove( irc->w_watch_source_id ); + irc->w_watch_source_id = 0; } - - va_end( params ); - return; -} + 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. */ +} -void irc_names( irc_t *irc, char *channel ) +/* Meant for takeover functionality. Transfer an IRC connection to a different + socket. */ +void irc_switch_fd( irc_t *irc, int fd ) { - user_t *u; - char namelist[385] = ""; - struct groupchat *c = NULL; - char *ops = set_getstr( &irc->set, "ops" ); + irc_write( irc, "ERROR :Transferring session to a new connection" ); + irc_flush( irc ); /* Write it now or forget about it forever. */ - /* RFCs say there is no error reply allowed on NAMES, so when the - channel is invalid, just give an empty reply. */ - - if( g_strcasecmp( channel, irc->channel ) == 0 ) + if( irc->sendbuffer ) { - for( u = irc->users; u; u = u->next ) if( u->online ) - { - if( strlen( namelist ) + strlen( u->nick ) > sizeof( namelist ) - 4 ) - { - irc_reply( irc, 353, "= %s :%s", channel, namelist ); - *namelist = 0; - } - - if( u->ic && !u->away && set_getbool( &irc->set, "away_devoice" ) ) - strcat( namelist, "+" ); - else if( ( strcmp( u->nick, irc->mynick ) == 0 && ( strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) == 0 ) ) || - ( strcmp( u->nick, irc->nick ) == 0 && ( strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) == 0 ) ) ) - strcat( namelist, "@" ); - - strcat( namelist, u->nick ); - strcat( namelist, " " ); - } + b_event_remove( irc->w_watch_source_id ); + irc->w_watch_source_id = 0; + g_free( irc->sendbuffer ); + irc->sendbuffer = NULL; } - else if( ( c = irc_chat_by_channel( irc, channel ) ) ) + + 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 ) { - GList *l; - - /* root and the user aren't in the channel userlist but should - show up in /NAMES, so list them first: */ - sprintf( namelist, "%s%s %s%s ", strcmp( ops, "root" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->mynick, - strcmp( ops, "user" ) == 0 || strcmp( ops, "both" ) ? "@" : "", irc->nick ); - - for( l = c->in_room; l; l = l->next ) if( ( u = user_findhandle( c->ic, l->data ) ) ) - { - if( strlen( namelist ) + strlen( u->nick ) > sizeof( namelist ) - 4 ) - { - irc_reply( irc, 353, "= %s :%s", channel, namelist ); - *namelist = 0; - } - - strcat( namelist, u->nick ); - strcat( namelist, " " ); - } + irc_channel_t *ic = l->data; + if( ic->flags & IRC_CHANNEL_JOINED ) + irc_send_join( ic, irc->user ); } +} + +void irc_desync( irc_t *irc ) +{ + GSList *l; - if( *namelist ) - irc_reply( irc, 353, "= %s :%s", channel, namelist ); + for( l = irc->channels; l; l = l->next ) + irc_channel_del_user( l->data, irc->user, IRC_CDU_KICK, + "Switching to old session" ); - irc_reply( irc, 366, "%s :End of /NAMES list", channel ); + 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 && irc->nick ) + if( irc->user->user && irc->user->nick ) { if( global.conf->authmode == AUTHMODE_CLOSED && !( irc->status & USTATUS_AUTHORIZED ) ) { - irc_reply( irc, 464, ":This server is password-protected." ); + irc_send_num( irc, 464, ":This server is password-protected." ); return 0; } else { - irc_login( irc ); + 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->last_root_cmd = g_strdup( ROOT_CHAN ); + + irc_send_msg( irc->root, "PRIVMSG", ROOT_CHAN, + "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.", NULL ); + + /* 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; } } @@ -813,135 +769,12 @@ int irc_check_login( irc_t *irc ) } } -void irc_login( irc_t *irc ) -{ - user_t *u; - - irc_reply( irc, 1, ":Welcome to the BitlBee gateway, %s", irc->nick ); - irc_reply( irc, 2, ":Host %s is running BitlBee " BITLBEE_VERSION " " ARCH "/" CPU ".", irc->myhost ); - irc_reply( irc, 3, ":%s", IRCD_INFO ); - irc_reply( irc, 4, "%s %s %s %s", irc->myhost, BITLBEE_VERSION, UMODES UMODES_PRIV, CMODES ); - irc_reply( irc, 5, "PREFIX=(ov)@+ CHANTYPES=%s CHANMODES=,,,%s NICKLEN=%d NETWORK=BitlBee " - "CASEMAPPING=rfc1459 MAXTARGETS=1 WATCH=128 :are supported by this server", - CTYPES, CMODES, MAX_NICK_LENGTH - 1 ); - irc_motd( irc ); - irc->umode[0] = '\0'; - irc_umode_set( irc, "+" UMODE, 1 ); - - u = user_add( irc, irc->mynick ); - u->host = g_strdup( irc->myhost ); - u->realname = g_strdup( ROOT_FN ); - u->online = 1; - u->send_handler = root_command_string; - u->is_private = 0; /* [SH] The channel is root's personal playground. */ - irc_spawn( irc, u ); - - u = user_add( irc, NS_NICK ); - u->host = g_strdup( irc->myhost ); - u->realname = g_strdup( ROOT_FN ); - u->online = 0; - u->send_handler = root_command_string; - u->is_private = 1; /* [SH] NickServ is not in the channel, so should always /query. */ - - u = user_add( irc, irc->nick ); - u->user = g_strdup( irc->user ); - u->host = g_strdup( irc->host ); - u->realname = g_strdup( irc->realname ); - u->online = 1; - irc_spawn( irc, u ); - - irc_usermsg( 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." ); - - if( global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON ) - ipc_to_master_str( "CLIENT %s %s :%s\r\n", irc->host, irc->nick, irc->realname ); - - irc->status |= USTATUS_LOGGED_IN; - - /* 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] ); - } -} - -void irc_motd( irc_t *irc ) -{ - int fd; - - fd = open( global.conf->motdfile, O_RDONLY ); - if( fd == -1 ) - { - irc_reply( irc, 422, ":We don't need MOTDs." ); - } - else - { - char linebuf[80]; /* Max. line length for MOTD's is 79 chars. It's what most IRC networks seem to do. */ - char *add, max; - int len; - - linebuf[79] = len = 0; - max = sizeof( linebuf ) - 1; - - irc_reply( irc, 375, ":- %s Message Of The Day - ", irc->myhost ); - while( read( fd, linebuf + len, 1 ) == 1 ) - { - if( linebuf[len] == '\n' || len == max ) - { - linebuf[len] = 0; - irc_reply( irc, 372, ":- %s", linebuf ); - len = 0; - } - else if( linebuf[len] == '%' ) - { - read( fd, linebuf + len, 1 ); - if( linebuf[len] == 'h' ) - add = irc->myhost; - else if( linebuf[len] == 'v' ) - add = BITLBEE_VERSION; - else if( linebuf[len] == 'n' ) - add = irc->nick; - else - add = "%"; - - strncpy( linebuf + len, add, max - len ); - while( linebuf[++len] ); - } - else if( len < max ) - { - len ++; - } - } - irc_reply( irc, 376, ":End of MOTD" ); - close( fd ); - } -} - -void irc_topic( irc_t *irc, char *channel ) -{ - struct groupchat *c = irc_chat_by_channel( irc, channel ); - - if( c && c->topic ) - irc_reply( irc, 332, "%s :%s", channel, c->topic ); - else if( g_strcasecmp( channel, irc->channel ) == 0 ) - irc_reply( irc, 332, "%s :%s", channel, CONTROL_TOPIC ); - else - irc_reply( irc, 331, "%s :No topic for this channel", channel ); -} - -void irc_umode_set( irc_t *irc, char *s, int allow_priv ) +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[256], st = 1, *t; + char m[128], st = 1; + const char *t; int i; char changes[512], *p, st2 = 2; char badflag = 0; @@ -949,14 +782,17 @@ void irc_umode_set( irc_t *irc, char *s, int allow_priv ) memset( m, 0, sizeof( m ) ); for( t = irc->umode; *t; t ++ ) - m[(int)*t] = 1; - + if( *t < sizeof( m ) ) + m[(int)*t] = 1; + p = changes; for( t = s; *t; t ++ ) { if( *t == '+' || *t == '-' ) st = *t == '+'; - else if( st == 0 || ( strchr( UMODES, *t ) || ( allow_priv && strchr( UMODES_PRIV, *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) { @@ -973,328 +809,16 @@ void irc_umode_set( irc_t *irc, char *s, int allow_priv ) memset( irc->umode, 0, sizeof( irc->umode ) ); - for( i = 0; i < 256 && strlen( irc->umode ) < ( sizeof( irc->umode ) - 1 ); i ++ ) + 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_reply( irc, 501, ":Unknown MODE flag" ); - /* Deliberately no !user@host on the prefix here */ + irc_send_num( irc, 501, ":Unknown MODE flag" ); if( *changes ) - irc_write( irc, ":%s MODE %s %s", irc->nick, irc->nick, changes ); -} - -void irc_spawn( irc_t *irc, user_t *u ) -{ - irc_join( irc, u, irc->channel ); -} - -void irc_join( irc_t *irc, user_t *u, char *channel ) -{ - char *nick; - - if( ( g_strcasecmp( channel, irc->channel ) != 0 ) || user_find( irc, irc->nick ) ) - irc_write( irc, ":%s!%s@%s JOIN :%s", u->nick, u->user, u->host, channel ); - - if( nick_cmp( u->nick, irc->nick ) == 0 ) - { - irc_write( irc, ":%s MODE %s +%s", irc->myhost, channel, CMODE ); - irc_names( irc, channel ); - irc_topic( irc, channel ); - } - - nick = g_strdup( u->nick ); - nick_lc( nick ); - if( g_hash_table_lookup( irc->watches, nick ) ) - { - irc_reply( irc, 600, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged online" ); - } - g_free( nick ); -} - -void irc_part( irc_t *irc, user_t *u, char *channel ) -{ - irc_write( irc, ":%s!%s@%s PART %s :%s", u->nick, u->user, u->host, channel, "" ); -} - -void irc_kick( irc_t *irc, user_t *u, char *channel, user_t *kicker ) -{ - irc_write( irc, ":%s!%s@%s KICK %s %s :%s", kicker->nick, kicker->user, kicker->host, channel, u->nick, "" ); -} - -void irc_kill( irc_t *irc, user_t *u ) -{ - char *nick, *s; - char reason[128]; - - if( u->ic && u->ic->flags & OPT_LOGGING_OUT && set_getbool( &irc->set, "simulate_netsplit" ) ) - { - if( u->ic->acc->server ) - g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost, - u->ic->acc->server ); - else if( ( s = strchr( u->ic->acc->user, '@' ) ) ) - g_snprintf( reason, sizeof( reason ), "%s %s", irc->myhost, - s + 1 ); - else - g_snprintf( reason, sizeof( reason ), "%s %s.%s", irc->myhost, - u->ic->acc->prpl->name, irc->myhost ); - - /* proto_opt might contain garbage after the : */ - if( ( s = strchr( reason, ':' ) ) ) - *s = 0; - } - else - { - strcpy( reason, "Leaving..." ); - } - - irc_write( irc, ":%s!%s@%s QUIT :%s", u->nick, u->user, u->host, reason ); - - nick = g_strdup( u->nick ); - nick_lc( nick ); - if( g_hash_table_lookup( irc->watches, nick ) ) - { - irc_reply( irc, 601, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "logged offline" ); - } - g_free( nick ); -} - -int irc_send( irc_t *irc, char *nick, char *s, int flags ) -{ - struct groupchat *c = NULL; - user_t *u = NULL; - - if( strchr( CTYPES, *nick ) ) - { - if( !( c = irc_chat_by_channel( irc, nick ) ) ) - { - irc_reply( irc, 403, "%s :Channel does not exist", nick ); - return( 0 ); - } - } - else - { - u = user_find( irc, nick ); - - if( !u ) - { - if( irc->is_private ) - irc_reply( irc, 401, "%s :Nick does not exist", nick ); - else - irc_usermsg( irc, "Nick `%s' does not exist!", nick ); - return( 0 ); - } - } - - if( *s == 1 && s[strlen(s)-1] == 1 ) - { - if( g_strncasecmp( s + 1, "ACTION", 6 ) == 0 ) - { - if( s[7] == ' ' ) s ++; - s += 3; - *(s++) = '/'; - *(s++) = 'm'; - *(s++) = 'e'; - *(s++) = ' '; - s -= 4; - s[strlen(s)-1] = 0; - } - else if( g_strncasecmp( s + 1, "VERSION", 7 ) == 0 ) - { - u = user_find( irc, irc->mynick ); - irc_privmsg( irc, u, "NOTICE", irc->nick, "", "\001VERSION BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\001" ); - return( 1 ); - } - else if( g_strncasecmp( s + 1, "PING", 4 ) == 0 ) - { - u = user_find( irc, irc->mynick ); - irc_privmsg( irc, u, "NOTICE", irc->nick, "", s ); - return( 1 ); - } - else if( g_strncasecmp( s + 1, "TYPING", 6 ) == 0 ) - { - if( u && u->ic && u->ic->acc->prpl->send_typing && strlen( s ) >= 10 ) - { - time_t current_typing_notice = time( NULL ); - - if( current_typing_notice - u->last_typing_notice >= 5 ) - { - u->ic->acc->prpl->send_typing( u->ic, u->handle, ( s[8] - '0' ) << 8 ); - u->last_typing_notice = current_typing_notice; - } - } - return( 1 ); - } - else - { - irc_usermsg( irc, "Non-ACTION CTCP's aren't supported" ); - return( 0 ); - } - } - - if( u ) - { - /* For the next message, we probably do have to send new notices... */ - u->last_typing_notice = 0; - u->is_private = irc->is_private; - - if( u->is_private ) - { - if( !u->online ) - irc_reply( irc, 301, "%s :%s", u->nick, "User is offline" ); - else if( u->away ) - irc_reply( irc, 301, "%s :%s", u->nick, u->away ); - } - - if( u->send_handler ) - { - u->send_handler( irc, u, s, flags ); - return 1; - } - } - else if( c && c->ic && c->ic->acc && c->ic->acc->prpl ) - { - return( imc_chat_msg( c, s, 0 ) ); - } - - return( 0 ); -} - -static gboolean buddy_send_handler_delayed( gpointer data, gint fd, b_input_condition cond ) -{ - user_t *u = data; - - /* Shouldn't happen, but just to be sure. */ - if( u->sendbuf_len < 2 ) - return FALSE; - - u->sendbuf[u->sendbuf_len-2] = 0; /* Cut off the last newline */ - imc_buddy_msg( u->ic, u->handle, u->sendbuf, u->sendbuf_flags ); - - g_free( u->sendbuf ); - u->sendbuf = NULL; - u->sendbuf_len = 0; - u->sendbuf_timer = 0; - u->sendbuf_flags = 0; - - return FALSE; -} - -void buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags ) -{ - if( !u || !u->ic ) return; - - if( set_getbool( &irc->set, "buddy_sendbuffer" ) && set_getint( &irc->set, "buddy_sendbuffer_delay" ) > 0 ) - { - int delay; - - if( u->sendbuf_len > 0 && u->sendbuf_flags != flags) - { - /* Flush the buffer */ - b_event_remove( u->sendbuf_timer ); - buddy_send_handler_delayed( u, -1, 0 ); - } - - if( u->sendbuf_len == 0 ) - { - u->sendbuf_len = strlen( msg ) + 2; - u->sendbuf = g_new( char, u->sendbuf_len ); - u->sendbuf[0] = 0; - u->sendbuf_flags = flags; - } - else - { - u->sendbuf_len += strlen( msg ) + 1; - u->sendbuf = g_renew( char, u->sendbuf, u->sendbuf_len ); - } - - strcat( u->sendbuf, msg ); - strcat( u->sendbuf, "\n" ); - - delay = set_getint( &irc->set, "buddy_sendbuffer_delay" ); - if( delay <= 5 ) - delay *= 1000; - - if( u->sendbuf_timer > 0 ) - b_event_remove( u->sendbuf_timer ); - u->sendbuf_timer = b_timeout_add( delay, buddy_send_handler_delayed, u ); - } - else - { - imc_buddy_msg( u->ic, u->handle, msg, flags ); - } -} - -int irc_privmsg( irc_t *irc, user_t *u, char *type, char *to, char *prefix, char *msg ) -{ - char last = 0; - char *s = msg, *line = msg; - - /* The almighty linesplitter .. woohoo!! */ - while( !last ) - { - if( *s == '\r' && *(s+1) == '\n' ) - *(s++) = 0; - if( *s == '\n' ) - { - last = s[1] == 0; - *s = 0; - } - else - { - last = s[0] == 0; - } - if( *s == 0 ) - { - if( g_strncasecmp( line, "/me ", 4 ) == 0 && ( !prefix || !*prefix ) && g_strcasecmp( type, "PRIVMSG" ) == 0 ) - { - irc_write( irc, ":%s!%s@%s %s %s :\001ACTION %s\001", u->nick, u->user, u->host, - type, to, line + 4 ); - } - else - { - irc_write( irc, ":%s!%s@%s %s %s :%s%s", u->nick, u->user, u->host, - type, to, prefix ? prefix : "", line ); - } - line = s + 1; - } - s ++; - } - - return( 1 ); -} - -int irc_msgfrom( irc_t *irc, char *nick, char *msg ) -{ - user_t *u = user_find( irc, nick ); - static char *prefix = NULL; - - if( !u ) return( 0 ); - if( prefix && *prefix ) g_free( prefix ); - - if( !u->is_private && nick_cmp( u->nick, irc->mynick ) != 0 ) - { - int len = strlen( irc->nick) + 3; - prefix = g_new (char, len ); - g_snprintf( prefix, len, "%s%s", irc->nick, set_getstr( &irc->set, "to_char" ) ); - prefix[len-1] = 0; - } - else - { - prefix = ""; - } - - return( irc_privmsg( irc, u, "PRIVMSG", u->is_private ? irc->nick : irc->channel, prefix, msg ) ); -} - -int irc_noticefrom( irc_t *irc, char *nick, char *msg ) -{ - user_t *u = user_find( irc, nick ); - - if( u ) - return( irc_privmsg( irc, u, "NOTICE", irc->nick, "", msg ) ); - else - return( 0 ); + 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 @@ -1333,26 +857,76 @@ static gboolean irc_userping( gpointer _irc, gint fd, b_input_condition cond ) return TRUE; } -struct groupchat *irc_chat_by_channel( irc_t *irc, char *channel ) +static char *set_eval_charset( set_t *set, char *value ) { - struct groupchat *c; - account_t *a; + 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; + } - /* This finds the connection which has a conversation which belongs to this channel */ - for( a = irc->accounts; a; a = a->next ) + /* 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 ) { - if( a->ic == NULL ) - continue; - - c = a->ic->groupchats; - while( c ) - { - if( c->channel && g_strcasecmp( c->channel, channel ) == 0 ) - return c; - - c = c->next; - } + g_free( test ); + g_iconv_close( oc ); + irc_usermsg( 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_usermsg( 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 + return SET_INVALID; + + 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 NULL; + return SET_INVALID; } @@ -4,7 +4,7 @@ * Copyright 2002-2004 Wilmer van der Gaast and others * \********************************************************************/ -/* The big hairy IRCd part of the project */ +/* The IRC-based UI (for now the only one) */ /* This program is free software; you can redistribute it and/or modify @@ -32,12 +32,14 @@ #define IRC_LOGIN_TIMEOUT 60 #define IRC_PING_STRING "PinglBee" -#define UMODES "abisw" -#define UMODES_PRIV "Ro" -#define CMODES "nt" -#define CMODE "t" -#define UMODE "s" -#define CTYPES "&#" +#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 "nt" /* Allowed modes */ +#define CMODE "t" /* Default mode */ +#define UMODE "s" /* Default mode */ + +#define CTYPES "&#" /* Valid channel name prefixes */ typedef enum { @@ -48,6 +50,8 @@ typedef enum USTATUS_SHUTDOWN = 8 } irc_status_t; +struct irc_user; + typedef struct irc { int fd; @@ -58,85 +62,240 @@ typedef struct irc char *readbuffer; GIConv iconv, oconv; - int sentbytes; - time_t oldtime; + struct irc_user *root; + struct irc_user *user; + + char *last_root_cmd; /* Either the nickname from which the last root + msg came, or the last channel root was talked + to. */ - char *nick; - char *user; - char *host; - char *realname; 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]; - char *myhost; - char *mynick; - - char *channel; - int c_id; - - char is_private; /* Not too nice... */ - char *last_target; - struct query *queries; - struct account *accounts; - struct chat *chatrooms; + GSList *file_transfers; - struct __USER *users; - GHashTable *userhash; - GHashTable *watches; - struct __NICK *nicks; - struct set *set; + 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 bee *b; } irc_t; -#include "user.h" +typedef enum +{ + /* Replaced with iu->last_channel IRC_USER_PRIVATE = 1, */ + IRC_USER_AWAY = 2, +} irc_user_flags_t; + +typedef struct irc_user +{ + irc_t *irc; + + char *nick; + char *user; + char *host; + char *fullname; + + /* Nickname in lowercase for case sensitive 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 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 ); + 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, + IRC_CC_TYPE_REST, + IRC_CC_TYPE_GROUP, + IRC_CC_TYPE_ACCOUNT, + IRC_CC_TYPE_PROTOCOL, +} 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; + +/* 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_exec( irc_t *irc, char **cmd ); void irc_process( irc_t *irc ); char **irc_parse_line( char *line ); char *irc_build_line( char **cmd ); -void irc_vawrite( irc_t *irc, char *format, va_list params ); 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_reply( irc_t *irc, int code, char *format, ... ) G_GNUC_PRINTF( 3, 4 ); -G_MODULE_EXPORT int irc_usermsg( irc_t *irc, char *format, ... ) G_GNUC_PRINTF( 2, 3 ); -char **irc_tokenize( char *buffer ); +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 ); -void irc_login( irc_t *irc ); int irc_check_login( irc_t *irc ); -void irc_motd( irc_t *irc ); -void irc_names( irc_t *irc, char *channel ); -void irc_topic( irc_t *irc, char *channel ); -void irc_umode_set( irc_t *irc, char *s, int allow_priv ); -void irc_who( irc_t *irc, char *channel ); -void irc_spawn( irc_t *irc, user_t *u ); -void irc_join( irc_t *irc, user_t *u, char *channel ); -void irc_part( irc_t *irc, user_t *u, char *channel ); -void irc_kick( irc_t *irc, user_t *u, char *channel, user_t *kicker ); -void irc_kill( irc_t *irc, user_t *u ); -void irc_invite( irc_t *irc, char *nick, char *channel ); -void irc_whois( irc_t *irc, char *nick ); -void irc_setpass( irc_t *irc, const char *pass ); /* USE WITH CAUTION! */ - -int irc_send( irc_t *irc, char *nick, char *s, int flags ); -int irc_privmsg( irc_t *irc, user_t *u, char *type, char *to, char *prefix, char *msg ); -int irc_msgfrom( irc_t *irc, char *nick, char *msg ); -int irc_noticefrom( irc_t *irc, char *nick, char *msg ); - -void buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags ); -struct groupchat *irc_chat_by_channel( irc_t *irc, char *channel ); + +void irc_umode_set( irc_t *irc, const char *s, gboolean allow_priv ); + +/* 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 ); +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_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 ); + +/* 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 ); +void irc_usermsg( irc_t *irc, 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 ); +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 ); + +/* 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 ); +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 ); #endif diff --git a/irc_channel.c b/irc_channel.c new file mode 100644 index 00000000..5d504f66 --- /dev/null +++ b/irc_channel.c @@ -0,0 +1,701 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 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; + + 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 ); + set_add( &ic->set, "type", "control", set_eval_channel_type, ic ); + + 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 = 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 ); + } + + 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( 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; +} + +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_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; + 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]; + irc_user_t *iu; + + memset( to, 0, sizeof( to ) ); + strncpy( to, msg, s - msg ); + while( *(++s) && isspace( *s ) ) {} + + iu = irc_user_by_name( irc, to ); + if( iu && iu->f->privmsg ) + { + iu->last_channel = ic; + iu->f->privmsg( iu, s ); + } + else + { + irc_channel_printf( ic, "User does not exist: %s", to ); + } + } + else + { + /* TODO: Maybe just use root->privmsg here now? */ + char cmd[strlen(msg)+1]; + + g_free( ic->irc->last_root_cmd ); + ic->irc->last_root_cmd = g_strdup( ic->name ); + + strcpy( cmd, msg ); + root_command_string( ic->irc, cmd ); + } + + 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" ); + + 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_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; + + if( strcmp( value, "all" ) == 0 ) + icc->type = IRC_CC_TYPE_DEFAULT; + else if( strcmp( value, "rest" ) == 0 ) + icc->type = IRC_CC_TYPE_REST; + else if( strcmp( value, "group" ) == 0 ) + icc->type = IRC_CC_TYPE_GROUP; + else if( strcmp( value, "account" ) == 0 ) + icc->type = IRC_CC_TYPE_ACCOUNT; + else if( strcmp( value, "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_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_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; +} + +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" ); + + g_free( icc ); + ic->data = NULL; + + 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, +}; diff --git a/irc_commands.c b/irc_commands.c index 7a286ce2..0bf20cfc 100644 --- a/irc_commands.c +++ b/irc_commands.c @@ -1,7 +1,7 @@ /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * - * Copyright 2002-2006 Wilmer van der Gaast and others * + * Copyright 2002-2010 Wilmer van der Gaast and others * \********************************************************************/ /* IRC commands */ @@ -26,7 +26,6 @@ #define BITLBEE_CORE #include "bitlbee.h" #include "ipc.h" -#include "chat.h" static void irc_cmd_pass( irc_t *irc, char **cmd ) { @@ -52,7 +51,7 @@ static void irc_cmd_pass( irc_t *irc, char **cmd ) } else if( global.conf->auth_pass ) { - irc_reply( irc, 464, ":Incorrect password" ); + irc_send_num( irc, 464, ":Incorrect password" ); } else { @@ -64,41 +63,44 @@ static void irc_cmd_pass( irc_t *irc, char **cmd ) static void irc_cmd_user( irc_t *irc, char **cmd ) { - irc->user = g_strdup( cmd[1] ); - irc->realname = g_strdup( cmd[4] ); + 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 ) { - if( irc->status & USTATUS_IDENTIFIED && irc->nick ) - { - irc_reply( irc, 438, "%s %s :You can only change your nick if you're not " - "logged in (i.e. pre-identify)", irc->nick, cmd[1] ); - } - /* This is not clean, but for now it'll have to be like this... */ - else if( ( nick_cmp( cmd[1], irc->mynick ) == 0 ) || ( nick_cmp( cmd[1], NS_NICK ) == 0 ) || ( user_find( irc, cmd[1] ) != NULL ) ) + irc_user_t *iu; + + if( ( iu = irc_user_by_name( irc, cmd[1] ) ) && iu != irc->user ) { - irc_reply( irc, 433, "%s :This nick is already in use", cmd[1] ); + irc_send_num( irc, 433, "%s :This nick is already in use", cmd[1] ); } else if( !nick_ok( cmd[1] ) ) { /* [SH] Invalid characters. */ - irc_reply( irc, 432, "%s :This nick contains invalid characters", cmd[1] ); + irc_send_num( irc, 432, "%s :This nick contains invalid characters", cmd[1] ); } - else if(irc->nick) + else if( irc->status & USTATUS_LOGGED_IN ) { - if( user_find( irc, irc->nick ) ) - user_rename(irc, irc->nick, cmd[1]); - - irc_write( irc, ":%s!%s@%s NICK %s", irc->nick, irc->user, irc->host, cmd[1] ); - g_free(irc->nick); - irc->nick = g_strdup( cmd[1] ); + if( irc->status & USTATUS_IDENTIFIED ) + { + irc_setpass( irc, NULL ); + irc->status &= ~USTATUS_IDENTIFIED; + irc_umode_set( irc, "-R", 1 ); + irc_usermsg( 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." ); + } + + irc_user_set_nick( irc->user, cmd[1] ); } else { - irc->nick = g_strdup( cmd[1] ); + g_free( irc->user->nick ); + irc->user->nick = g_strdup( cmd[1] ); irc_check_login( irc ); } @@ -114,209 +116,313 @@ static void irc_cmd_quit( irc_t *irc, char **cmd ) static void irc_cmd_ping( irc_t *irc, char **cmd ) { - irc_write( irc, ":%s PONG %s :%s", irc->myhost, irc->myhost, cmd[1]?cmd[1]:irc->myhost ); + irc_write( irc, ":%s PONG %s :%s", irc->root->host, + irc->root->host, cmd[1]?cmd[1]:irc->root->host ); } -static void irc_cmd_oper( irc_t *irc, char **cmd ) +static void irc_cmd_pong( irc_t *irc, char **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 ) ) + /* 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_umode_set( irc, "+o", 1 ); - irc_reply( irc, 381, ":Password accepted" ); + 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 + { + bee_irc_channel_update( ic->irc, ic, NULL ); + } + } + + 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 { - irc_reply( irc, 432, ":Incorrect password" ); + 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( strchr( CTYPES, *cmd[1] ) ) + if( irc_channel_name_ok( cmd[1] ) ) { - if( cmd[2] ) + 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_reply( irc, 477, "%s :Can't change channel modes", cmd[1] ); + irc_send_num( irc, 477, "%s :Can't change channel modes", cmd[1] ); else if( *cmd[2] == 'b' ) - irc_reply( irc, 368, "%s :No bans possible", cmd[1] ); + irc_send_num( irc, 368, "%s :No bans possible", cmd[1] ); } else - irc_reply( irc, 324, "%s +%s", cmd[1], CMODE ); + irc_send_num( irc, 324, "%s +%s", cmd[1], ic->mode ); } else { - if( nick_cmp( cmd[1], irc->nick ) == 0 ) + if( nick_cmp( cmd[1], irc->user->nick ) == 0 ) { if( cmd[2] ) irc_umode_set( irc, cmd[2], 0 ); else - irc_reply( irc, 221, "+%s", irc->umode ); + irc_send_num( irc, 221, "+%s", irc->umode ); } else - irc_reply( irc, 502, ":Don't touch their modes" ); + irc_send_num( irc, 502, ":Don't touch their modes" ); } } -static void irc_cmd_names( irc_t *irc, char **cmd ) +static void irc_cmd_who( irc_t *irc, char **cmd ) { - irc_names( irc, cmd[1]?cmd[1]:irc->channel ); + char *channel = cmd[1]; + irc_channel_t *ic; + + 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 + irc_send_num( irc, 403, "%s :No such channel", channel ); } -static void irc_cmd_part( irc_t *irc, char **cmd ) +static void irc_cmd_privmsg( irc_t *irc, char **cmd ) { - struct groupchat *c; + irc_channel_t *ic; + irc_user_t *iu; - if( g_strcasecmp( cmd[1], irc->channel ) == 0 ) + if( !cmd[2] ) { - user_t *u = user_find( irc, irc->nick ); - - /* Not allowed to leave control channel */ - irc_part( irc, u, irc->channel ); - irc_join( irc, u, irc->channel ); + irc_send_num( irc, 412, ":No text to send" ); + return; } - else if( ( c = irc_chat_by_channel( irc, cmd[1] ) ) ) + + /* Don't treat CTCP actions as real CTCPs, just convert them right now. */ + if( g_strncasecmp( cmd[2], "\001ACTION", 7 ) == 0 ) { - user_t *u = user_find( irc, irc->nick ); - - irc_part( irc, u, c->channel ); - - if( c->ic ) + 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( ic->f->privmsg ) + ic->f->privmsg( ic, cmd[2] ); + } + else if( ( iu = irc_user_by_name( irc, cmd[1] ) ) ) + { + if( cmd[2][0] == '\001' ) { - c->joined = 0; - c->ic->acc->prpl->chat_leave( c ); + 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_reply( irc, 403, "%s :No such channel", cmd[1] ); + irc_send_num( irc, 401, "%s :No such nick/channel", cmd[1] ); } } -static void irc_cmd_join( irc_t *irc, char **cmd ) +static void irc_cmd_notice( irc_t *irc, char **cmd ) { - if( g_strcasecmp( cmd[1], irc->channel ) == 0 ) - ; /* Dude, you're already there... - RFC doesn't have any reply for that though? */ - else if( cmd[1] ) + if( !cmd[2] ) { - struct chat *c; - - if( strchr( CTYPES, cmd[1][0] ) == NULL || cmd[1][1] == 0 ) - irc_reply( irc, 479, "%s :Invalid channel name", cmd[1] ); - else if( ( c = chat_bychannel( irc, cmd[1] ) ) && c->acc && c->acc->ic ) - chat_join( irc, c, cmd[2] ); - else - irc_reply( irc, 403, "%s :No such channel", cmd[1] ); + 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( cmd[1], irc->user->nick ) == 0 ) + irc_send_msg( irc->user, "NOTICE", irc->user->nick, cmd[2], NULL ); } -static void irc_cmd_invite( irc_t *irc, char **cmd ) +static void irc_cmd_nickserv( irc_t *irc, char **cmd ) { - char *nick = cmd[1], *channel = cmd[2]; - struct groupchat *c = irc_chat_by_channel( irc, channel ); - user_t *u = user_find( irc, nick ); - - if( u && c && ( u->ic == c->ic ) ) - if( c->ic && c->ic->acc->prpl->chat_invite ) - { - c->ic->acc->prpl->chat_invite( c, u->handle, NULL ); - irc_reply( irc, 341, "%s %s", nick, channel ); - return; - } - - irc_reply( irc, 482, "%s :Invite impossible; User/Channel non-existent or incompatible", channel ); + /* [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_privmsg( irc_t *irc, char **cmd ) + + +static void irc_cmd_oper( irc_t *irc, char **cmd ) { - if ( !cmd[2] ) - { - irc_reply( irc, 412, ":No text to send" ); - } - else if ( irc->nick && g_strcasecmp( cmd[1], irc->nick ) == 0 ) + 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_write( irc, ":%s!%s@%s %s %s :%s", irc->nick, irc->user, irc->host, cmd[0], cmd[1], cmd[2] ); + irc_umode_set( irc, "+o", 1 ); + irc_send_num( irc, 381, ":Password accepted" ); } - else + else { - if( g_strcasecmp( cmd[1], irc->channel ) == 0 ) - { - unsigned int i; - char *t = set_getstr( &irc->set, "default_target" ); - - if( g_strcasecmp( t, "last" ) == 0 && irc->last_target ) - cmd[1] = irc->last_target; - else if( g_strcasecmp( t, "root" ) == 0 ) - cmd[1] = irc->mynick; - - for( i = 0; i < strlen( cmd[2] ); i ++ ) - { - if( cmd[2][i] == ' ' ) break; - if( cmd[2][i] == ':' || cmd[2][i] == ',' ) - { - cmd[1] = cmd[2]; - cmd[2] += i; - *cmd[2] = 0; - while( *(++cmd[2]) == ' ' ); - break; - } - } - - irc->is_private = 0; - - if( cmd[1] != irc->last_target ) - { - g_free( irc->last_target ); - irc->last_target = g_strdup( cmd[1] ); - } - } - else - { - irc->is_private = 1; - } - irc_send( irc, cmd[1], cmd[2], ( g_strcasecmp( cmd[0], "NOTICE" ) == 0 ) ? OPT_AWAY : 0 ); + irc_send_num( irc, 432, ":Incorrect password" ); } } -static void irc_cmd_who( irc_t *irc, char **cmd ) +static void irc_cmd_invite( irc_t *irc, char **cmd ) { - char *channel = cmd[1]; - user_t *u = irc->users; - struct groupchat *c; - GList *l; + irc_channel_t *ic; + irc_user_t *iu; - if( !channel || *channel == '0' || *channel == '*' || !*channel ) - while( u ) - { - irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", u->online ? irc->channel : "*", u->user, u->host, irc->myhost, u->nick, u->online ? ( u->away ? 'G' : 'H' ) : 'G', u->realname ); - u = u->next; - } - else if( g_strcasecmp( channel, irc->channel ) == 0 ) - while( u ) - { - if( u->online ) - irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->away ? 'G' : 'H', u->realname ); - u = u->next; - } - else if( ( c = irc_chat_by_channel( irc, channel ) ) ) - for( l = c->in_room; l; l = l->next ) - { - if( ( u = user_findhandle( c->ic, l->data ) ) ) - irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->away ? 'G' : 'H', u->realname ); - } - else if( ( u = user_find( irc, channel ) ) ) - irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->online ? ( u->away ? 'G' : 'H' ) : 'G', u->realname ); + 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; + } - irc_reply( irc, 315, "%s :End of /WHO list", channel?channel:"**" ); + 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 ) { - user_t *u; int i; /* [TV] Usable USERHOST-implementation according to @@ -326,18 +432,18 @@ static void irc_cmd_userhost( irc_t *irc, char **cmd ) */ for( i = 1; cmd[i]; i ++ ) - if( ( u = user_find( irc, cmd[i] ) ) ) - { - if( u->online && u->away ) - irc_reply( irc, 302, ":%s=-%s@%s", u->nick, u->user, u->host ); - else - irc_reply( irc, 302, ":%s=+%s@%s", u->nick, u->user, u->host ); - } + { + 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 ) { - user_t *u; char buff[IRC_MAX_LINE]; int lenleft, i; @@ -353,17 +459,20 @@ static void irc_cmd_ison( irc_t *irc, char **cmd ) this = cmd[i]; while( *this ) { + irc_user_t *iu; + if( ( next = strchr( this, ' ' ) ) ) *next = 0; - if( ( u = user_find( irc, this ) ) && u->online ) + if( ( iu = irc_user_by_name( irc, this ) ) && + iu->bu && iu->bu->flags & BEE_USER_ONLINE ) { - lenleft -= strlen( u->nick ) + 1; + lenleft -= strlen( iu->nick ) + 1; if( lenleft < 0 ) break; - strcat( buff, u->nick ); + strcat( buff, iu->nick ); strcat( buff, " " ); } @@ -386,7 +495,7 @@ static void irc_cmd_ison( irc_t *irc, char **cmd ) if( strlen( buff ) > 0 ) buff[strlen(buff)-1] = '\0'; - irc_reply( irc, 303, ":%s", buff ); + irc_send_num( irc, 303, ":%s", buff ); } static void irc_cmd_watch( irc_t *irc, char **cmd ) @@ -399,7 +508,7 @@ static void irc_cmd_watch( irc_t *irc, char **cmd ) for( i = 1; cmd[i]; i ++ ) { char *nick; - user_t *u; + irc_user_t *iu; if( !cmd[i][0] || !cmd[i][1] ) break; @@ -407,17 +516,19 @@ static void irc_cmd_watch( irc_t *irc, char **cmd ) nick = g_strdup( cmd[i] + 1 ); nick_lc( nick ); - u = user_find( 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( u && u->online ) - irc_reply( irc, 604, "%s %s %s %d :%s", u->nick, u->user, u->host, (int) time( NULL ), "is online" ); + 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_reply( irc, 605, "%s %s %s %d :%s", nick, "*", "*", (int) time( NULL ), "is offline" ); + irc_send_num( irc, 605, "%s %s %s %d :%s", nick, "*", "*", + (int) time( NULL ), "is offline" ); } else if( cmd[i][0] == '-' ) { @@ -428,7 +539,7 @@ static void irc_cmd_watch( irc_t *irc, char **cmd ) g_hash_table_remove( irc->watches, okey ); g_free( okey ); - irc_reply( irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching" ); + irc_send_num( irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching" ); } } } @@ -436,142 +547,74 @@ static void irc_cmd_watch( irc_t *irc, char **cmd ) static void irc_cmd_topic( irc_t *irc, char **cmd ) { - char *channel = cmd[1]; - char *topic = cmd[2]; + irc_channel_t *ic = irc_channel_by_name( irc, cmd[1] ); + const char *new = cmd[2]; - if( topic ) + if( ic == NULL ) + { + irc_send_num( irc, 403, "%s :No such channel", cmd[1] ); + } + else if( new ) { - /* Send the topic */ - struct groupchat *c = irc_chat_by_channel( irc, channel ); - if( c && c->ic && c->ic->acc->prpl->chat_topic ) - c->ic->acc->prpl->chat_topic( c, topic ); + 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 { - /* Get the topic */ - irc_topic( irc, channel ); + irc_send_topic( ic, FALSE ); } } static void irc_cmd_away( irc_t *irc, char **cmd ) { - user_t *u = user_find( irc, irc->nick ); - char *away = cmd[1]; - - if( !u ) return; - - if( away && *away ) + 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. */ - u->away = g_malloc( strlen( away ) + 1 ); - for( i = j = 0; away[i]; i ++ ) - if( ( u->away[j] = away[i] ) >= ' ' ) + for( i = j = 0; cmd[1][i]; i ++ ) + if( ( away[j] = cmd[1][i] ) >= ' ' ) j ++; - u->away[j] = 0; + away[j] = '\0'; - irc_reply( irc, 306, ":You're now away: %s", u->away ); - /* irc_umode_set( irc, irc->myhost, "+a" ); */ + irc_send_num( irc, 306, ":You're now away: %s", away ); + set_setstr( &irc->b->set, "away", away ); } else { - if( u->away ) g_free( u->away ); - u->away = NULL; - /* irc_umode_set( irc, irc->myhost, "-a" ); */ - irc_reply( irc, 305, ":Welcome back" ); + irc_send_num( irc, 305, ":Welcome back" ); + set_setstr( &irc->b->set, "away", NULL ); } - - set_setstr( &irc->set, "away", u->away ); -} - -static void irc_cmd_whois( irc_t *irc, char **cmd ) -{ - char *nick = cmd[1]; - user_t *u = user_find( irc, nick ); - - if( u ) - { - irc_reply( irc, 311, "%s %s %s * :%s", u->nick, u->user, u->host, u->realname ); - - if( u->ic ) - irc_reply( irc, 312, "%s %s.%s :%s network", u->nick, u->ic->acc->user, - u->ic->acc->server && *u->ic->acc->server ? u->ic->acc->server : "", - u->ic->acc->prpl->name ); - else - irc_reply( irc, 312, "%s %s :%s", u->nick, irc->myhost, IRCD_INFO ); - - if( !u->online ) - irc_reply( irc, 301, "%s :%s", u->nick, "User is offline" ); - else if( u->away ) - irc_reply( irc, 301, "%s :%s", u->nick, u->away ); - if( u->status_msg ) - irc_reply( irc, 320, "%s :%s", u->nick, u->status_msg ); - - irc_reply( irc, 318, "%s :End of /WHOIS list", nick ); - } - else - { - irc_reply( 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_reply( irc, 406, "%s :Nick does not exist", cmd[1] ); - irc_reply( irc, 369, "%s :End of WHOWAS", cmd[1] ); -} - -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_motd( irc_t *irc, char **cmd ) -{ - irc_motd( irc ); -} - -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 he's still alive. */ - irc->last_pong = gettime(); - irc->pinging = 0; } static void irc_cmd_version( irc_t *irc, char **cmd ) { - irc_reply( irc, 351, "bitlbee-%s. %s :%s/%s ", BITLBEE_VERSION, irc->myhost, ARCH, CPU ); + irc_send_num( irc, 351, "bitlbee-%s. %s :%s/%s ", + BITLBEE_VERSION, irc->root->host, ARCH, CPU ); } static void irc_cmd_completions( irc_t *irc, char **cmd ) { - user_t *u = user_find( irc, irc->mynick ); help_t *h; set_t *s; int i; - irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", "OK" ); + irc_send_msg_raw( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS OK" ); for( i = 0; commands[i].command; i ++ ) - irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", commands[i].command ); + irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS %s", commands[i].command ); for( h = global.help; h; h = h->next ) - irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS help ", h->title ); + irc_send_msg_f( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS help %s", h->title ); - for( s = irc->set; s; s = s->next ) - irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS set ", s->key ); + 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_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", "END" ); + irc_send_msg_raw( irc->root, "NOTICE", irc->user->nick, "COMPLETIONS END" ); } static void irc_cmd_rehash( irc_t *irc, char **cmd ) @@ -581,7 +624,7 @@ static void irc_cmd_rehash( irc_t *irc, char **cmd ) else ipc_to_master( cmd ); - irc_reply( irc, 382, "%s :Rehashing", global.conf_file ); + irc_send_num( irc, 382, "%s :Rehashing", global.conf_file ); } static const command_t irc_commands[] = { @@ -590,28 +633,28 @@ static const command_t irc_commands[] = { { "nick", 1, irc_cmd_nick, 0 }, { "quit", 0, irc_cmd_quit, 0 }, { "ping", 0, irc_cmd_ping, 0 }, - { "oper", 2, irc_cmd_oper, IRC_CMD_LOGGED_IN }, - { "mode", 1, irc_cmd_mode, IRC_CMD_LOGGED_IN }, - { "names", 0, irc_cmd_names, IRC_CMD_LOGGED_IN }, - { "part", 1, irc_cmd_part, IRC_CMD_LOGGED_IN }, + { "pong", 0, irc_cmd_pong, IRC_CMD_LOGGED_IN }, { "join", 1, irc_cmd_join, IRC_CMD_LOGGED_IN }, - { "invite", 2, irc_cmd_invite, IRC_CMD_LOGGED_IN }, - { "privmsg", 1, irc_cmd_privmsg, IRC_CMD_LOGGED_IN }, - { "notice", 1, irc_cmd_privmsg, IRC_CMD_LOGGED_IN }, - { "who", 0, irc_cmd_who, 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 }, - { "topic", 1, irc_cmd_topic, IRC_CMD_LOGGED_IN }, - { "away", 0, irc_cmd_away, 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 }, - { "motd", 0, irc_cmd_motd, IRC_CMD_LOGGED_IN }, - { "pong", 0, irc_cmd_pong, 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 }, { "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 }, @@ -637,19 +680,19 @@ void irc_exec( irc_t *irc, char *cmd[] ) if( irc_commands[i].flags & IRC_CMD_PRE_LOGIN && irc->status & USTATUS_LOGGED_IN ) { - irc_reply( irc, 462, ":Only allowed before logging 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_reply( irc, 451, ":Register first" ); + irc_send_num( irc, 451, ":Register first" ); } else if( irc_commands[i].flags & IRC_CMD_OPER_ONLY && !strchr( irc->umode, 'o' ) ) { - irc_reply( irc, 481, ":Permission denied - You're not an IRC operator" ); + irc_send_num( irc, 481, ":Permission denied - You're not an IRC operator" ); } else if( n_arg < irc_commands[i].required_parameters ) { - irc_reply( irc, 461, "%s :Need more parameters", cmd[0] ); + irc_send_num( irc, 461, "%s :Need more parameters", cmd[0] ); } else if( irc_commands[i].flags & IRC_CMD_TO_MASTER ) { @@ -666,5 +709,5 @@ void irc_exec( irc_t *irc, char *cmd[] ) } if( irc->status >= USTATUS_LOGGED_IN ) - irc_reply( irc, 421, "%s :Unknown command", cmd[0] ); + irc_send_num( irc, 421, "%s :Unknown command", cmd[0] ); } diff --git a/irc_im.c b/irc_im.c new file mode 100644 index 00000000..2033b1a8 --- /dev/null +++ b/irc_im.c @@ -0,0 +1,907 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 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( ( s = strchr( bu->handle, '@' ) ) ) + { + iu->host = g_strdup( s + 1 ); + iu->user = g_strndup( bu->handle, s - bu->handle ); + } + else if( bu->ic->acc->server ) + { + iu->host = g_strdup( bu->ic->acc->server ); + iu->user = g_strdup( bu->handle ); + + /* s/ /_/ ... important for AOL screennames */ + for( s = iu->user; *s; s ++ ) + if( *s == ' ' ) + *s = '_'; + } + else + { + iu->host = g_strdup( bu->ic->acc->prpl->name ); + iu->user = g_strdup( bu->handle ); + } + + 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 ) +{ + struct irc_control_channel *icc; + GSList *l; + gboolean match = FALSE; + + 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; + } + + icc = ic->data; + + if( icc->type == IRC_CC_TYPE_DEFAULT ) + match = TRUE; + else if( icc->type == IRC_CC_TYPE_GROUP ) + match = iu->bu->group == icc->group; + else if( icc->type == IRC_CC_TYPE_ACCOUNT ) + match = iu->bu->ic->acc == icc->account; + else if( icc->type == IRC_CC_TYPE_PROTOCOL ) + match = iu->bu->ic->acc->prpl == icc->protocol; + + if( !match ) + { + irc_channel_del_user( ic, iu, IRC_CDU_PART, NULL ); + } + else + { + 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; + char *dst, *prefix = NULL; + char *wrapped, *ts = NULL; + + if( sent_at > 0 && set_getbool( &irc->b->set, "display_timestamps" ) ) + ts = irc_format_timestamp( irc, sent_at ); + + if( iu->last_channel ) + { + dst = iu->last_channel->name; + prefix = g_strdup_printf( "%s%s%s", irc->user->nick, set_getstr( &bee->set, "to_char" ), ts ? : "" ); + } + else + { + dst = irc->user->nick; + prefix = ts; + ts = NULL; + } + + wrapped = word_wrap( msg, 425 ); + irc_send_msg( iu, "PRIVMSG", dst, wrapped, prefix ); + + g_free( wrapped ); + g_free( prefix ); + 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_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; + irc_t *irc = (irc_t *) bee->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" ) ) + { + char *msg = g_strdup_printf( "<< \002BitlBee\002 - Changed name to `%s' >>", iu->fullname ); + irc_send_msg( iu, "NOTICE", irc->user->nick, msg, NULL ); + } + + 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_irc_channel_update( irc, NULL, iu ); + bee_irc_user_nick_update( 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; +} + +/* 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( set_getbool( &iu->irc->b->set, "paste_buffer" ) ) + { + int delay; + + 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( ( 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 + return bee_user_msg( iu->irc->b, iu->bu, msg, 0 ); +} + +static gboolean bee_irc_user_privmsg_cb( gpointer data, gint fd, b_input_condition cond ) +{ + irc_user_t *iu = data; + + bee_user_msg( iu->irc->b, iu->bu, iu->pastebuf->str, 0 ); + + g_string_free( iu->pastebuf, TRUE ); + iu->pastebuf = 0; + iu->pastebuf_timer = 0; + + 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; + } + } + + 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; + 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 ) + 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( 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; +} + +/* 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->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; + + 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 ); + + 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 ); + return TRUE; + } + + 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_add( &ic->set, "account", NULL, set_eval_room_account, ic ); + set_add( &ic->set, "chat_type", "groupchat", set_eval_chat_type, ic ); + set_add( &ic->set, "nick", NULL, NULL, ic ); + 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_usermsg( 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_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_ft_in_start, + bee_irc_ft_out_start, + bee_irc_ft_close, + bee_irc_ft_finished, +}; diff --git a/irc_send.c b/irc_send.c new file mode 100644 index 00000000..b62d2011 --- /dev/null +++ b/irc_send.c @@ -0,0 +1,399 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2010 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 BitlBee gateway, %s", irc->user->nick ); + irc_send_num( irc, 2, ":Host %s is running BitlBee " BITLBEE_VERSION " " ARCH "/" CPU ".", irc->root->host ); + 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=(ov)@+ CHANTYPES=%s CHANMODES=,,,%s NICKLEN=%d NETWORK=BitlBee " + "CASEMAPPING=rfc1459 MAXTARGETS=1 WATCH=128 :are supported by this server", + CTYPES, CMODES, MAX_NICK_LENGTH - 1 ); + irc_send_motd( irc ); +} + +void irc_send_motd( irc_t *irc ) +{ + int fd; + + fd = open( global.conf->motdfile, O_RDONLY ); + if( fd == -1 ) + { + irc_send_num( irc, 422, ":We don't need MOTDs." ); + } + else + { + char linebuf[80]; /* Max. line length for MOTD's is 79 chars. It's what most IRC networks seem to do. */ + char *add, max; + int len; + + linebuf[79] = len = 0; + max = sizeof( linebuf ) - 1; + + irc_send_num( irc, 375, ":- %s Message Of The Day - ", irc->root->host ); + while( read( fd, linebuf + len, 1 ) == 1 ) + { + if( linebuf[len] == '\n' || len == max ) + { + linebuf[len] = 0; + irc_send_num( irc, 372, ":- %s", linebuf ); + len = 0; + } + else if( linebuf[len] == '%' ) + { + read( fd, linebuf + len, 1 ); + 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 + add = "%"; + + strncpy( linebuf + len, add, max - len ); + while( linebuf[++len] ); + } + else if( len < max ) + { + len ++; + } + } + irc_send_num( irc, 376, ":End of MOTD" ); + close( fd ); + } +} + +void irc_usermsg( irc_t *irc, char *format, ... ) +{ + irc_channel_t *ic; + irc_user_t *iu; + char text[1024]; + va_list params; + + va_start( params, format ); + g_vsnprintf( text, sizeof( text ), format, params ); + va_end( params ); + + if( irc->last_root_cmd && + irc_channel_name_ok( irc->last_root_cmd ) && + ( ic = irc_channel_by_name( irc, irc->last_root_cmd ) ) && + ic->flags & IRC_CHANNEL_JOINED ) + irc_send_msg( irc->root, "PRIVMSG", irc->last_root_cmd, text, NULL ); + else if( irc->last_root_cmd && + ( iu = irc_user_by_name( irc, irc->last_root_cmd ) ) && + iu->f == &irc_user_root_funcs ) + irc_send_msg( iu, "PRIVMSG", irc->user->nick, text, NULL ); + else + { + g_free( irc->last_root_cmd ); + irc->last_root_cmd = NULL; + + irc_send_msg( irc->root, "PRIVMSG", irc->user->nick, text, NULL ); + } + + /*return( irc_msgfrom( irc, u->nick, text ) );*/ +} + +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 = strcmp( channel, "**" ) != 0; + + 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", + 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 ); +} + +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 ); + + irc_write( ic->irc, ":%s MODE %s %s", from, ic->name, changes ); +} diff --git a/irc_user.c b/irc_user.c new file mode 100644 index 00000000..8db1de28 --- /dev/null +++ b/irc_user.c @@ -0,0 +1,264 @@ + /********************************************************************\ + * BitlBee -- An IRC to other IM-networks gateway * + * * + * Copyright 2002-2004 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; + + if( set_getbool( &irc->b->set, "private" ) ) + iu->last_channel = NULL; + else + iu->last_channel = irc->default_channel; + + iu->key = g_strdup( nick ); + nick_lc( 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( 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( 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 ); + + 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 ) + send_quit |= irc_channel_del_user( (irc_channel_t*) l->data, iu, IRC_CDU_SILENT, NULL ); + + 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]; + + g_free( iu->irc->last_root_cmd ); + iu->irc->last_root_cmd = g_strdup( iu->nick ); + + 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], "BitlBee " 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, +}; diff --git a/irc_util.c b/irc_util.c new file mode 100644 index 00000000..f664a835 --- /dev/null +++ b/irc_util.c @@ -0,0 +1,115 @@ + /********************************************************************\ + * 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 ); +} diff --git a/lib/Makefile b/lib/Makefile index 441634cd..8fd9b19e 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -7,9 +7,12 @@ ### DEFINITIONS -include ../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)lib/ +endif # [SH] Program variables -objects = arc.o base64.o $(EVENT_HANDLER) http_client.o ini.o md5.o misc.o oauth.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o +objects = arc.o base64.o $(EVENT_HANDLER) ftutil.o http_client.o ini.o md5.o misc.o oauth.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o CFLAGS += -Wall LFLAGS += -r @@ -36,6 +39,6 @@ lib.o: $(objects) $(subdirs) $(objects): ../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/lib/events.h b/lib/events.h index 4baea7b6..fa30cf27 100644 --- a/lib/events.h +++ b/lib/events.h @@ -47,8 +47,10 @@ /* The conditions you can pass to b_input_add()/that will be passed to the given callback function. */ typedef enum { - GAIM_INPUT_READ = 1 << 1, - GAIM_INPUT_WRITE = 1 << 2 + 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); diff --git a/lib/events_glib.c b/lib/events_glib.c index 3e194e98..d6ac82cc 100644 --- a/lib/events_glib.c +++ b/lib/events_glib.c @@ -48,6 +48,7 @@ typedef struct _GaimIOClosure { b_event_handler function; gpointer data; + guint flags; } GaimIOClosure; static GMainLoop *loop = NULL; @@ -75,9 +76,9 @@ static gboolean gaim_io_invoke(GIOChannel *source, GIOCondition condition, gpoin gboolean st; if (condition & GAIM_READ_COND) - gaim_cond |= GAIM_INPUT_READ; + gaim_cond |= B_EV_IO_READ; if (condition & GAIM_WRITE_COND) - gaim_cond |= GAIM_INPUT_WRITE; + 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 ); @@ -86,7 +87,12 @@ static gboolean gaim_io_invoke(GIOChannel *source, GIOCondition condition, gpoin if( !st ) event_debug( "Returned FALSE, cancelling.\n" ); - return st; + 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) @@ -104,10 +110,11 @@ gint b_input_add(gint source, b_input_condition condition, b_event_handler funct closure->function = function; closure->data = data; + closure->flags = condition; - if (condition & GAIM_INPUT_READ) + if (condition & B_EV_IO_READ) cond |= GAIM_READ_COND; - if (condition & GAIM_INPUT_WRITE) + if (condition & B_EV_IO_WRITE) cond |= GAIM_WRITE_COND; channel = g_io_channel_unix_new(source); diff --git a/lib/events_libevent.c b/lib/events_libevent.c index cf616576..43d770ea 100644 --- a/lib/events_libevent.c +++ b/lib/events_libevent.c @@ -59,6 +59,7 @@ struct b_event_data gint timeout; b_event_handler function; void *data; + guint flags; }; void b_main_init() @@ -125,9 +126,9 @@ static void b_event_passthrough( int fd, short event, void *data ) if( fd >= 0 ) { if( event & EV_READ ) - cond |= GAIM_INPUT_READ; + cond |= B_EV_IO_READ; if( event & EV_WRITE ) - cond |= GAIM_INPUT_WRITE; + cond |= B_EV_IO_WRITE; } event_debug( "b_event_passthrough( %d, %d, 0x%x ) (%d)\n", fd, event, (int) data, b_ev->id ); @@ -149,7 +150,7 @@ static void b_event_passthrough( int fd, short event, void *data ) /* This event was killed already, don't touch it! */ return; } - else if( !st ) + else if( !st && !( b_ev->flags & B_EV_FLAG_FORCE_REPEAT ) ) { event_debug( "Handler returned FALSE: " ); b_event_remove( id_cur ); @@ -173,8 +174,8 @@ gint b_input_add( gint fd, b_input_condition condition, b_event_handler function event_debug( "b_input_add( %d, %d, 0x%x, 0x%x ) ", fd, condition, function, data ); - if( ( condition & GAIM_INPUT_READ && ( b_ev = g_hash_table_lookup( read_hash, &fd ) ) ) || - ( condition & GAIM_INPUT_WRITE && ( b_ev = g_hash_table_lookup( write_hash, &fd ) ) ) ) + 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 ); @@ -197,9 +198,9 @@ gint b_input_add( gint fd, b_input_condition condition, b_event_handler function b_ev->data = data; out_cond = EV_PERSIST; - if( condition & GAIM_INPUT_READ ) + if( condition & B_EV_IO_READ ) out_cond |= EV_READ; - if( condition & GAIM_INPUT_WRITE ) + if( condition & B_EV_IO_WRITE ) out_cond |= EV_WRITE; event_set( &b_ev->evinfo, fd, out_cond, b_event_passthrough, b_ev ); @@ -211,6 +212,7 @@ gint b_input_add( gint fd, b_input_condition condition, b_event_handler function 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; } diff --git a/lib/ftutil.c b/lib/ftutil.c new file mode 100644 index 00000000..d59dd4e0 --- /dev/null +++ b/lib/ftutil.c @@ -0,0 +1,134 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Utility functions for file transfer * +* * +* Copyright 2008 Uli Meis <a.sporto+bee@gmail.com> * +* * +* 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 <poll.h> +#include <netinet/tcp.h> +#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 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 <IP-A>[:<Port-A>];<IP-B>[:<Port-B>] 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 + { + 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; + } + + 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; +} diff --git a/lib/ftutil.h b/lib/ftutil.h new file mode 100644 index 00000000..c4a5b02b --- /dev/null +++ b/lib/ftutil.h @@ -0,0 +1,40 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Utility functions for file transfer * +* * +* Copyright 2008 Uli Meis <a.sporto+bee@gmail.com> * +* * +* 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 <sys/param.h> +#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 for_bitlbee_client, char **errptr ); diff --git a/lib/http_client.c b/lib/http_client.c index 529be578..69f06ec5 100644 --- a/lib/http_client.c +++ b/lib/http_client.c @@ -150,10 +150,10 @@ static gboolean http_connected( gpointer data, int source, b_input_condition con if( req->bytes_written < req->request_length ) req->inpa = b_input_add( source, - req->ssl ? ssl_getdirection( req->ssl ) : GAIM_INPUT_WRITE, + req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_WRITE, http_connected, req ); else - req->inpa = b_input_add( source, GAIM_INPUT_READ, http_incoming_data, req ); + req->inpa = b_input_add( source, B_EV_IO_READ, http_incoming_data, req ); return FALSE; @@ -235,7 +235,7 @@ static gboolean http_incoming_data( gpointer data, int source, b_input_condition /* There will be more! */ req->inpa = b_input_add( req->fd, - req->ssl ? ssl_getdirection( req->ssl ) : GAIM_INPUT_READ, + req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_READ, http_incoming_data, req ); return FALSE; @@ -646,3 +646,51 @@ int md5_verify_password( char *password, char *hash ) return ret; } + +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; +} @@ -68,4 +68,6 @@ 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 ); + #endif diff --git a/lib/proxy.c b/lib/proxy.c index e52837fe..baf5823a 100644 --- a/lib/proxy.c +++ b/lib/proxy.c @@ -90,9 +90,9 @@ static gboolean gaim_io_connected(gpointer data, gint source, b_input_condition closesocket(source); b_event_remove(phb->inpa); if( phb->proxy_func ) - phb->proxy_func(phb->proxy_data, -1, GAIM_INPUT_READ); + phb->proxy_func(phb->proxy_data, -1, B_EV_IO_READ); else { - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb); } return FALSE; @@ -101,9 +101,9 @@ static gboolean gaim_io_connected(gpointer data, gint source, b_input_condition sock_make_blocking(source); b_event_remove(phb->inpa); if( phb->proxy_func ) - phb->proxy_func(phb->proxy_data, source, GAIM_INPUT_READ); + phb->proxy_func(phb->proxy_data, source, B_EV_IO_READ); else { - phb->func(phb->data, source, GAIM_INPUT_READ); + phb->func(phb->data, source, B_EV_IO_READ); g_free(phb); } @@ -146,7 +146,7 @@ static int proxy_connect_none(const char *host, unsigned short port, struct PHB return -1; } else { - phb->inpa = b_input_add(fd, GAIM_INPUT_WRITE, gaim_io_connected, phb); + phb->inpa = b_input_add(fd, B_EV_IO_WRITE, gaim_io_connected, phb); phb->fd = fd; return fd; @@ -178,14 +178,14 @@ static gboolean http_canread(gpointer data, gint source, b_input_condition cond) if ((memcmp(HTTP_GOODSTRING, inputline, strlen(HTTP_GOODSTRING)) == 0) || (memcmp(HTTP_GOODSTRING2, inputline, strlen(HTTP_GOODSTRING2)) == 0)) { - phb->func(phb->data, source, GAIM_INPUT_READ); + 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, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); @@ -203,7 +203,7 @@ static gboolean http_canwrite(gpointer data, gint source, b_input_condition cond len = sizeof(error); if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; @@ -214,7 +214,7 @@ static gboolean http_canwrite(gpointer data, gint source, b_input_condition cond phb->host, phb->port); if (send(source, cmd, strlen(cmd), 0) < 0) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; @@ -229,7 +229,7 @@ static gboolean http_canwrite(gpointer data, gint source, b_input_condition cond g_free(t2); if (send(source, cmd, strlen(cmd), 0) < 0) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; @@ -239,13 +239,13 @@ static gboolean http_canwrite(gpointer data, gint source, b_input_condition cond g_snprintf(cmd, sizeof(cmd), "\r\n"); if (send(source, cmd, strlen(cmd), 0) < 0) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } - phb->inpa = b_input_add(source, GAIM_INPUT_READ, http_canread, phb); + phb->inpa = b_input_add(source, B_EV_IO_READ, http_canread, phb); return FALSE; } @@ -272,14 +272,14 @@ static gboolean s4_canread(gpointer data, gint source, b_input_condition cond) memset(packet, 0, sizeof(packet)); if (read(source, packet, 9) >= 4 && packet[1] == 90) { - phb->func(phb->data, source, GAIM_INPUT_READ); + 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, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); @@ -298,7 +298,7 @@ static gboolean s4_canwrite(gpointer data, gint source, b_input_condition cond) len = sizeof(error); if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; @@ -308,7 +308,7 @@ static gboolean s4_canwrite(gpointer data, gint source, b_input_condition cond) /* XXX does socks4 not support host name lookups by the proxy? */ if (!(hp = gethostbyname(phb->host))) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; @@ -325,13 +325,13 @@ static gboolean s4_canwrite(gpointer data, gint source, b_input_condition cond) packet[8] = 0; if (write(source, packet, 9) != 9) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } - phb->inpa = b_input_add(source, GAIM_INPUT_READ, s4_canread, phb); + phb->inpa = b_input_add(source, B_EV_IO_READ, s4_canread, phb); return FALSE; } @@ -358,20 +358,20 @@ static gboolean s5_canread_again(gpointer data, gint source, b_input_condition c if (read(source, buf, 10) < 10) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + 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, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } - phb->func(phb->data, source, GAIM_INPUT_READ); + phb->func(phb->data, source, B_EV_IO_READ); g_free(phb->host); g_free(phb); @@ -395,13 +395,13 @@ static void s5_sendconnect(gpointer data, gint source) if (write(source, buf, (5 + strlen(phb->host) + 2)) < (5 + strlen(phb->host) + 2)) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return; } - phb->inpa = b_input_add(source, GAIM_INPUT_READ, s5_canread_again, phb); + 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) @@ -413,7 +413,7 @@ static gboolean s5_readauth(gpointer data, gint source, b_input_condition cond) if (read(source, buf, 2) < 2) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; @@ -421,7 +421,7 @@ static gboolean s5_readauth(gpointer data, gint source, b_input_condition cond) if ((buf[0] != 0x01) || (buf[1] != 0x00)) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; @@ -441,7 +441,7 @@ static gboolean s5_canread(gpointer data, gint source, b_input_condition cond) if (read(source, buf, 2) < 2) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; @@ -449,7 +449,7 @@ static gboolean s5_canread(gpointer data, gint source, b_input_condition cond) if ((buf[0] != 0x05) || (buf[1] == 0xff)) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; @@ -464,13 +464,13 @@ static gboolean s5_canread(gpointer data, gint source, b_input_condition cond) memcpy(buf + 2 + i + 1, proxypass, j); if (write(source, buf, 3 + i + j) < 3 + i + j) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } - phb->inpa = b_input_add(source, GAIM_INPUT_READ, s5_readauth, phb); + phb->inpa = b_input_add(source, B_EV_IO_READ, s5_readauth, phb); } else { s5_sendconnect(phb, source); } @@ -490,7 +490,7 @@ static gboolean s5_canwrite(gpointer data, gint source, b_input_condition cond) len = sizeof(error); if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; @@ -512,13 +512,13 @@ static gboolean s5_canwrite(gpointer data, gint source, b_input_condition cond) if (write(source, buf, i) < i) { close(source); - phb->func(phb->data, -1, GAIM_INPUT_READ); + phb->func(phb->data, -1, B_EV_IO_READ); g_free(phb->host); g_free(phb); return FALSE; } - phb->inpa = b_input_add(source, GAIM_INPUT_READ, s5_canread, phb); + phb->inpa = b_input_add(source, B_EV_IO_READ, s5_canread, phb); return FALSE; } diff --git a/lib/ssl_bogus.c b/lib/ssl_bogus.c index a07ea752..9c368c66 100644 --- a/lib/ssl_bogus.c +++ b/lib/ssl_bogus.c @@ -58,7 +58,7 @@ void *ssl_starttls( int fd, ssl_input_function func, gpointer data ) b_input_condition ssl_getdirection( void *conn ) { - return GAIM_INPUT_READ; + return B_EV_IO_READ; } int ssl_pending( void *conn ) diff --git a/lib/ssl_client.h b/lib/ssl_client.h index f91d0d70..0a8e82d8 100644 --- a/lib/ssl_client.h +++ b/lib/ssl_client.h @@ -70,7 +70,7 @@ G_MODULE_EXPORT void ssl_disconnect( void *conn_ ); handling. */ G_MODULE_EXPORT int ssl_getfd( void *conn ); -/* This function returns GAIM_INPUT_READ/WRITE. With SSL connections it's +/* 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 diff --git a/lib/ssl_gnutls.c b/lib/ssl_gnutls.c index f5945442..5a14b825 100644 --- a/lib/ssl_gnutls.c +++ b/lib/ssl_gnutls.c @@ -105,7 +105,7 @@ static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition { struct scd *conn = data; - return ssl_connected( conn, conn->fd, GAIM_INPUT_WRITE ); + return ssl_connected( conn, conn->fd, B_EV_IO_WRITE ); } static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ) @@ -243,5 +243,5 @@ int ssl_getfd( void *conn ) b_input_condition ssl_getdirection( void *conn ) { return( gnutls_record_get_direction( ((struct scd*)conn)->session ) ? - GAIM_INPUT_WRITE : GAIM_INPUT_READ ); + B_EV_IO_WRITE : B_EV_IO_READ ); } diff --git a/lib/ssl_nss.c b/lib/ssl_nss.c index eba3c441..de6e7ec6 100644 --- a/lib/ssl_nss.c +++ b/lib/ssl_nss.c @@ -192,5 +192,5 @@ int ssl_getfd( void *conn ) b_input_condition ssl_getdirection( void *conn ) { /* Just in case someone calls us, let's return the most likely case: */ - return GAIM_INPUT_READ; + return B_EV_IO_READ; } diff --git a/lib/ssl_openssl.c b/lib/ssl_openssl.c index fc6d433e..8abff390 100644 --- a/lib/ssl_openssl.c +++ b/lib/ssl_openssl.c @@ -101,7 +101,7 @@ static gboolean ssl_starttls_real( gpointer data, gint source, b_input_condition { struct scd *conn = data; - return ssl_connected( conn, conn->fd, GAIM_INPUT_WRITE ); + return ssl_connected( conn, conn->fd, B_EV_IO_WRITE ); } static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ) @@ -269,5 +269,5 @@ int ssl_getfd( void *conn ) b_input_condition ssl_getdirection( void *conn ) { - return( ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_WRITE ? GAIM_INPUT_WRITE : GAIM_INPUT_READ ); + return( ((struct scd*)conn)->lasterr == SSL_ERROR_WANT_WRITE ? B_EV_IO_WRITE : B_EV_IO_READ ); } diff --git a/lib/ssl_sspi.c b/lib/ssl_sspi.c index a16423b1..e14c451e 100644 --- a/lib/ssl_sspi.c +++ b/lib/ssl_sspi.c @@ -274,5 +274,5 @@ int ssl_getfd(void *conn) GaimInputCondition ssl_getdirection( void *conn ) { - return GAIM_INPUT_WRITE; /* FIXME: or GAIM_INPUT_READ */ + return B_EV_IO_WRITE; /* FIXME: or B_EV_IO_READ */ } @@ -1,7 +1,7 @@ /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * - * Copyright 2002-2007 Wilmer van der Gaast and others * + * Copyright 2002-2010 Wilmer van der Gaast and others * \********************************************************************/ /* Some stuff to fetch, save and handle nicknames for your buddies */ @@ -26,6 +26,12 @@ #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 ) { @@ -41,61 +47,180 @@ static char *clean_handle( const char *orig ) return new; } -void nick_set( account_t *acc, const char *handle, const char *nick ) +void nick_set_raw( account_t *acc, const char *handle, const char *nick ) { char *store_handle, *store_nick = g_malloc( MAX_NICK_LENGTH + 1 ); store_handle = clean_handle( handle ); - store_nick[MAX_NICK_LENGTH] = 0; + store_nick[MAX_NICK_LENGTH] = '\0'; strncpy( store_nick, nick, MAX_NICK_LENGTH ); nick_strip( store_nick ); g_hash_table_replace( acc->nicks, store_handle, store_nick ); } -char *nick_get( account_t *acc, const char *handle ) +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; memset( nick, 0, MAX_NICK_LENGTH + 1 ); - store_handle = clean_handle( handle ); + 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( acc->nicks, store_handle ) ) ) + 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", handle ); + g_snprintf( nick, MAX_NICK_LENGTH, "%s", bu->handle ); if( ( s = strchr( nick, '@' ) ) ) while( *s ) *(s++) = 0; nick_strip( nick ); - if( set_getbool( &acc->irc->set, "lcnicks" ) ) + if( set_getbool( &bu->bee->set, "lcnicks" ) ) nick_lc( 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( acc, handle, nick ); + nick_dedupe( bu, nick ); return nick; } -void nick_dedupe( account_t *acc, const char *handle, char nick[MAX_NICK_LENGTH+1] ) +char *nick_gen( bee_user_t *bu ) +{ + gboolean ok = FALSE; /* Set to true once the nick contains something unique. */ + GString *ret = g_string_new( "" ); + 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, chop = '\0', *asc = NULL; + int len = MAX_NICK_LENGTH; + + 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' ) + 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 + { + return NULL; + } + } + + /* 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( part ) + part = asc = g_convert( part, -1, "ASCII//TRANSLIT//IGNORE", + "UTF-8", NULL, NULL, NULL ); + + if( ret->len == 0 && part && isdigit( *part ) ) + g_string_append_c( ret, '_' ); + + while( part && *part && *part != chop && len > 0 ) + { + if( strchr( nick_lc_chars, *part ) || + strchr( nick_uc_chars, *part ) ) + g_string_append_c( ret, *part ); + + part ++; + len --; + } + g_free( asc ); + } + + /* This returns NULL if the nick is empty or otherwise not ok. */ + return g_string_free( ret, ret->len == 0 || !ok ); +} + +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( nick ) || user_find( acc->irc, nick ) ) + while( !nick_ok( nick ) || + ( ( iu = irc_user_by_name( irc, nick ) ) && iu->bu != bu ) ) { if( strlen( nick ) < ( MAX_NICK_LENGTH - 1 ) ) { @@ -111,19 +236,19 @@ void nick_dedupe( account_t *acc, const char *handle, char nick[MAX_NICK_LENGTH+ { int i; - irc_usermsg( acc->irc, "Warning: Almost had an infinite loop in nick_get()! " - "This used to be a fatal BitlBee bug, but we tried to fix it. " - "This message should *never* appear anymore. " - "If it does, please *do* send us a bug report! " - "Please send all the following lines in your report:" ); + irc_usermsg( irc, "Warning: Almost had an infinite loop in nick_get()! " + "This used to be a fatal BitlBee bug, but we tried to fix it. " + "This message should *never* appear anymore. " + "If it does, please *do* send us a bug report! " + "Please send all the following lines in your report:" ); - irc_usermsg( acc->irc, "Trying to get a sane nick for handle %s", handle ); + irc_usermsg( irc, "Trying to get a sane nick for handle %s", bu->handle ); for( i = 0; i < MAX_NICK_LENGTH; i ++ ) - irc_usermsg( acc->irc, "Char %d: %c/%d", i, nick[i], nick[i] ); + irc_usermsg( irc, "Char %d: %c/%d", i, nick[i], nick[i] ); - irc_usermsg( acc->irc, "FAILED. Returning an insane nick now. Things might break. " - "Good luck, and please don't forget to paste the lines up here " - "in #bitlbee on OFTC or in a mail to wilmer@gaast.net" ); + irc_usermsg( irc, "FAILED. Returning an insane nick now. Things might break. " + "Good luck, and please don't forget to paste the lines up here " + "in #bitlbee on OFTC or in a mail to wilmer@gaast.net" ); g_snprintf( nick, MAX_NICK_LENGTH + 1, "xx%x", rand() ); @@ -134,29 +259,23 @@ void nick_dedupe( account_t *acc, const char *handle, char nick[MAX_NICK_LENGTH+ /* Just check if there is a nickname set for this buddy or if we'd have to generate one. */ -int nick_saved( account_t *acc, const char *handle ) +int nick_saved( bee_user_t *bu ) { char *store_handle, *found; - store_handle = clean_handle( handle ); - found = g_hash_table_lookup( acc->nicks, store_handle ); + 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( account_t *acc, const char *handle ) +void nick_del( bee_user_t *bu ) { - g_hash_table_remove( acc->nicks, handle ); + g_hash_table_remove( bu->ic->acc->nicks, bu->handle ); } -/* Character maps, _lc_[x] == _uc_[x] (but uppercase), according to the RFC's. - With one difference, we allow dashes. */ - -static char *nick_lc_chars = "0123456789abcdefghijklmnopqrstuvwxyz{}^`-_|"; -static char *nick_uc_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[]~`-_\\"; - void nick_strip( char *nick ) { int i, j; @@ -23,11 +23,13 @@ Suite 330, Boston, MA 02111-1307 USA */ -void nick_set( account_t *acc, const char *handle, const char *nick ); -char *nick_get( account_t *acc, const char *handle ); -void nick_dedupe( account_t *acc, const char *handle, char nick[MAX_NICK_LENGTH+1] ); -int nick_saved( account_t *acc, const char *handle ); -void nick_del( account_t *acc, const char *handle ); +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( char *nick ); int nick_ok( const char *nick ); diff --git a/protocols/Makefile b/protocols/Makefile index 18d79e8d..d4aa6e14 100644 --- a/protocols/Makefile +++ b/protocols/Makefile @@ -7,9 +7,13 @@ ### DEFINITIONS -include ../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/ +endif # [SH] Program variables -objects = nogaim.o +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 @@ -48,6 +52,6 @@ protocols.o: $(objects) $(subdirs) $(objects): ../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/account.c b/protocols/account.c index a844d229..7fceae91 100644 --- a/account.c +++ b/protocols/account.c @@ -26,43 +26,69 @@ #define BITLBEE_CORE #include "bitlbee.h" #include "account.h" -#include "chat.h" -account_t *account_add( irc_t *irc, struct prpl *prpl, char *user, char *pass ) +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( irc->accounts ) + if( bee->accounts ) { - for( a = irc->accounts; a->next; a = a->next ); + for( a = bee->accounts; a->next; a = a->next ); a = a->next = g_new0( account_t, 1 ); } else { - irc->accounts = a = g_new0 ( account_t, 1 ); + 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->irc = irc; + a->bee = bee; s = set_add( &a->set, "auto_connect", "true", set_eval_account, a ); s->flags |= ACC_SET_NOSAVE; s = set_add( &a->set, "auto_reconnect", "true", set_eval_bool, a ); - s = set_add( &a->set, "nick_source", "handle", NULL, 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 |= ACC_SET_NOSAVE; /* Just for bw compatibility! */ s = set_add( &a->set, "password", NULL, set_eval_account, a ); s->flags |= ACC_SET_NOSAVE | SET_NULL_OK; + s = set_add( &a->set, "tag", NULL, set_eval_account, a ); + s->flags |= ACC_SET_NOSAVE; + s = set_add( &a->set, "username", NULL, set_eval_account, a ); s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY; set_setstr( &a->set, "username", user ); + if( account_by_tag( bee, prpl->name ) ) + { + int i; + + for( i = 2; i < 10000; i ++ ) + { + sprintf( tag, "%s%d", prpl->name, i ); + if( !account_by_tag( bee, tag ) ) + break; + } + } + else + { + strcpy( tag, prpl->name ); + } + 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 @@ -125,6 +151,18 @@ char *set_eval_account( set_t *set, char *value ) return SET_INVALID; } } + 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 ) ) @@ -152,12 +190,31 @@ char *set_eval_account( set_t *set, char *value ) return SET_INVALID; } -account_t *account_get( irc_t *irc, char *id ) +/* 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 ) { @@ -168,7 +225,7 @@ account_t *account_get( irc_t *irc, char *id ) if( ( proto = find_protocol( id ) ) ) { - for( a = irc->accounts; a; a = a->next ) + for( a = bee->accounts; a; a = a->next ) if( a->prpl == proto && a->prpl->handle_cmp( handle, a->user ) == 0 ) ret = a; @@ -185,14 +242,14 @@ account_t *account_get( irc_t *irc, char *id ) if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 ) { - for( a = irc->accounts; a; a = a->next ) + for( a = bee->accounts; a; a = a->next ) if( ( nr-- ) == 0 ) return( a ); return( NULL ); } - for( a = irc->accounts; a; a = a->next ) + for( a = bee->accounts; a; a = a->next ) { if( g_strcasecmp( id, a->prpl->name ) == 0 ) { @@ -213,35 +270,48 @@ account_t *account_get( irc_t *irc, char *id ) return( ret ); } -void account_del( irc_t *irc, account_t *acc ) +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; - struct chat *c, *nc; if( acc->ic ) /* Caller should have checked, accounts still in use can't be deleted. */ return; - for( a = irc->accounts; a; a = (l=a)->next ) + for( a = bee->accounts; a; a = (l=a)->next ) if( a == acc ) { if( l ) l->next = a->next; else - irc->accounts = a->next; + bee->accounts = a->next; - for( c = irc->chatrooms; c; c = nc ) + /** FIXME + for( c = bee->chatrooms; c; c = nc ) { nc = c->next; if( acc == c->acc ) - chat_del( irc, c ); + 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 ); @@ -253,7 +323,7 @@ void account_del( irc_t *irc, account_t *acc ) } } -void account_on( irc_t *irc, account_t *a ) +void account_on( bee_t *bee, account_t *a ) { if( a->ic ) { @@ -267,7 +337,7 @@ void account_on( irc_t *irc, account_t *a ) a->prpl->login( a ); } -void account_off( irc_t *irc, account_t *a ) +void account_off( bee_t *bee, account_t *a ) { imc_logout( a->ic, FALSE ); a->ic = NULL; @@ -335,7 +405,7 @@ char *set_eval_account_reconnect_delay( set_t *set, char *value ) int account_reconnect_delay( account_t *a ) { - char *setting = set_getstr( &a->irc->set, "auto_reconnect_delay" ); + char *setting = set_getstr( &a->bee->set, "auto_reconnect_delay" ); struct account_reconnect_delay p; if( account_reconnect_delay_parse( setting, &p ) ) diff --git a/account.h b/protocols/account.h index 984dcfe6..a39be2e2 100644 --- a/account.h +++ b/protocols/account.h @@ -32,6 +32,7 @@ typedef struct account char *user; char *pass; char *server; + char *tag; int auto_connect; int auto_reconnect_delay; @@ -41,16 +42,17 @@ typedef struct account set_t *set; GHashTable *nicks; - struct irc *irc; + struct bee *bee; struct im_connection *ic; struct account *next; } account_t; -account_t *account_add( irc_t *irc, struct prpl *prpl, char *user, char *pass ); -account_t *account_get( irc_t *irc, char *id ); -void account_del( irc_t *irc, account_t *acc ); -void account_on( irc_t *irc, account_t *a ); -void account_off( irc_t *irc, account_t *a ); +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 ); diff --git a/protocols/bee.c b/protocols/bee.c new file mode 100644 index 00000000..c5eeee17 --- /dev/null +++ b/protocols/bee.c @@ -0,0 +1,95 @@ + /********************************************************************\ + * 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, "away", NULL, set_eval_away_status, b ); + s->flags |= SET_NULL_OK; + 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, "debug", "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; +} diff --git a/protocols/bee.h b/protocols/bee.h new file mode 100644 index 00000000..4b6a1f4a --- /dev/null +++ b/protocols/bee.h @@ -0,0 +1,151 @@ + /********************************************************************\ + * 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 +{ + struct set *set; + + GSList *users; + GSList *groups; + 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(). This seems slightly cleaner than abusing NULL. */ + struct bee_user *user; + + const struct bee_ui_funcs *ui; + void *ui_data; +} bee_t; + +bee_t *bee_new(); +void bee_free( bee_t *b ); + +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_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; + char *status_msg; + + time_t login_time, idle_time; + + bee_t *bee; + void *ui_data; +} bee_user_t; + +typedef struct bee_group +{ + char *key; + 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 ); + gboolean (*user_fullname)( bee_t *bee, bee_user_t *bu ); + gboolean (*user_nick_hint)( bee_t *bee, bee_user_t *bu, const char *hint ); + gboolean (*user_group)( bee_t *bee, bee_user_t *bu ); + gboolean (*user_status)( bee_t *bee, struct bee_user *bu, struct bee_user *old ); + gboolean (*user_msg)( bee_t *bee, bee_user_t *bu, const char *msg, time_t sent_at ); + gboolean (*user_typing)( bee_t *bee, bee_user_t *bu, guint32 flags ); + + gboolean (*chat_new)( bee_t *bee, struct groupchat *c ); + gboolean (*chat_free)( bee_t *bee, struct groupchat *c ); + 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, bee_user_t *bu ); + gboolean (*chat_name_hint)( bee_t *bee, struct groupchat *c, const char *name ); + + 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_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 */ +#if 0 +struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle ); +void imcb_chat_name_hint( struct groupchat *c, const char *name ); +void imcb_chat_free( struct groupchat *c ); +void imcb_chat_msg( struct groupchat *c, const char *who, char *msg, uint32_t flags, time_t sent_at ); +void imcb_chat_log( struct groupchat *c, char *format, ... ); +void imcb_chat_topic( struct groupchat *c, char *who, char *topic, time_t set_at ); +void imcb_chat_add_buddy( struct groupchat *b, const char *handle ); +void imcb_chat_remove_buddy( struct groupchat *b, const char *handle, const char *reason ); +static int remove_chat_buddy_silent( struct groupchat *b, const char *handle ); +#endif +int bee_chat_msg( bee_t *bee, struct groupchat *c, const char *msg, int flags ); +struct groupchat *bee_chat_by_title( bee_t *bee, struct im_connection *ic, const char *title ); + +#endif /* __BEE_H__ */ diff --git a/protocols/bee_chat.c b/protocols/bee_chat.c new file mode 100644 index 00000000..3be6f189 --- /dev/null +++ b/protocols/bee_chat.c @@ -0,0 +1,234 @@ + /********************************************************************\ + * 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 ) + 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, "<html>", 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; +} diff --git a/protocols/bee_ft.c b/protocols/bee_ft.c new file mode 100644 index 00000000..1026eab3 --- /dev/null +++ b/protocols/bee_ft.c @@ -0,0 +1,66 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2010 Wilmer van der Gaast <wilmer@gaast.net> * +\********************************************************************/ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License 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 ); +} diff --git a/protocols/bee_user.c b/protocols/bee_user.c new file mode 100644 index 00000000..4399a566 --- /dev/null +++ b/protocols/bee_user.c @@ -0,0 +1,248 @@ + /********************************************************************\ + * 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 ); + + /* 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 ); + + 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, "<html>", 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 = g_strdup( ( flags & OPT_AWAY ) && state == NULL ? "Away" : state ); + bu->status_msg = g_strdup( message ); + + if( bee->ui->user_status ) + bee->ui->user_status( bee, bu, old ); + + g_free( old->status_msg ); + g_free( old->status ); + 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 ) + { + 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( ( 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( msg ); + + 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, 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 ); + } +} diff --git a/protocols/ft.h b/protocols/ft.h new file mode 100644 index 00000000..159f16f2 --- /dev/null +++ b/protocols/ft.h @@ -0,0 +1,176 @@ +/********************************************************************\ +* 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 diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile index e7a505ba..912ea702 100644 --- a/protocols/jabber/Makefile +++ b/protocols/jabber/Makefile @@ -7,9 +7,12 @@ ### 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 sasl.o +objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o s5bytestream.o sasl.o si.o CFLAGS += -Wall LFLAGS += -r @@ -32,7 +35,7 @@ distclean: clean $(objects): ../../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/protocols/jabber/conference.c b/protocols/jabber/conference.c index affe8aef..e04b9792 100644 --- a/protocols/jabber/conference.c +++ b/protocols/jabber/conference.c @@ -91,18 +91,20 @@ static xt_status jabber_chat_join_failed( struct im_connection *ic, struct xt_no 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( ret = ic->groupchats; ret; ret = ret->next ) + 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 ret; + return l ? ret : NULL; } void jabber_chat_free( struct groupchat *c ) diff --git a/protocols/jabber/io.c b/protocols/jabber/io.c index a14ad21c..d6f92a5f 100644 --- a/protocols/jabber/io.c +++ b/protocols/jabber/io.c @@ -63,7 +63,7 @@ int jabber_write( struct im_connection *ic, char *buf, int len ) 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, GAIM_INPUT_WRITE, jabber_write_callback, ic ); + jd->w_inpa = b_input_add( jd->fd, B_EV_IO_WRITE, jabber_write_callback, ic ); } else { @@ -503,7 +503,7 @@ gboolean jabber_start_stream( struct im_connection *ic ) jd->xt = xt_new( jabber_handlers, ic ); if( jd->r_inpa <= 0 ) - jd->r_inpa = b_input_add( jd->fd, GAIM_INPUT_READ, jabber_read_callback, ic ); + jd->r_inpa = b_input_add( jd->fd, B_EV_IO_READ, jabber_read_callback, ic ); greet = g_strdup_printf( "%s<stream:stream to=\"%s\" xmlns=\"jabber:client\" " "xmlns:stream=\"http://etherx.jabber.org/streams\" version=\"1.0\">", diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c index 68777b0b..82c90d39 100644 --- a/protocols/jabber/iq.c +++ b/protocols/jabber/iq.c @@ -90,14 +90,17 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) xt_add_attr( reply, "id", s ); pack = 0; } - else if( strcmp( s, XMLNS_DISCOVER ) == 0 ) + else if( strcmp( s, XMLNS_DISCO_INFO ) == 0 ) { - const char *features[] = { XMLNS_DISCOVER, + const char *features[] = { XMLNS_DISCO_INFO, XMLNS_VERSION, XMLNS_TIME, XMLNS_CHATSTATES, XMLNS_MUC, XMLNS_PING, + XMLNS_SI, + XMLNS_BYTESTREAMS, + XMLNS_FILETRANSFER, NULL }; const char **f; @@ -117,23 +120,28 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) else { xt_free_node( reply ); - reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel" ); + 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, "query" ) ) || - !( s = xt_find_attr( c, "xmlns" ) ) ) + 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. */ - if( strcmp( s, XMLNS_ROSTER ) == 0 ) - { int bare_len = strlen( ic->acc->user ); if( ( s = xt_find_attr( node, "from" ) ) == NULL || @@ -150,14 +158,19 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ) 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" ); + 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" ); + reply = jabber_make_error_packet( node, "feature-not-implemented", "cancel", NULL ); pack = 0; } } @@ -368,7 +381,7 @@ static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node * c = query->children; while( ( c = xt_find_node( c, "item" ) ) ) { - struct xt_node *group = xt_find_node( node->children, "group" ); + 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" ); @@ -377,9 +390,8 @@ static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node * { if( ( strcmp( sub, "both" ) == 0 || strcmp( sub, "to" ) == 0 ) ) { - if( initial || imcb_find_buddy( ic, jid ) == NULL ) - imcb_add_buddy( ic, jid, ( group && group->text_len ) ? - group->text : NULL ); + imcb_add_buddy( ic, jid, ( group && group->text_len ) ? + group->text : NULL ); if( name ) imcb_rename_buddy( ic, jid, name ); @@ -542,7 +554,7 @@ static xt_status jabber_iq_display_vcard( struct im_connection *ic, struct xt_no 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, char *handle, char *name ) +int jabber_add_to_roster( struct im_connection *ic, const char *handle, const char *name, const char *group ) { struct xt_node *node; int st; @@ -552,6 +564,8 @@ int jabber_add_to_roster( struct im_connection *ic, char *handle, char *name ) 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 ); @@ -575,7 +589,7 @@ static xt_status jabber_add_to_roster_callback( struct im_connection *ic, struct ( s = xt_find_attr( node, "type" ) ) && strcmp( s, "result" ) == 0 ) { - if( imcb_find_buddy( ic, jid ) == NULL ) + if( bee_user_by_handle( ic->bee, ic, jid ) == NULL ) imcb_add_buddy( ic, jid, NULL ); } else @@ -607,3 +621,175 @@ int jabber_remove_from_roster( struct im_connection *ic, char *handle ) 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; +} diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c index 75351d0d..01353f8e 100644 --- a/protocols/jabber/jabber.c +++ b/protocols/jabber/jabber.c @@ -64,6 +64,8 @@ static void jabber_init( account_t *acc ) s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add( &acc->set, "priority", "0", set_eval_priority, acc ); + + s = set_add( &acc->set, "proxy", "<local>;<auto>", NULL, acc ); s = set_add( &acc->set, "resource", "BitlBee", NULL, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; @@ -265,11 +267,23 @@ 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 ); + jabber_chat_free( ic->groupchats->data ); if( jd->r_inpa >= 0 ) b_event_remove( jd->r_inpa ); @@ -400,7 +414,7 @@ static void jabber_add_buddy( struct im_connection *ic, char *who, char *group ) return; } - if( jabber_add_to_roster( ic, who, NULL ) ) + if( jabber_add_to_roster( ic, who, NULL, group ) ) presence_send_request( ic, who, "subscribe" ); } @@ -426,7 +440,7 @@ static void jabber_remove_buddy( struct im_connection *ic, char *who, char *grou 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 ) +static struct groupchat *jabber_chat_join_( struct im_connection *ic, const char *room, const char *nick, const char *password, set_t **sets ) { if( strchr( room, '@' ) == NULL ) imcb_error( ic, "Invalid room name: %s", room ); @@ -548,6 +562,7 @@ void jabber_initmodule() ret->keepalive = jabber_keepalive; ret->send_typing = jabber_send_typing; ret->handle_cmp = g_strcasecmp; + ret->transfer_request = jabber_si_transfer_request; register_protocol( ret ); } diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h index 3f4144b8..45a1c5c1 100644 --- a/protocols/jabber/jabber.h +++ b/protocols/jabber/jabber.h @@ -60,6 +60,14 @@ typedef enum have a real JID. */ } 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 @@ -90,6 +98,10 @@ struct jabber_data md5_state_t cached_id_prefix; GHashTable *node_cache; GHashTable *buddies; + + GSList *filetransfers; + GSList *streamhosts; + int have_streamhosts; }; struct jabber_away_state @@ -126,6 +138,7 @@ struct jabber_buddy int priority; struct jabber_away_state *away_state; char *away_message; + GSList *features; time_t last_msg; jabber_buddy_flags_t flags; @@ -141,6 +154,36 @@ struct jabber_chat struct jabber_buddy *me; }; +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" /* Prefixes to use for packet IDs (mainly for IQ packets ATM). Usually the @@ -166,17 +209,24 @@ struct jabber_chat #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 "jabber:iq:time" /* XEP-0090 */ -#define XMLNS_PING "urn:xmpp:ping" /* XEP-0199 */ -#define XMLNS_VCARD "vcard-temp" /* XEP-0054 */ -#define XMLNS_DELAY "jabber:x:delay" /* XEP-0091 */ -#define XMLNS_CHATSTATES "http://jabber.org/protocol/chatstates" /* 0085 */ -#define XMLNS_DISCOVER "http://jabber.org/protocol/disco#info" /* 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_AUTH "jabber:iq:auth" /* XEP-0078 */ +#define XMLNS_VERSION "jabber:iq:version" /* XEP-0092 */ +#define XMLNS_TIME "jabber:iq:time" /* XEP-0090 */ +#define XMLNS_PING "urn:xmpp:ping" /* XEP-0199 */ +#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 */ /* iq.c */ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ); @@ -184,8 +234,20 @@ 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, char *handle, char *name ); +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 ); + +/* 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 ); @@ -199,7 +261,7 @@ int presence_send_request( struct im_connection *ic, char *handle, char *request 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 ); +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 ); diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c index 651b7068..ab3e6c38 100644 --- a/protocols/jabber/jabber_util.c +++ b/protocols/jabber/jabber_util.c @@ -98,7 +98,7 @@ struct xt_node *jabber_make_packet( char *name, char *type, char *to, struct xt_ return node; } -struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, char *err_type ) +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; @@ -111,6 +111,10 @@ struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond, 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 <error>/type="error" tag. Including the original packet is recommended, so let's just do it. */ @@ -274,8 +278,7 @@ static void jabber_buddy_ask_yes( void *data ) presence_send_request( bla->ic, bla->handle, "subscribed" ); - if( imcb_find_buddy( bla->ic, bla->handle ) == NULL ) - imcb_ask_add( bla->ic, bla->handle, NULL ); + imcb_ask_add( bla->ic, bla->handle, NULL ); g_free( bla->handle ); g_free( bla ); @@ -457,7 +460,7 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_, } if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && - ( bare_exists || imcb_find_buddy( ic, jid ) ) ) + ( bare_exists || bee_user_by_handle( ic->bee, ic, jid ) ) ) { *s = '/'; bud = jabber_buddy_add( ic, jid ); @@ -478,7 +481,8 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_, if( bud == NULL ) /* No match. Create it now? */ - return ( ( flags & GET_BUDDY_CREAT ) && imcb_find_buddy( ic, jid_ ) ) ? + 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. */ diff --git a/protocols/jabber/presence.c b/protocols/jabber/presence.c index 006eeead..2875d23e 100644 --- a/protocols/jabber/presence.c +++ b/protocols/jabber/presence.c @@ -204,7 +204,7 @@ int presence_send_update( struct im_connection *ic ) { struct jabber_data *jd = ic->proto_data; struct xt_node *node, *cap; - struct groupchat *c; + GSList *l; int st; node = jabber_make_packet( "presence", NULL, NULL, NULL ); @@ -228,8 +228,9 @@ int presence_send_update( struct im_connection *ic ) /* Have to send this update to all groupchats too, the server won't do this automatically. */ - for( c = ic->groupchats; c && st; c = c->next ) + 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 ); diff --git a/protocols/jabber/s5bytestream.c b/protocols/jabber/s5bytestream.c new file mode 100644 index 00000000..6759b78b --- /dev/null +++ b/protocols/jabber/s5bytestream.c @@ -0,0 +1,1153 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - SOCKS5 Bytestreams ( XEP-0065 ) * +* * +* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com> * +* * +* 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 <poll.h> + +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: + * + * <iq type=set> + * <query xmlns=bs sid=sid> + * <activate>tgt_jid</activate> + * </query> + * </iq> + */ +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 ); + strcpy( sh->port, 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, "<local>" ) == 0 ) { + if( ( tf->fd = ft_listen( &tf->saddr, host, port, FALSE, &errmsg ) ) != -1 ) { + sh = g_new0( jabber_streamhost_t, 1 ); + sh->jid = g_strdup( tf->ini_jid ); + sh->host = g_strdup( host ); + strcpy( sh->port, 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, "<auto>" ) == 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 diff --git a/protocols/jabber/si.c b/protocols/jabber/si.c new file mode 100644 index 00000000..58c0e17f --- /dev/null +++ b/protocols/jabber/si.c @@ -0,0 +1,529 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Jabber module - SI packets * +* * +* Copyright 2007 Uli Meis <a.sporto+bee@gmail.com> * +* * +* 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 <auto> is not set don't check for proxies */ + if( ( jd->have_streamhosts!=1 ) && ( jd->streamhosts==NULL ) && + ( strstr( set_getstr( &ic->acc->set, "proxy" ), "<auto>" ) != 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 ) + * <iq from=... to=... id=...> + * <si id=id xmlns=si profile=ft> + * <file xmlns=ft/> + * <feature xmlns=feature> + * <x xmlns=xdata type=submit> + * <field var=stream-method> + * + */ + 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; + } + + 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; + } + + *s = '/'; + } + else + { + 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, *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 ) + * <iq from=... to=... id=...> + * <si xmlns=si> + * [ <file xmlns=ft/> ] <-- not neccessary + * <feature xmlns=feature> + * <x xmlns=xdata type=submit> + * <field var=stream-method> + * <value> + */ + 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 ); +} diff --git a/protocols/msn/Makefile b/protocols/msn/Makefile index 6a588613..6c59aedb 100644 --- a/protocols/msn/Makefile +++ b/protocols/msn/Makefile @@ -7,6 +7,9 @@ ### DEFINITIONS -include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/msn/ +endif # [SH] Program variables objects = msn.o msn_util.o ns.o passport.o sb.o tables.o @@ -32,7 +35,7 @@ distclean: clean $(objects): ../../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/protocols/msn/invitation.c b/protocols/msn/invitation.c new file mode 100644 index 00000000..9f8b9a6e --- /dev/null +++ b/protocols/msn/invitation.c @@ -0,0 +1,622 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2008 Uli Meis * +* Copyright 2006 Marijn Kruisselbrink and others * +\********************************************************************/ + +/* MSN module - File transfer support */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA + */ + +#include "bitlbee.h" +#include "invitation.h" +#include "msn.h" +#include "lib/ftutil.h" + +#ifdef debug +#undef debug +#endif +#define debug(msg...) log_message( LOGLVL_INFO, msg ) + +static void msn_ftp_free( file_transfer_t *file ); +static void msn_ftpr_accept( file_transfer_t *file ); +static void msn_ftp_finished( file_transfer_t *file ); +static void msn_ftp_canceled( file_transfer_t *file, char *reason ); +static gboolean msn_ftpr_write_request( file_transfer_t *file ); + +static gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond ); +static gboolean msn_ftp_read( gpointer data, gint fd, b_input_condition cond ); +gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len ); + +/* + * Vararg wrapper for imcb_file_canceled(). + */ +gboolean msn_ftp_abort( file_transfer_t *file, char *format, ... ) +{ + va_list params; + va_start( params, format ); + char error[128]; + + if( vsnprintf( error, 128, format, params ) < 0 ) + sprintf( error, "internal error parsing error string (BUG)" ); + va_end( params ); + imcb_file_canceled( file, error ); + return FALSE; +} + +/* very useful */ +#define ASSERTSOCKOP(op, msg) \ + if( (op) == -1 ) \ + return msn_ftp_abort( file , msg ": %s", strerror( errno ) ); + +void msn_ftp_invitation_cmd( struct im_connection *ic, char *who, int cookie, char *icmd, + char *trailer ) +{ + struct msn_message *m = g_new0( struct msn_message, 1 ); + + m->text = g_strdup_printf( "%s" + "Invitation-Command: %s\r\n" + "Invitation-Cookie: %u\r\n" + "%s", + MSN_INVITE_HEADERS, + icmd, + cookie, + trailer); + + m->who = g_strdup( who ); + + msn_sb_write_msg( ic, m ); +} + +void msn_ftp_cancel_invite( struct im_connection *ic, char *who, int cookie, char *code ) +{ + char buf[64]; + + g_snprintf( buf, sizeof( buf ), "Cancel-Code: %s\r\n", code ); + msn_ftp_invitation_cmd( ic, who, cookie, "CANCEL", buf ); +} + +void msn_ftp_transfer_request( struct im_connection *ic, file_transfer_t *file, char *who ) +{ + unsigned int cookie = time( NULL ); /* TODO: randomize */ + char buf[2048]; + + msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 ); + file->data = msn_file; + file->free = msn_ftp_free; + file->canceled = msn_ftp_canceled; + file->write = msn_ftps_write; + msn_file->md = ic->proto_data; + msn_file->invite_cookie = cookie; + msn_file->handle = g_strdup( who ); + msn_file->dcc = file; + msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc ); + msn_file->fd = -1; + msn_file->sbufpos = 3; + + g_snprintf( buf, sizeof( buf ), + "Application-Name: File Transfer\r\n" + "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n" + "Application-File: %s\r\n" + "Application-FileSize: %zd\r\n", + file->file_name, + file->file_size); + + msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, cookie, "INVITE", buf ); + + imcb_file_recv_start( file ); +} + +void msn_invitation_invite( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + char *itype = msn_findheader( body, "Application-GUID:", blen ); + char *name, *size, *invitecookie, *reject = NULL; + user_t *u; + size_t isize; + file_transfer_t *file; + + if( !itype || strcmp( itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" ) != 0 ) { + /* Don't know what that is - don't care */ + char *iname = msn_findheader( body, "Application-Name:", blen ); + imcb_log( sb->ic, "Received unknown MSN invitation %s (%s) from %s", + itype ? : "with no GUID", iname ? iname : "no application name", handle ); + g_free( iname ); + reject = "REJECT_NOT_INSTALLED"; + } else if ( + !( name = msn_findheader( body, "Application-File:", blen )) || + !( size = msn_findheader( body, "Application-FileSize:", blen )) || + !( invitecookie = msn_findheader( body, "Invitation-Cookie:", blen)) || + !( isize = atoll( size ) ) ) { + imcb_log( sb->ic, "Received corrupted transfer request from %s" + "(name=%s, size=%s, invitecookie=%s)", + handle, name, size, invitecookie ); + reject = "REJECT"; + } else if ( !( u = user_findhandle( sb->ic, handle ) ) ) { + imcb_log( sb->ic, "Error in parsing transfer request, User '%s'" + "is not in contact list", handle ); + reject = "REJECT"; + } else if ( !( file = imcb_file_send_start( sb->ic, handle, name, isize ) ) ) { + imcb_log( sb->ic, "Error initiating transfer for request from %s for %s", + handle, name ); + reject = "REJECT"; + } else { + msn_filetransfer_t *msn_file = g_new0( msn_filetransfer_t, 1 ); + file->data = msn_file; + file->accept = msn_ftpr_accept; + file->free = msn_ftp_free; + file->finished = msn_ftp_finished; + file->canceled = msn_ftp_canceled; + file->write_request = msn_ftpr_write_request; + msn_file->md = sb->ic->proto_data; + msn_file->invite_cookie = cookie; + msn_file->handle = g_strdup( handle ); + msn_file->dcc = file; + msn_file->md->filetransfers = g_slist_prepend( msn_file->md->filetransfers, msn_file->dcc ); + msn_file->fd = -1; + } + + if( reject ) + msn_ftp_cancel_invite( sb->ic, sb->who, cookie, reject ); + + g_free( name ); + g_free( size ); + g_free( invitecookie ); + g_free( itype ); +} + +msn_filetransfer_t* msn_find_filetransfer( struct msn_data *md, unsigned int cookie, char *handle ) +{ + GSList *l; + + for( l = md->filetransfers; l; l = l->next ) { + msn_filetransfer_t *file = ( (file_transfer_t*) l->data )->data; + if( file->invite_cookie == cookie && strcmp( handle, file->handle ) == 0 ) { + return file; + } + } + return NULL; +} + +gboolean msn_ftps_connected( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + struct sockaddr_storage clt_addr; + socklen_t ssize = sizeof( clt_addr ); + + debug( "Connected to MSNFTP client" ); + + ASSERTSOCKOP( msn_file->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" ); + + closesocket( fd ); + fd = msn_file->fd; + sock_make_nonblocking( fd ); + + msn_file->r_event_id = b_input_add( fd, B_EV_IO_READ, msn_ftp_read, file ); + + return FALSE; +} + +void msn_invitations_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + file_transfer_t *file = msn_file->dcc; + char buf[1024]; + unsigned int acookie = time ( NULL ); + char host[HOST_NAME_MAX+1]; + char port[6]; + char *errmsg; + + msn_file->auth_cookie = acookie; + + if( ( msn_file->fd = ft_listen( NULL, host, port, FALSE, &errmsg ) ) == -1 ) { + msn_ftp_abort( file, "Failed to listen locally, check your ft_listen setting in bitlbee.conf: %s", errmsg ); + return; + } + + msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftps_connected, file ); + + g_snprintf( buf, sizeof( buf ), + "IP-Address: %s\r\n" + "Port: %s\r\n" + "AuthCookie: %d\r\n" + "Launch-Application: FALSE\r\n" + "Request-Data: IP-Address:\r\n\r\n", + host, + port, + msn_file->auth_cookie ); + + msn_ftp_invitation_cmd( msn_file->md->ic, handle, msn_file->invite_cookie, "ACCEPT", buf ); +} + +void msn_invitationr_accept( msn_filetransfer_t *msn_file, struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) { + file_transfer_t *file = msn_file->dcc; + char *authcookie, *ip, *port; + + if( !( authcookie = msn_findheader( body, "AuthCookie:", blen ) ) || + !( ip = msn_findheader( body, "IP-Address:", blen ) ) || + !( port = msn_findheader( body, "Port:", blen ) ) ) { + msn_ftp_abort( file, "Received invalid accept reply" ); + } else if( + ( msn_file->fd = proxy_connect( ip, atoi( port ), msn_ftp_connected, file ) ) + < 0 ) { + msn_ftp_abort( file, "Error connecting to MSN client" ); + } else + msn_file->auth_cookie = strtoul( authcookie, NULL, 10 ); + + g_free( authcookie ); + g_free( ip ); + g_free( port ); +} + +void msn_invitation_accept( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle ); + file_transfer_t *file = msn_file ? msn_file->dcc : NULL; + + if( !msn_file ) + imcb_log( sb->ic, "Received invitation ACCEPT message for unknown invitation (already aborted?)" ); + else if( file->sending ) + msn_invitations_accept( msn_file, sb, handle, cookie, body, blen ); + else + msn_invitationr_accept( msn_file, sb, handle, cookie, body, blen ); +} + +void msn_invitation_cancel( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ) +{ + msn_filetransfer_t *msn_file = msn_find_filetransfer( sb->ic->proto_data, cookie, handle ); + + if( !msn_file ) + imcb_log( sb->ic, "Received invitation CANCEL message for unknown invitation (already aborted?)" ); + else + msn_ftp_abort( msn_file->dcc, msn_findheader( body, "Cancel-Code:", blen ) ); +} + +int msn_ftp_write( file_transfer_t *file, char *format, ... ) +{ + msn_filetransfer_t *msn_file = file->data; + va_list params; + int st; + char *s; + + va_start( params, format ); + s = g_strdup_vprintf( format, params ); + va_end( params ); + + st = write( msn_file->fd, s, strlen( s ) ); + if( st != strlen( s ) ) + return msn_ftp_abort( file, "Error sending data over MSNFTP connection: %s", + strerror( errno ) ); + + g_free( s ); + return 1; +} + +gboolean msn_ftp_connected( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + debug( "Connected to MSNFTP server, starting authentication" ); + if( !msn_ftp_write( file, "VER MSNFTP\r\n" ) ) + return FALSE; + + sock_make_nonblocking( msn_file->fd ); + msn_file->r_event_id = b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file ); + + return FALSE; +} + +gboolean msn_ftp_handle_command( file_transfer_t *file, char* line ) +{ + msn_filetransfer_t *msn_file = file->data; + char **cmd = msn_linesplit( line ); + int count = 0; + if( cmd[0] ) while( cmd[++count] ); + + if( count < 1 ) + return msn_ftp_abort( file, "Missing command in MSNFTP communication" ); + + if( strcmp( cmd[0], "VER" ) == 0 ) { + if( strcmp( cmd[1], "MSNFTP" ) != 0 ) + return msn_ftp_abort( file, "Unsupported filetransfer protocol: %s", cmd[1] ); + if( file->sending ) + msn_ftp_write( file, "VER MSNFTP\r\n" ); + else + msn_ftp_write( file, "USR %s %u\r\n", msn_file->md->ic->acc->user, msn_file->auth_cookie ); + } else if( strcmp( cmd[0], "FIL" ) == 0 ) { + if( strtoul( cmd[1], NULL, 10 ) != file->file_size ) + return msn_ftp_abort( file, "FIL reply contains a different file size than the size in the invitation" ); + msn_ftp_write( file, "TFR\r\n" ); + msn_file->status |= MSN_TRANSFER_RECEIVING; + } else if( strcmp( cmd[0], "USR" ) == 0 ) { + if( ( strcmp( cmd[1], msn_file->handle ) != 0 ) || + ( strtoul( cmd[2], NULL, 10 ) != msn_file->auth_cookie ) ) + msn_ftp_abort( file, "Authentication failed. " + "Expected handle: %s (got %s), cookie: %u (got %s)", + msn_file->handle, cmd[1], + msn_file->auth_cookie, cmd[2] ); + msn_ftp_write( file, "FIL %zu\r\n", file->file_size); + } else if( strcmp( cmd[0], "TFR" ) == 0 ) { + file->write_request( file ); + } else if( strcmp( cmd[0], "BYE" ) == 0 ) { + unsigned int retcode = count > 1 ? atoi(cmd[1]) : 1; + + if( ( retcode==16777989 ) || ( retcode==16777987 ) ) + imcb_file_finished( file ); + else if( retcode==2147942405 ) + imcb_file_canceled( file, "Failure: receiver is out of disk space" ); + else if( retcode==2164261682 ) + imcb_file_canceled( file, "Failure: receiver cancelled the transfer" ); + else if( retcode==2164261683 ) + imcb_file_canceled( file, "Failure: sender has cancelled the transfer" ); + else if( retcode==2164261694 ) + imcb_file_canceled( file, "Failure: connection is blocked" ); + else { + char buf[128]; + + sprintf( buf, "Failure: unknown BYE code: %d", retcode); + imcb_file_canceled( file, buf ); + } + } else if( strcmp( cmd[0], "CCL" ) == 0 ) { + imcb_file_canceled( file, "Failure: receiver cancelled the transfer" ); + } else { + msn_ftp_abort( file, "Received invalid command %s from msn client", cmd[0] ); + } + return TRUE; +} + +gboolean msn_ftp_send( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + msn_file->w_event_id = 0; + + file->write_request( file ); + + return FALSE; +} + +/* + * This should only be called if we can write, so just do it. + * Add a write watch so we can write more during the next cycle (if possible). + * This got a bit complicated because (at least) amsn expects packets of size 2045. + */ +gboolean msn_ftps_write( file_transfer_t *file, char *buffer, unsigned int len ) +{ + msn_filetransfer_t *msn_file = file->data; + int ret, overflow; + + /* what we can't send now */ + overflow = msn_file->sbufpos + len - MSNFTP_PSIZE; + + /* append what we can do the send buffer */ + memcpy( msn_file->sbuf + msn_file->sbufpos, buffer, MIN( len, MSNFTP_PSIZE - msn_file->sbufpos ) ); + msn_file->sbufpos += MIN( len, MSNFTP_PSIZE - msn_file->sbufpos ); + + /* if we don't have enough for a full packet and there's more wait for it */ + if( ( msn_file->sbufpos < MSNFTP_PSIZE ) && + ( msn_file->data_sent + msn_file->sbufpos - 3 < file->file_size ) ) { + if( !msn_file->w_event_id ) + msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file ); + return TRUE; + } + + /* Accumulated enough data, lets send something out */ + + msn_file->sbuf[0] = 0; + msn_file->sbuf[1] = ( msn_file->sbufpos - 3 ) & 0xff; + msn_file->sbuf[2] = ( ( msn_file->sbufpos - 3 ) >> 8 ) & 0xff; + + ASSERTSOCKOP( ret = send( msn_file->fd, msn_file->sbuf, msn_file->sbufpos, 0 ), "Sending" ); + + msn_file->data_sent += ret - 3; + + /* TODO: this should really not be fatal */ + if( ret < msn_file->sbufpos ) + return msn_ftp_abort( file, "send() sent %d instead of %d (send buffer full!)", ret, msn_file->sbufpos ); + + msn_file->sbufpos = 3; + + if( overflow > 0 ) { + while( overflow > ( MSNFTP_PSIZE - 3 ) ) { + if( !msn_ftps_write( file, buffer + len - overflow, MSNFTP_PSIZE - 3 ) ) + return FALSE; + overflow -= MSNFTP_PSIZE - 3; + } + return msn_ftps_write( file, buffer + len - overflow, overflow ); + } + + if( msn_file->data_sent == file->file_size ) { + if( msn_file->w_event_id ) { + b_event_remove( msn_file->w_event_id ); + msn_file->w_event_id = 0; + } + } else { + /* we might already be listening if this is data from an overflow */ + if( !msn_file->w_event_id ) + msn_file->w_event_id = b_input_add( msn_file->fd, B_EV_IO_WRITE, msn_ftp_send, file ); + } + + return TRUE; +} + +/* Binary part of the file transfer protocol */ +gboolean msn_ftpr_read( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + int st; + unsigned char buf[3]; + + if( msn_file->data_remaining ) { + msn_file->r_event_id = 0; + + ASSERTSOCKOP( st = read( msn_file->fd, file->buffer, MIN( sizeof( file->buffer ), msn_file->data_remaining ) ), "Receiving" ); + + if( st == 0 ) + return msn_ftp_abort( file, "Remote end closed connection"); + + msn_file->data_sent += st; + + msn_file->data_remaining -= st; + + file->write( file, file->buffer, st ); + + if( msn_file->data_sent >= file->file_size ) + imcb_file_finished( file ); + + return FALSE; + } else { + ASSERTSOCKOP( st = read( msn_file->fd, buf, 1 ), "Receiving" ); + if( st == 0 ) { + return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" ); + } else if( buf[0] == '\r' || buf[0] == '\n' ) { + debug( "Discarding extraneous newline" ); + } else if( buf[0] != 0 ) { + msn_ftp_abort( file, "Remote end canceled the transfer"); + /* don't really care about these last 2 (should be 0,0) */ + read( msn_file->fd, buf, 2 ); + return FALSE; + } else { + unsigned int size; + ASSERTSOCKOP( st = read( msn_file->fd, buf, 2 ), "Receiving" ); + if( st < 2 ) + return msn_ftp_abort( file, "read returned EOF while reading data header from msn client" ); + + size = buf[0] + ((unsigned int) buf[1] << 8); + msn_file->data_remaining = size; + } + } + return TRUE; +} + +/* Text mode part of the file transfer protocol */ +gboolean msn_ftp_txtproto( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + int i = msn_file->tbufpos, st; + char *tbuf = msn_file->tbuf; + + ASSERTSOCKOP( st = read( msn_file->fd, + tbuf + msn_file->tbufpos, + sizeof( msn_file->tbuf ) - msn_file->tbufpos ), + "Receiving" ); + + if( st == 0 ) + return msn_ftp_abort( file, "read returned EOF while reading text from msn client" ); + + msn_file->tbufpos += st; + + do { + for( ;i < msn_file->tbufpos; i++ ) { + if( tbuf[i] == '\n' || tbuf[i] == '\r' ) { + tbuf[i] = '\0'; + if( i > 0 ) + msn_ftp_handle_command( file, tbuf ); + else + while( tbuf[i] == '\n' || tbuf[i] == '\r' ) i++; + memmove( tbuf, tbuf + i + 1, msn_file->tbufpos - i - 1 ); + msn_file->tbufpos -= i + 1; + i = 0; + break; + } + } + } while ( i < msn_file->tbufpos ); + + if( msn_file->tbufpos == sizeof( msn_file->tbuf ) ) + return msn_ftp_abort( file, + "Line exceeded %d bytes in text protocol", + sizeof( msn_file->tbuf ) ); + return TRUE; +} + +gboolean msn_ftp_read( gpointer data, gint fd, b_input_condition cond ) +{ + file_transfer_t *file = data; + msn_filetransfer_t *msn_file = file->data; + + if( msn_file->status & MSN_TRANSFER_RECEIVING ) + return msn_ftpr_read( file ); + else + return msn_ftp_txtproto( file ); +} + +void msn_ftp_free( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + + if( msn_file->r_event_id ) + b_event_remove( msn_file->r_event_id ); + + if( msn_file->w_event_id ) + b_event_remove( msn_file->w_event_id ); + + if( msn_file->fd != -1 ) + closesocket( msn_file->fd ); + + msn_file->md->filetransfers = g_slist_remove( msn_file->md->filetransfers, msn_file->dcc ); + + g_free( msn_file->handle ); + + g_free( msn_file ); +} + +void msn_ftpr_accept( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + + msn_ftp_invitation_cmd( msn_file->md->ic, msn_file->handle, msn_file->invite_cookie, "ACCEPT", + "Launch-Application: FALSE\r\n" + "Request-Data: IP-Address:\r\n"); +} + +void msn_ftp_finished( file_transfer_t *file ) +{ + msn_ftp_write( file, "BYE 16777989\r\n" ); +} + +void msn_ftp_canceled( file_transfer_t *file, char *reason ) +{ + msn_filetransfer_t *msn_file = file->data; + + msn_ftp_cancel_invite( msn_file->md->ic, msn_file->handle, + msn_file->invite_cookie, + file->status & FT_STATUS_TRANSFERRING ? + "FTTIMEOUT" : + "FAIL" ); + + imcb_log( msn_file->md->ic, "File transfer aborted: %s", reason ); +} + +gboolean msn_ftpr_write_request( file_transfer_t *file ) +{ + msn_filetransfer_t *msn_file = file->data; + if( msn_file->r_event_id != 0 ) { + msn_ftp_abort( file, + "BUG in MSN file transfer:" + "write_request called when" + "already watching for input" ); + return FALSE; + } + + msn_file->r_event_id = + b_input_add( msn_file->fd, B_EV_IO_READ, msn_ftp_read, file ); + + return TRUE; +} diff --git a/protocols/msn/invitation.h b/protocols/msn/invitation.h new file mode 100644 index 00000000..289efd7b --- /dev/null +++ b/protocols/msn/invitation.h @@ -0,0 +1,82 @@ +/********************************************************************\ +* BitlBee -- An IRC to other IM-networks gateway * +* * +* Copyright 2006 Marijn Kruisselbrink and others * +\********************************************************************/ + +/* MSN module - File transfer support */ + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License with + the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; + if not, write to the Free Software Foundation, Inc., 59 Temple Place, + Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _MSN_INVITATION_H +#define _MSN_INVITATION_H + +#include "msn.h" + +#define MSN_INVITE_HEADERS "MIME-Version: 1.0\r\n" \ + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" \ + "\r\n" + +#define MSNFTP_PSIZE 2048 + +typedef enum { + MSN_TRANSFER_RECEIVING = 1, + MSN_TRANSFER_SENDING = 2 +} msn_filetransfer_status_t; + +typedef struct msn_filetransfer +{ +/* Generic invitation data */ + /* msn_data instance this invitation was received with. */ + struct msn_data *md; + /* Cookie specifying this invitation. */ + unsigned int invite_cookie; + /* Handle of user that started this invitation. */ + char *handle; + +/* File transfer specific data */ + /* Current status of the file transfer. */ + msn_filetransfer_status_t status; + /* Pointer to the dcc structure for this transfer. */ + file_transfer_t *dcc; + /* Socket the transfer is taking place over. */ + int fd; + /* Cookie received in the original invitation, this must be sent as soon as + a connection has been established. */ + unsigned int auth_cookie; + /* Data remaining to be received in the current packet. */ + unsigned int data_remaining; + /* Buffer containing received, but unprocessed text. */ + char tbuf[256]; + unsigned int tbufpos; + + unsigned int data_sent; + + gint r_event_id; + gint w_event_id; + + unsigned char sbuf[2048]; + int sbufpos; + +} msn_filetransfer_t; + +void msn_invitation_invite( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); +void msn_invitation_accept( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); +void msn_invitation_cancel( struct msn_switchboard *sb, char *handle, unsigned int cookie, char *body, int blen ); + +#endif diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c index 7dbdb9d6..8b4f1a81 100644 --- a/protocols/msn/msn.c +++ b/protocols/msn/msn.c @@ -78,6 +78,12 @@ static void msn_logout( struct im_connection *ic ) if( md ) { + /** Disabling MSN ft support for now. + while( md->filetransfers ) { + imcb_file_canceled( md->filetransfers->data, "Closing connection" ); + } + */ + if( md->fd >= 0 ) closesocket( md->fd ); @@ -97,6 +103,15 @@ static void msn_logout( struct im_connection *ic ) g_free( md->grouplist[--md->groupcount] ); g_free( md->grouplist ); + while( md->grpq ) + { + struct msn_groupadd *ga = md->grpq->data; + g_free( ga->group ); + g_free( ga->who ); + g_free( ga ); + md->grpq = g_slist_remove( md->grpq, ga ); + } + g_free( md ); } @@ -115,6 +130,14 @@ static int msn_buddy_msg( struct im_connection *ic, char *who, char *message, in { struct msn_switchboard *sb; +#ifdef DEBUG + if( strcmp( who, "raw" ) == 0 ) + { + msn_write( ic, message, strlen( message ) ); + msn_write( ic, "\r\n", 2 ); + } + else +#endif if( ( sb = msn_sb_by_handle( ic, who ) ) ) { return( msn_sb_sendmessage( sb, message ) ); @@ -175,7 +198,7 @@ static void msn_get_info(struct im_connection *ic, char *who) static void msn_add_buddy( struct im_connection *ic, char *who, char *group ) { - msn_buddy_list_add( ic, "FL", who, who ); + msn_buddy_list_add( ic, "FL", who, who, group ); } static void msn_remove_buddy( struct im_connection *ic, char *who, char *group ) @@ -216,6 +239,7 @@ static void msn_chat_leave( struct groupchat *c ) static struct groupchat *msn_chat_with( struct im_connection *ic, char *who ) { struct msn_switchboard *sb; + struct groupchat *c = imcb_chat_new( ic, who ); if( ( sb = msn_sb_by_handle( ic, who ) ) ) { @@ -233,10 +257,8 @@ static struct groupchat *msn_chat_with( struct im_connection *ic, char *who ) msn_sb_write_msg( ic, m ); - return NULL; + return c; } - - return NULL; } static void msn_keepalive( struct im_connection *ic ) @@ -246,7 +268,7 @@ static void msn_keepalive( struct im_connection *ic ) static void msn_add_permit( struct im_connection *ic, char *who ) { - msn_buddy_list_add( ic, "AL", who, who ); + msn_buddy_list_add( ic, "AL", who, who, NULL ); } static void msn_rem_permit( struct im_connection *ic, char *who ) @@ -258,7 +280,7 @@ static void msn_add_deny( struct im_connection *ic, char *who ) { struct msn_switchboard *sb; - msn_buddy_list_add( ic, "BL", who, who ); + msn_buddy_list_add( ic, "BL", who, who, NULL ); /* If there's still a conversation with this person, close it. */ if( ( sb = msn_sb_by_handle( ic, who ) ) ) @@ -327,6 +349,7 @@ void msn_initmodule() ret->rem_deny = msn_rem_deny; ret->send_typing = msn_send_typing; ret->handle_cmp = g_strcasecmp; + //ret->transfer_request = msn_ftp_transfer_request; register_protocol(ret); } diff --git a/protocols/msn/msn.h b/protocols/msn/msn.h index 83a080a3..59036e37 100644 --- a/protocols/msn/msn.h +++ b/protocols/msn/msn.h @@ -69,10 +69,11 @@ struct msn_data int trId; - GSList *msgq; + GSList *msgq, *grpq; GSList *switchboards; int sb_failures; time_t first_sb_failure; + GSList *filetransfers; const struct msn_away_state *away_state; int buddycount; @@ -119,6 +120,12 @@ struct msn_message char *text; }; +struct msn_groupadd +{ + char *who; + char *group; +}; + struct msn_handler_data { int fd; @@ -158,7 +165,7 @@ gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond ); /* msn_util.c */ int msn_write( struct im_connection *ic, char *s, int len ); int msn_logged_in( struct im_connection *ic ); -int msn_buddy_list_add( struct im_connection *ic, char *list, char *who, char *realname ); +int msn_buddy_list_add( struct im_connection *ic, const char *list, const char *who, const char *realname_, const char *group ); int msn_buddy_list_remove( struct im_connection *ic, char *list, char *who ); void msn_buddy_ask( struct im_connection *ic, char *handle, char *realname ); char *msn_findheader( char *text, char *header, int len ); @@ -188,4 +195,7 @@ int msn_sb_write_msg( struct im_connection *ic, struct msn_message *m ); void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial ); void msn_sb_stop_keepalives( struct msn_switchboard *sb ); +/* invitation.c */ +void msn_ftp_transfer_request( struct im_connection *ic, file_transfer_t *ft, char *who ); + #endif //_MSN_H diff --git a/protocols/msn/msn_util.c b/protocols/msn/msn_util.c index 9c9d2720..24f7e10e 100644 --- a/protocols/msn/msn_util.c +++ b/protocols/msn/msn_util.c @@ -50,24 +50,62 @@ int msn_logged_in( struct im_connection *ic ) return( 0 ); } -int msn_buddy_list_add( struct im_connection *ic, char *list, char *who, char *realname_ ) +int msn_buddy_list_add( struct im_connection *ic, const char *list, const char *who, const char *realname_, const char *group ) { struct msn_data *md = ic->proto_data; - char buf[1024], *realname; + char buf[1024], *realname, groupid[8]; - realname = msn_http_encode( realname_ ); - - g_snprintf( buf, sizeof( buf ), "ADD %d %s %s %s\r\n", ++md->trId, list, who, realname ); - if( msn_write( ic, buf, strlen( buf ) ) ) + *groupid = '\0'; + if( group ) { - g_free( realname ); + int i; + for( i = 0; i < md->groupcount; i ++ ) + if( g_strcasecmp( md->grouplist[i], group ) == 0 ) + { + g_snprintf( groupid, sizeof( groupid ), " %d", i ); + break; + } - return( 1 ); + if( *groupid == '\0' ) + { + /* Have to create this group, it doesn't exist yet. */ + struct msn_groupadd *ga; + GSList *l; + + for( l = md->grpq; l; l = l->next ) + { + ga = l->data; + if( g_strcasecmp( ga->group, group ) == 0 ) + break; + } + + ga = g_new0( struct msn_groupadd, 1 ); + ga->who = g_strdup( who ); + ga->group = g_strdup( group ); + md->grpq = g_slist_prepend( md->grpq, ga ); + + if( l == NULL ) + { + char *groupname = msn_http_encode( group ); + g_snprintf( buf, sizeof( buf ), "ADG %d %s %d\r\n", ++md->trId, groupname, 0 ); + g_free( groupname ); + return msn_write( ic, buf, strlen( buf ) ); + } + else + { + /* This can happen if the user's doing lots of adds to a + new group at once; we're still waiting for the server + to confirm group creation. */ + return 1; + } + } } + realname = msn_http_encode( realname_ ); + g_snprintf( buf, sizeof( buf ), "ADD %d %s %s %s%s\r\n", ++md->trId, list, who, realname, groupid ); g_free( realname ); - return( 0 ); + return msn_write( ic, buf, strlen( buf ) ); } int msn_buddy_list_remove( struct im_connection *ic, char *list, char *who ) @@ -93,10 +131,9 @@ static void msn_buddy_ask_yes( void *data ) { struct msn_buddy_ask_data *bla = data; - msn_buddy_list_add( bla->ic, "AL", bla->handle, bla->realname ); + msn_buddy_list_add( bla->ic, "AL", bla->handle, bla->realname, NULL ); - if( imcb_find_buddy( bla->ic, bla->handle ) == NULL ) - imcb_ask_add( bla->ic, bla->handle, NULL ); + imcb_ask_add( bla->ic, bla->handle, NULL ); g_free( bla->handle ); g_free( bla->realname ); @@ -107,7 +144,7 @@ static void msn_buddy_ask_no( void *data ) { struct msn_buddy_ask_data *bla = data; - msn_buddy_list_add( bla->ic, "BL", bla->handle, bla->realname ); + msn_buddy_list_add( bla->ic, "BL", bla->handle, bla->realname, NULL ); g_free( bla->handle ); g_free( bla->realname ); diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c index 2f656ea5..0be9e727 100644 --- a/protocols/msn/ns.c +++ b/protocols/msn/ns.c @@ -75,7 +75,7 @@ gboolean msn_ns_connected( gpointer data, gint source, b_input_condition cond ) g_snprintf( s, sizeof( s ), "VER %d MSNP8 CVR0\r\n", ++md->trId ); if( msn_write( ic, s, strlen( s ) ) ) { - ic->inpa = b_input_add( md->fd, GAIM_INPUT_READ, msn_ns_callback, ic ); + ic->inpa = b_input_add( md->fd, B_EV_IO_READ, msn_ns_callback, ic ); imcb_log( ic, "Connected to server, waiting for reply" ); } @@ -532,8 +532,14 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) } else if( num_parts >= 6 && strcmp( cmd[2], "FL" ) == 0 ) { + const char *group = NULL; + int num; + + if( cmd[6] != NULL && sscanf( cmd[6], "%d", &num ) == 1 && num < md->groupcount ) + group = md->grouplist[num]; + http_decode( cmd[5] ); - imcb_add_buddy( ic, cmd[4], NULL ); + imcb_add_buddy( ic, cmd[4], group ); imcb_rename_buddy( ic, cmd[4], cmd[5] ); } } @@ -605,6 +611,50 @@ static int msn_ns_command( gpointer data, char **cmd, int num_parts ) return( 0 ); } } + else if( strcmp( cmd[0], "ADG" ) == 0 ) + { + char *group = g_strdup( cmd[3] ); + int groupnum, i; + GSList *l, *next; + + http_decode( group ); + if( sscanf( cmd[4], "%d", &groupnum ) == 1 ) + { + if( groupnum >= md->groupcount ) + { + md->grouplist = g_renew( char *, md->grouplist, groupnum + 1 ); + for( i = md->groupcount; i <= groupnum; i ++ ) + md->grouplist[i] = NULL; + md->groupcount = groupnum + 1; + } + g_free( md->grouplist[groupnum] ); + md->grouplist[groupnum] = group; + } + else + { + /* Shouldn't happen, but if it does, give up on the group. */ + g_free( group ); + imcb_error( ic, "Syntax error" ); + imc_logout( ic, TRUE ); + return 0; + } + + for( l = md->grpq; l; l = next ) + { + struct msn_groupadd *ga = l->data; + next = l->next; + if( g_strcasecmp( ga->group, group ) == 0 ) + { + if( !msn_buddy_list_add( ic, "FL", ga->who, ga->who, group ) ) + return 0; + + g_free( ga->group ); + g_free( ga->who ); + g_free( ga ); + md->grpq = g_slist_remove( md->grpq, ga ); + } + } + } else if( isdigit( cmd[0][0] ) ) { int num = atoi( cmd[0] ); diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c index 49eed601..cb5789b8 100644 --- a/protocols/msn/sb.c +++ b/protocols/msn/sb.c @@ -28,6 +28,7 @@ #include "msn.h" #include "passport.h" #include "md5.h" +#include "invitation.h" static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond ); static int msn_sb_command( gpointer data, char **cmd, int num_parts ); @@ -178,6 +179,11 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) buf = g_strdup( SB_KEEPALIVE_HEADERS ); i = strlen( buf ); } + else if( strncmp( text, MSN_INVITE_HEADERS, sizeof( MSN_INVITE_HEADERS ) - 1 ) == 0 ) + { + buf = g_strdup( text ); + i = strlen( buf ); + } else { buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 + 1 ); @@ -226,11 +232,17 @@ int msn_sb_sendmessage( struct msn_switchboard *sb, char *text ) struct groupchat *msn_sb_to_chat( struct msn_switchboard *sb ) { struct im_connection *ic = sb->ic; + struct groupchat *c = NULL; char buf[1024]; /* Create the groupchat structure. */ g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session ); - sb->chat = imcb_chat_new( ic, buf ); + if( sb->who ) + c = bee_chat_by_title( ic->bee, ic, sb->who ); + if( c && !msn_sb_by_chat( c ) ) + sb->chat = c; + else + sb->chat = imcb_chat_new( ic, buf ); /* Populate the channel. */ if( sb->who ) imcb_chat_add_buddy( sb->chat, sb->who ); @@ -314,7 +326,7 @@ gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond ) g_snprintf( buf, sizeof( buf ), "ANS %d %s %s %d\r\n", ++sb->trId, ic->acc->user, sb->key, sb->session ); if( msn_sb_write( sb, buf, strlen( buf ) ) ) - sb->inp = b_input_add( sb->fd, GAIM_INPUT_READ, msn_sb_callback, sb ); + sb->inp = b_input_add( sb->fd, B_EV_IO_READ, msn_sb_callback, sb ); else debug( "Error %d while connecting to switchboard server", 2 ); @@ -691,64 +703,46 @@ static int msn_sb_message( gpointer data, char *msg, int msglen, char **cmd, int /* PANIC! */ } } +#if 0 + // Disable MSN ft support for now. else if( g_strncasecmp( ct, "text/x-msmsgsinvite", 19 ) == 0 ) { - char *itype = msn_findheader( body, "Application-GUID:", blen ); - char buf[1024]; + char *command = msn_findheader( body, "Invitation-Command:", blen ); + char *cookie = msn_findheader( body, "Invitation-Cookie:", blen ); + unsigned int icookie; g_free( ct ); - *buf = 0; - - if( !itype ) - return( 1 ); - - /* File transfer. */ - if( strcmp( itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" ) == 0 ) - { - char *name = msn_findheader( body, "Application-File:", blen ); - char *size = msn_findheader( body, "Application-FileSize:", blen ); - - if( name && size ) - { - g_snprintf( buf, sizeof( buf ), "<< \x02""BitlBee\x02"" - Filetransfer: `%s', %s bytes >>\n" - "Filetransfers are not supported by BitlBee for now...", name, size ); - } - else - { - strcpy( buf, "<< \x02""BitlBee\x02"" - Corrupted MSN filetransfer invitation message >>" ); - } - - if( name ) g_free( name ); - if( size ) g_free( size ); - } - else - { - char *iname = msn_findheader( body, "Application-Name:", blen ); - - g_snprintf( buf, sizeof( buf ), "<< \x02""BitlBee\x02"" - Unknown MSN invitation - %s (%s) >>", - itype, iname ? iname : "no name" ); - - if( iname ) g_free( iname ); + /* Every invite should have both a Command and Cookie header */ + if( !command || !cookie ) { + g_free( command ); + g_free( cookie ); + imcb_log( ic, "Warning: No command or cookie from %s", sb->who ); + return 1; } - g_free( itype ); - - if( !*buf ) - return( 1 ); + icookie = strtoul( cookie, NULL, 10 ); + g_free( cookie ); - if( sb->who ) - { - imcb_buddy_msg( ic, cmd[1], buf, 0, 0 ); - } - else if( sb->chat ) - { - imcb_chat_msg( sb->chat, cmd[1], buf, 0, 0 ); - } - else - { - /* PANIC! */ + if( g_strncasecmp( command, "INVITE", 6 ) == 0 ) { + msn_invitation_invite( sb, cmd[1], icookie, body, blen ); + } else if( g_strncasecmp( command, "ACCEPT", 6 ) == 0 ) { + msn_invitation_accept( sb, cmd[1], icookie, body, blen ); + } else if( g_strncasecmp( command, "CANCEL", 6 ) == 0 ) { + msn_invitation_cancel( sb, cmd[1], icookie, body, blen ); + } else { + imcb_log( ic, "Warning: Received invalid invitation with " + "command %s from %s", command, sb->who ); } + + g_free( command ); + } +#endif + else if( g_strncasecmp( ct, "application/x-msnmsgrp2p", 24 ) == 0 ) + { + imcb_error( sb->ic, "Cannot receive file from %s: BitlBee does not " + "support msnmsgrp2p yet.", sb->who ); + g_free( ct ); } else if( g_strncasecmp( ct, "text/x-msmsgscontrol", 20 ) == 0 ) { @@ -779,10 +773,11 @@ static gboolean msn_sb_keepalive( gpointer data, gint source, b_input_condition void msn_sb_start_keepalives( struct msn_switchboard *sb, gboolean initial ) { - struct buddy *b; + bee_user_t *bu; if( sb && sb->who && sb->keepalive == 0 && - ( b = imcb_find_buddy( sb->ic, sb->who ) ) && !b->present && + ( bu = bee_user_by_handle( sb->ic->bee, sb->ic, sb->who ) ) && + !( bu->flags & BEE_USER_ONLINE ) && set_getbool( &sb->ic->acc->set, "switchboard_keepalives" ) ) { if( initial ) diff --git a/protocols/nogaim.c b/protocols/nogaim.c index 2248d11e..e628126f 100644 --- a/protocols/nogaim.c +++ b/protocols/nogaim.c @@ -35,10 +35,6 @@ #include <ctype.h> #include "nogaim.h" -#include "chat.h" - -static int remove_chat_buddy_silent( struct groupchat *b, const char *handle ); -static char *format_timestamp( irc_t *irc, time_t msg_ts ); GSList *connections; @@ -92,8 +88,6 @@ void load_plugins(void) } #endif -/* nogaim.c */ - GList *protocols = NULL; void register_protocol (struct prpl *p) @@ -116,16 +110,18 @@ void register_protocol (struct prpl *p) struct prpl *find_protocol(const char *name) { GList *gl; - for (gl = protocols; gl; gl = gl->next) + + for( gl = protocols; gl; gl = gl->next ) { struct prpl *proto = gl->data; - if(!g_strcasecmp(proto->name, name)) + + if( g_strcasecmp( proto->name, name ) == 0 ) return proto; } + return NULL; } -/* nogaim.c */ void nogaim_init() { extern void msn_initmodule(); @@ -133,6 +129,7 @@ void nogaim_init() extern void byahoo_initmodule(); extern void jabber_initmodule(); extern void twitter_initmodule(); + extern void purple_initmodule(); #ifdef WITH_MSN msn_initmodule(); @@ -154,6 +151,10 @@ void nogaim_init() twitter_initmodule(); #endif +#ifdef WITH_PURPLE + purple_initmodule(); +#endif + #ifdef WITH_PLUGINS load_plugins(); #endif @@ -161,15 +162,13 @@ void nogaim_init() GSList *get_connections() { return connections; } -/* multi.c */ - struct im_connection *imcb_new( account_t *acc ) { struct im_connection *ic; ic = g_new0( struct im_connection, 1 ); - ic->irc = acc->irc; + ic->bee = acc->bee; ic->acc = acc; acc->ic = ic; @@ -183,7 +182,7 @@ void imc_free( struct im_connection *ic ) account_t *a; /* Destroy the pointer to this connection from the account list */ - for( a = ic->irc->accounts; a; a = a->next ) + for( a = ic->bee->accounts; a; a = a->next ) if( a->ic == ic ) { a->ic = NULL; @@ -204,20 +203,21 @@ static void serv_got_crap( struct im_connection *ic, char *format, ... ) text = g_strdup_vprintf( format, params ); va_end( params ); - if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) || - ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) ) + 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->irc->accounts; a; a = a->next ) + 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 ) - irc_usermsg( ic->irc, "%s(%s) - %s", ic->acc->prpl->name, ic->acc->user, text ); + /* FIXME(wilmer): ui_log callback or so */ + irc_usermsg( ic->bee->ui_data, "%s(%s) - %s", ic->acc->prpl->name, ic->acc->user, text ); else - irc_usermsg( ic->irc, "%s - %s", ic->acc->prpl->name, text ); + irc_usermsg( ic->bee->ui_data, "%s - %s", ic->acc->prpl->name, text ); g_free( text ); } @@ -268,18 +268,12 @@ static gboolean send_keepalive( gpointer d, gint fd, b_input_condition cond ) void imcb_connected( struct im_connection *ic ) { - irc_t *irc = ic->irc; - struct chat *c; - user_t *u; - /* 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; - u = user_find( ic->irc, ic->irc->nick ); - imcb_log( ic, "Logged in" ); ic->keepalive = b_timeout_add( 60000, send_keepalive, ic ); @@ -292,6 +286,10 @@ void imcb_connected( struct im_connection *ic ) exponential backoff timer. */ ic->acc->auto_reconnect_delay = 0; + if( ic->bee->ui->imc_connected ) + ic->bee->ui->imc_connected( ic ); + + /* for( c = irc->chatrooms; c; c = c->next ) { if( c->acc != ic->acc ) @@ -300,6 +298,7 @@ void imcb_connected( struct im_connection *ic ) if( set_getbool( &c->set, "auto_join" ) ) chat_join( irc, c, NULL ); } + */ } gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond ) @@ -307,7 +306,7 @@ gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond ) account_t *a = data; a->reconnect = 0; - account_on( a->irc, a ); + account_on( a->bee, a ); return( FALSE ); /* Only have to run the timeout once */ } @@ -320,9 +319,9 @@ void cancel_auto_reconnect( account_t *a ) void imc_logout( struct im_connection *ic, int allow_reconnect ) { - irc_t *irc = ic->irc; - user_t *t, *u; + bee_t *bee = ic->bee; account_t *a; + GSList *l; int delay; /* Nested calls might happen sometimes, this is probably the best @@ -332,6 +331,9 @@ void imc_logout( struct im_connection *ic, int allow_reconnect ) else ic->flags |= OPT_LOGGING_OUT; + if( ic->bee->ui->imc_disconnected ) + ic->bee->ui->imc_disconnected( ic ); + imcb_log( ic, "Signing off.." ); b_event_remove( ic->keepalive ); @@ -342,22 +344,20 @@ void imc_logout( struct im_connection *ic, int allow_reconnect ) g_free( ic->away ); ic->away = NULL; - u = irc->users; - while( u ) + for( l = bee->users; l; ) { - if( u->ic == ic ) - { - t = u->next; - user_del( irc, u->nick ); - u = t; - } - else - u = u->next; + bee_user_t *bu = l->data; + GSList *next = l->next; + + if( bu->ic == ic ) + bee_user_free( bee, bu ); + + l = next; } - query_del_by_conn( ic->irc, ic ); + query_del_by_conn( (irc_t*) ic->bee->ui_data, ic ); - for( a = irc->accounts; a; a = a->next ) + for( a = bee->accounts; a; a = a->next ) if( a->ic == ic ) break; @@ -365,7 +365,7 @@ void imc_logout( struct im_connection *ic, int allow_reconnect ) { /* Uhm... This is very sick. */ } - else if( allow_reconnect && set_getbool( &irc->set, "auto_reconnect" ) && + else if( allow_reconnect && set_getbool( &bee->set, "auto_reconnect" ) && set_getbool( &a->set, "auto_reconnect" ) && ( delay = account_reconnect_delay( a ) ) > 0 ) { @@ -376,170 +376,68 @@ void imc_logout( struct im_connection *ic, int allow_reconnect ) imc_free( ic ); } - -/* dialogs.c */ - void imcb_ask( struct im_connection *ic, char *msg, void *data, query_callback doit, query_callback dont ) { - query_add( ic->irc, ic, msg, doit, dont, data ); + query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, g_free, data ); } - -/* list.c */ - -void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group ) +void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data, + query_callback doit, query_callback dont, query_callback myfree ) { - user_t *u; - char nick[MAX_NICK_LENGTH+1], *s; - irc_t *irc = ic->irc; - - if( user_findhandle( ic, handle ) ) - { - if( set_getbool( &irc->set, "debug" ) ) - imcb_log( ic, "User already exists, ignoring add request: %s", handle ); - - return; - - /* Buddy seems to exist already. Let's ignore this request then... - Eventually subsequent calls to this function *should* be possible - when a buddy is in multiple groups. But for now BitlBee doesn't - even support groups so let's silently ignore this for now. */ - } - - memset( nick, 0, MAX_NICK_LENGTH + 1 ); - strcpy( nick, nick_get( ic->acc, handle ) ); - - u = user_add( ic->irc, nick ); - -// if( !realname || !*realname ) realname = nick; -// u->realname = g_strdup( realname ); - - if( ( s = strchr( handle, '@' ) ) ) - { - u->host = g_strdup( s + 1 ); - u->user = g_strndup( handle, s - handle ); - } - else if( ic->acc->server ) - { - u->host = g_strdup( ic->acc->server ); - u->user = g_strdup( handle ); - - /* s/ /_/ ... important for AOL screennames */ - for( s = u->user; *s; s ++ ) - if( *s == ' ' ) - *s = '_'; - } - else - { - u->host = g_strdup( ic->acc->prpl->name ); - u->user = g_strdup( handle ); - } - - u->ic = ic; - u->handle = g_strdup( handle ); - if( group ) u->group = g_strdup( group ); - u->send_handler = buddy_send_handler; - u->last_typing_notice = 0; + query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, myfree, data ); } -struct buddy *imcb_find_buddy( struct im_connection *ic, char *handle ) +void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group ) { - static struct buddy b[1]; - user_t *u; - - u = user_findhandle( ic, handle ); + bee_user_t *bu; + bee_t *bee = ic->bee; - if( !u ) - return( NULL ); + if( !( bu = bee_user_by_handle( bee, ic, handle ) ) ) + bu = bee_user_new( bee, ic, handle, 0 ); - memset( b, 0, sizeof( b ) ); - strncpy( b->name, handle, 80 ); - strncpy( b->show, u->realname, BUDDY_ALIAS_MAXLEN ); - b->present = u->online; - b->ic = u->ic; + bu->group = bee_group_by_name( bee, group, TRUE ); - return( b ); + if( bee->ui->user_group ) + bee->ui->user_group( bee, bu ); } -void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *realname ) +void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *fullname ) { - user_t *u = user_findhandle( ic, handle ); - char *set; + bee_t *bee = ic->bee; + bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); - if( !u || !realname ) return; + if( !bu || !fullname ) return; - if( g_strcasecmp( u->realname, realname ) != 0 ) + if( !bu->fullname || strcmp( bu->fullname, fullname ) != 0 ) { - if( u->realname != u->nick ) g_free( u->realname ); + g_free( bu->fullname ); + bu->fullname = g_strdup( fullname ); - u->realname = g_strdup( realname ); - - if( ( ic->flags & OPT_LOGGED_IN ) && set_getbool( &ic->irc->set, "display_namechanges" ) ) - imcb_log( ic, "User `%s' changed name to `%s'", u->nick, u->realname ); - } - - set = set_getstr( &ic->acc->set, "nick_source" ); - if( strcmp( set, "handle" ) != 0 ) - { - char *name = g_strdup( realname ); - - if( strcmp( set, "first_name" ) == 0 ) - { - int i; - for( i = 0; name[i] && !isspace( name[i] ); i ++ ) {} - name[i] = '\0'; - } - - imcb_buddy_nick_hint( ic, handle, name ); - - g_free( name ); + if( bee->ui->user_fullname ) + bee->ui->user_fullname( bee, bu ); } } void imcb_remove_buddy( struct im_connection *ic, const char *handle, char *group ) { - user_t *u; - - if( ( u = user_findhandle( ic, handle ) ) ) - user_del( ic->irc, u->nick ); + 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 ) { - user_t *u = user_findhandle( ic, handle ); - char newnick[MAX_NICK_LENGTH+1], *orig_nick; + bee_t *bee = ic->bee; + bee_user_t *bu = bee_user_by_handle( bee, ic, handle ); - if( u && !u->online && !nick_saved( ic->acc, handle ) ) - { - /* Only do this if the person isn't online yet (which should - be the case if we just added it) and if the user hasn't - assigned a nickname to this buddy already. */ - - strncpy( newnick, nick, MAX_NICK_LENGTH ); - newnick[MAX_NICK_LENGTH] = 0; - - /* Some processing to make sure this string is a valid IRC nickname. */ - nick_strip( newnick ); - if( set_getbool( &ic->irc->set, "lcnicks" ) ) - nick_lc( newnick ); - - if( strcmp( u->nick, newnick ) != 0 ) - { - /* Only do this if newnick is different from the current one. - If rejoining a channel, maybe we got this nick already - (and dedupe would only add an underscore. */ - nick_dedupe( ic->acc, handle, newnick ); - - /* u->nick will be freed halfway the process, so it can't be - passed as an argument. */ - orig_nick = g_strdup( u->nick ); - user_rename( ic->irc, orig_nick, newnick ); - g_free( orig_nick ); - } - } + 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 ); } @@ -584,7 +482,8 @@ void imcb_ask_auth( struct im_connection *ic, const char *handle, const char *re data->ic = ic; data->handle = g_strdup( handle ); - query_add( ic->irc, ic, s, imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, data ); + query_add( (irc_t *) ic->bee->ui_data, ic, s, + imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, g_free, data ); } @@ -609,672 +508,25 @@ void imcb_ask_add( struct im_connection *ic, const char *handle, const char *rea char *s; /* TODO: Make a setting for this! */ - if( user_findhandle( ic, handle ) != NULL ) + 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( ic->irc, ic, s, imcb_ask_add_cb_yes, imcb_ask_add_cb_no, data ); -} - - -/* server.c */ - -void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, const char *state, const char *message ) -{ - user_t *u; - int oa, oo; - - u = user_findhandle( ic, (char*) handle ); - - if( !u ) - { - if( g_strcasecmp( set_getstr( &ic->irc->set, "handle_unknown" ), "add" ) == 0 ) - { - imcb_add_buddy( ic, (char*) handle, NULL ); - u = user_findhandle( ic, (char*) handle ); - } - else - { - if( set_getbool( &ic->irc->set, "debug" ) || g_strcasecmp( set_getstr( &ic->irc->set, "handle_unknown" ), "ignore" ) != 0 ) - { - imcb_log( ic, "imcb_buddy_status() for unknown handle %s:", handle ); - imcb_log( ic, "flags = %d, state = %s, message = %s", flags, - state ? state : "NULL", message ? message : "NULL" ); - } - - return; - } - } - - oa = u->away != NULL; - oo = u->online; - - g_free( u->away ); - g_free( u->status_msg ); - u->away = u->status_msg = NULL; - - if( set_getbool( &ic->irc->set, "show_offline" ) && !u->online ) - { - /* always set users as online */ - irc_spawn( ic->irc, u ); - u->online = 1; - if( !( flags & OPT_LOGGED_IN ) ) - { - /* set away message if user isn't really online */ - u->away = g_strdup( "User is offline" ); - } - } - else if( ( flags & OPT_LOGGED_IN ) && !u->online ) - { - irc_spawn( ic->irc, u ); - u->online = 1; - } - else if( !( flags & OPT_LOGGED_IN ) && u->online ) - { - struct groupchat *c; - - if( set_getbool( &ic->irc->set, "show_offline" ) ) - { - /* keep offline users in channel and set away message to "offline" */ - u->away = g_strdup( "User is offline" ); - - /* Keep showing him/her in the control channel but not in groupchats. */ - for( c = ic->groupchats; c; c = c->next ) - { - if( remove_chat_buddy_silent( c, handle ) && c->joined ) - irc_part( c->ic->irc, u, c->channel ); - } - } - else - { - /* kill offline users */ - irc_kill( ic->irc, u ); - u->online = 0; - - /* Remove him/her from the groupchats to prevent PART messages after he/she QUIT already */ - for( c = ic->groupchats; c; c = c->next ) - remove_chat_buddy_silent( c, handle ); - } - } - - if( flags & OPT_AWAY ) - { - if( state && message ) - { - u->away = g_strdup_printf( "%s (%s)", state, message ); - } - else if( state ) - { - u->away = g_strdup( state ); - } - else if( message ) - { - u->away = g_strdup( message ); - } - else - { - u->away = g_strdup( "Away" ); - } - } - else - { - u->status_msg = g_strdup( message ); - } - - /* early if-clause for show_offline even if there is some redundant code here because this isn't LISP but C ;) */ - if( set_getbool( &ic->irc->set, "show_offline" ) && set_getbool( &ic->irc->set, "away_devoice" ) ) - { - char *from; - - if( set_getbool( &ic->irc->set, "simulate_netsplit" ) ) - { - from = g_strdup( ic->irc->myhost ); - } - else - { - from = g_strdup_printf( "%s!%s@%s", ic->irc->mynick, ic->irc->mynick, - ic->irc->myhost ); - } - - /* if we use show_offline, we op online users, voice away users, and devoice/deop offline users */ - if( flags & OPT_LOGGED_IN ) - { - /* user is "online" (either really online or away) */ - irc_write( ic->irc, ":%s MODE %s %cv%co %s %s", from, ic->irc->channel, - u->away?'+':'-', u->away?'-':'+', u->nick, u->nick ); - } - else - { - /* user is offline */ - irc_write( ic->irc, ":%s MODE %s -vo %s %s", from, ic->irc->channel, u->nick, u->nick ); - } - } - else - { - /* LISPy... */ - if( ( set_getbool( &ic->irc->set, "away_devoice" ) ) && /* Don't do a thing when user doesn't want it */ - ( u->online ) && /* Don't touch offline people */ - ( ( ( u->online != oo ) && !u->away ) || /* Voice joining people */ - ( ( u->online == oo ) && ( oa == !u->away ) ) ) ) /* (De)voice people changing state */ - { - char *from; - - if( set_getbool( &ic->irc->set, "simulate_netsplit" ) ) - { - from = g_strdup( ic->irc->myhost ); - } - else - { - from = g_strdup_printf( "%s!%s@%s", ic->irc->mynick, ic->irc->mynick, - ic->irc->myhost ); - } - irc_write( ic->irc, ":%s MODE %s %cv %s", from, ic->irc->channel, - u->away?'-':'+', u->nick ); - g_free( from ); - } - } -} - -void imcb_buddy_msg( struct im_connection *ic, const char *handle, char *msg, uint32_t flags, time_t sent_at ) -{ - irc_t *irc = ic->irc; - char *wrapped, *ts = NULL; - user_t *u; - - u = user_findhandle( ic, handle ); - - if( !u ) - { - char *h = set_getstr( &irc->set, "handle_unknown" ); - - if( g_strcasecmp( h, "ignore" ) == 0 ) - { - if( set_getbool( &irc->set, "debug" ) ) - imcb_log( ic, "Ignoring message from unknown handle %s", handle ); - - return; - } - else if( g_strncasecmp( h, "add", 3 ) == 0 ) - { - int private = set_getbool( &irc->set, "private" ); - - if( h[3] ) - { - if( g_strcasecmp( h + 3, "_private" ) == 0 ) - private = 1; - else if( g_strcasecmp( h + 3, "_channel" ) == 0 ) - private = 0; - } - - imcb_add_buddy( ic, handle, NULL ); - u = user_findhandle( ic, handle ); - u->is_private = private; - } - else - { - imcb_log( ic, "Message from unknown handle %s:", handle ); - u = user_find( irc, irc->mynick ); - } - } - - if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) || - ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) ) - strip_html( msg ); - - if( set_getbool( &ic->irc->set, "display_timestamps" ) && - ( ts = format_timestamp( irc, sent_at ) ) ) - { - char *new = g_strconcat( ts, msg, NULL ); - g_free( ts ); - ts = msg = new; - } - - wrapped = word_wrap( msg, 425 ); - irc_msgfrom( irc, u->nick, wrapped ); - g_free( wrapped ); - g_free( ts ); -} - -void imcb_buddy_typing( struct im_connection *ic, char *handle, uint32_t flags ) -{ - user_t *u; - - if( !set_getbool( &ic->irc->set, "typing_notice" ) ) - return; - - if( ( u = user_findhandle( ic, handle ) ) ) - { - char buf[256]; - - g_snprintf( buf, 256, "\1TYPING %d\1", ( flags >> 8 ) & 3 ); - irc_privmsg( ic->irc, u, "PRIVMSG", ic->irc->nick, NULL, buf ); - } -} - -struct groupchat *imcb_chat_new( struct im_connection *ic, const char *handle ) -{ - struct groupchat *c; - - /* This one just creates the conversation structure, user won't see anything yet */ - - if( ic->groupchats ) - { - for( c = ic->groupchats; c->next; c = c->next ); - c = c->next = g_new0( struct groupchat, 1 ); - } - else - ic->groupchats = c = g_new0( struct groupchat, 1 ); - - c->ic = ic; - c->title = g_strdup( handle ); - c->channel = g_strdup_printf( "&chat_%03d", ic->irc->c_id++ ); - 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->irc->set, "debug" ) ) - imcb_log( ic, "Creating new conversation: (id=%p,handle=%s)", c, handle ); - - return c; + query_add( (irc_t *) ic->bee->ui_data, ic, s, + imcb_ask_add_cb_yes, imcb_ask_add_cb_no, g_free, data ); } -void imcb_chat_name_hint( struct groupchat *c, const char *name ) +struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle ) { - if( !c->joined ) - { - struct im_connection *ic = c->ic; - char stripped[MAX_NICK_LENGTH+1], *full_name; - - strncpy( stripped, name, MAX_NICK_LENGTH ); - stripped[MAX_NICK_LENGTH] = '\0'; - nick_strip( stripped ); - if( set_getbool( &ic->irc->set, "lcnicks" ) ) - nick_lc( stripped ); - - full_name = g_strdup_printf( "&%s", stripped ); - - if( stripped[0] && - nick_cmp( stripped, ic->irc->channel + 1 ) != 0 && - irc_chat_by_channel( ic->irc, full_name ) == NULL ) - { - g_free( c->channel ); - c->channel = full_name; - } - else - { - g_free( full_name ); - } - } -} - -void imcb_chat_free( struct groupchat *c ) -{ - struct im_connection *ic = c->ic; - struct groupchat *l; - GList *ir; - - if( set_getbool( &ic->irc->set, "debug" ) ) - imcb_log( ic, "You were removed from conversation %p", c ); - - if( c ) - { - if( c->joined ) - { - user_t *u, *r; - - r = user_find( ic->irc, ic->irc->mynick ); - irc_privmsg( ic->irc, r, "PRIVMSG", c->channel, "", "Cleaning up channel, bye!" ); - - u = user_find( ic->irc, ic->irc->nick ); - irc_kick( ic->irc, u, c->channel, r ); - /* irc_part( ic->irc, u, c->channel ); */ - } - - /* Find the previous chat in the linked list. */ - for( l = ic->groupchats; l && l->next != c; l = l->next ); - - if( l ) - l->next = c->next; - else - ic->groupchats = c->next; - - for( ir = c->in_room; ir; ir = ir->next ) - g_free( ir->data ); - g_list_free( c->in_room ); - g_free( c->channel ); - 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; - char *wrapped; - user_t *u; - - /* Gaim sends own messages through this too. IRC doesn't want this, so kill them */ - if( g_strcasecmp( who, ic->acc->user ) == 0 ) - return; - - u = user_findhandle( ic, who ); - - if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) || - ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) ) - strip_html( msg ); - - wrapped = word_wrap( msg, 425 ); - if( c && u ) - { - char *ts = NULL; - if( set_getbool( &ic->irc->set, "display_timestamps" ) ) - ts = format_timestamp( ic->irc, sent_at ); - irc_privmsg( ic->irc, u, "PRIVMSG", c->channel, ts ? : "", wrapped ); - g_free( ts ); - } - else - { - imcb_log( ic, "Message from/to conversation %s@%p (unknown conv/user): %s", who, c, wrapped ); - } - g_free( wrapped ); -} - -void imcb_chat_log( struct groupchat *c, char *format, ... ) -{ - irc_t *irc = c->ic->irc; - va_list params; - char *text; - user_t *u; - - va_start( params, format ); - text = g_strdup_vprintf( format, params ); - va_end( params ); - - u = user_find( irc, irc->mynick ); - - irc_privmsg( irc, u, "PRIVMSG", c->channel, "System message: ", 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; - user_t *u = NULL; - - if( who == NULL) - u = user_find( ic->irc, ic->irc->mynick ); - else if( g_strcasecmp( who, ic->acc->user ) == 0 ) - u = user_find( ic->irc, ic->irc->nick ); - else - u = user_findhandle( ic, who ); - - if( ( g_strcasecmp( set_getstr( &ic->irc->set, "strip_html" ), "always" ) == 0 ) || - ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->irc->set, "strip_html" ) ) ) - strip_html( topic ); - - g_free( c->topic ); - c->topic = g_strdup( topic ); - - if( c->joined && u ) - irc_write( ic->irc, ":%s!%s@%s TOPIC %s :%s", u->nick, u->user, u->host, c->channel, topic ); -} - - -/* buddy_chat.c */ - -void imcb_chat_add_buddy( struct groupchat *b, const char *handle ) -{ - user_t *u = user_findhandle( b->ic, handle ); - int me = 0; - - if( set_getbool( &b->ic->irc->set, "debug" ) ) - imcb_log( b->ic, "User %s added to conversation %p", handle, b ); - - /* It might be yourself! */ - if( b->ic->acc->prpl->handle_cmp( handle, b->ic->acc->user ) == 0 ) - { - u = user_find( b->ic->irc, b->ic->irc->nick ); - if( !b->joined ) - irc_join( b->ic->irc, u, b->channel ); - b->joined = me = 1; - } - - /* Most protocols allow people to join, even when they're not in - your contact list. Try to handle that here */ - if( !u ) - { - imcb_add_buddy( b->ic, handle, NULL ); - u = user_findhandle( b->ic, handle ); - } - - /* Add the handle to the room userlist, if it's not 'me' */ - if( !me ) - { - if( b->joined ) - irc_join( b->ic->irc, u, b->channel ); - b->in_room = g_list_append( b->in_room, g_strdup( handle ) ); - } -} - -/* This function is one BIG hack... :-( EREWRITE */ -void imcb_chat_remove_buddy( struct groupchat *b, const char *handle, const char *reason ) -{ - user_t *u; - int me = 0; - - if( set_getbool( &b->ic->irc->set, "debug" ) ) - imcb_log( b->ic, "User %s removed from conversation %p (%s)", handle, b, reason ? reason : "" ); - - /* It might be yourself! */ - if( g_strcasecmp( handle, b->ic->acc->user ) == 0 ) - { - if( b->joined == 0 ) - return; - - u = user_find( b->ic->irc, b->ic->irc->nick ); - b->joined = 0; - me = 1; - } - else - { - u = user_findhandle( b->ic, handle ); - } - - if( me || ( remove_chat_buddy_silent( b, handle ) && b->joined && u ) ) - irc_part( b->ic->irc, u, b->channel ); -} - -static int remove_chat_buddy_silent( struct groupchat *b, const char *handle ) -{ - GList *i; - - /* Find the handle in the room userlist and shoot it */ - i = b->in_room; - while( i ) - { - if( g_strcasecmp( handle, i->data ) == 0 ) - { - g_free( i->data ); - b->in_room = g_list_remove( b->in_room, i->data ); - return( 1 ); - } - - i = i->next; - } - - return( 0 ); -} - - -/* Misc. BitlBee stuff which shouldn't really be here */ - -char *set_eval_away_devoice( set_t *set, char *value ) -{ - irc_t *irc = set->data; - int st; - - if( !is_bool( value ) ) - return SET_INVALID; - - st = bool2int( value ); - - /* Horror.... */ - - if( st != set_getbool( &irc->set, "away_devoice" ) ) - { - char list[80] = ""; - user_t *u = irc->users; - int i = 0, count = 0; - char pm; - char v[80]; - - if( st ) - pm = '+'; - else - pm = '-'; - - while( u ) - { - if( u->ic && u->online && !u->away ) - { - if( ( strlen( list ) + strlen( u->nick ) ) >= 79 ) - { - for( i = 0; i < count; v[i++] = 'v' ); v[i] = 0; - irc_write( irc, ":%s MODE %s %c%s%s", - irc->myhost, - irc->channel, pm, v, list ); - - *list = 0; - count = 0; - } - - sprintf( list + strlen( list ), " %s", u->nick ); - count ++; - } - u = u->next; - } - - /* $v = 'v' x $i */ - for( i = 0; i < count; v[i++] = 'v' ); v[i] = 0; - irc_write( irc, ":%s MODE %s %c%s%s", irc->myhost, - irc->channel, pm, v, list ); - } - - return value; -} - -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; -} - -static char *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->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 ); + 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_buddy_msg( struct im_connection *ic, char *handle, char *msg, int flags ) -{ - char *buf = NULL; - int st; - - if( ( ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) ) - { - buf = escape_html( msg ); - msg = buf; - } - - st = ic->acc->prpl->buddy_msg( ic, handle, msg, flags ); - g_free( buf ); - - return st; -} - int imc_chat_msg( struct groupchat *c, char *msg, int flags ) { char *buf = NULL; @@ -1302,7 +554,7 @@ int imc_away_send_update( struct im_connection *ic ) return 0; away = set_getstr( &ic->acc->set, "away" ) ? - : set_getstr( &ic->irc->set, "away" ); + : set_getstr( &ic->bee->set, "away" ); if( away && *away ) { GList *m = ic->acc->prpl->away_states( ic ); @@ -1313,7 +565,7 @@ int imc_away_send_update( struct im_connection *ic ) { away = NULL; msg = set_getstr( &ic->acc->set, "status" ) ? - : set_getstr( &ic->irc->set, "status" ); + : set_getstr( &ic->bee->set, "status" ); } ic->acc->prpl->set_away( ic, away, msg ); diff --git a/protocols/nogaim.h b/protocols/nogaim.h index 48a80413..46f6535a 100644 --- a/protocols/nogaim.h +++ b/protocols/nogaim.h @@ -1,7 +1,7 @@ /********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * - * Copyright 2002-2004 Wilmer van der Gaast and others * + * Copyright 2002-2010 Wilmer van der Gaast and others * \********************************************************************/ /* @@ -44,6 +44,8 @@ #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 */ @@ -84,9 +86,9 @@ struct im_connection int evil; /* BitlBee */ - irc_t *irc; + bee_t *bee; - struct groupchat *groupchats; + GSList *groupchats; }; struct groupchat { @@ -97,10 +99,9 @@ struct groupchat { * "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; + //GList *ignored; - struct groupchat *next; - char *channel; + //struct groupchat *next; /* The title variable contains the ID you gave when you created the * chat using imcb_chat_new(). */ char *title; @@ -111,6 +112,7 @@ struct groupchat { /* 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 { @@ -131,6 +133,7 @@ struct prpl { /* 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; /* 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 @@ -208,13 +211,19 @@ struct prpl { * 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); + (* 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". */ @@ -227,6 +236,16 @@ struct prpl { /* 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 ); + + /* 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. */ @@ -262,6 +281,7 @@ G_MODULE_EXPORT void imcb_error( struct im_connection *ic, char *format, ... ) G * - '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? @@ -280,16 +300,8 @@ G_MODULE_EXPORT struct buddy *imcb_find_buddy( struct im_connection *ic, char *h 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 ); -/* 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 ); -/* Not implemented yet! */ 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, uint32_t flags, time_t sent_at ); G_MODULE_EXPORT void imcb_buddy_typing( struct im_connection *ic, 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 ); /* Groupchats */ @@ -315,7 +327,6 @@ G_MODULE_EXPORT void imcb_chat_free( struct groupchat *c ); /* Actions, or whatever. */ int imc_away_send_update( struct im_connection *ic ); -int imc_buddy_msg( struct im_connection *ic, char *handle, char *msg, int flags ); int imc_chat_msg( struct groupchat *c, char *msg, int flags ); void imc_add_allow( struct im_connection *ic, char *handle ); @@ -325,7 +336,6 @@ void imc_rem_block( struct im_connection *ic, char *handle ); /* Misc. stuff */ char *set_eval_timezone( set_t *set, char *value ); -char *set_eval_away_devoice( set_t *set, char *value ); gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond ); void cancel_auto_reconnect( struct account *a ); diff --git a/protocols/oscar/Makefile b/protocols/oscar/Makefile index 2792f22a..0ec7436b 100644 --- a/protocols/oscar/Makefile +++ b/protocols/oscar/Makefile @@ -7,6 +7,10 @@ ### 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 @@ -32,7 +36,7 @@ distclean: clean $(objects): ../../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c index f98fbe6f..0310a27e 100644 --- a/protocols/oscar/oscar.c +++ b/protocols/oscar/oscar.c @@ -253,8 +253,6 @@ static char *normalize(const char *s) g_return_val_if_fail((s != NULL), NULL); u = t = g_strdup(s); - - strcpy(t, s); g_strdown(t); while (*t && (x < BUF_LEN - 1)) { @@ -289,7 +287,7 @@ static gboolean oscar_callback(gpointer data, gint source, odata = (struct oscar_data *)ic->proto_data; - if (condition & GAIM_INPUT_READ) { + if (condition & B_EV_IO_READ) { if (aim_get_command(odata->sess, conn) >= 0) { aim_rxdispatch(odata->sess); if (odata->killme) @@ -361,7 +359,7 @@ static gboolean oscar_login_connect(gpointer data, gint source, b_input_conditio } aim_conn_completeconnect(sess, conn); - ic->inpa = b_input_add(conn->fd, GAIM_INPUT_READ, + ic->inpa = b_input_add(conn->fd, B_EV_IO_READ, oscar_callback, conn); return FALSE; @@ -492,7 +490,7 @@ static gboolean oscar_bos_connect(gpointer data, gint source, b_input_condition } aim_conn_completeconnect(sess, bosconn); - ic->inpa = b_input_add(bosconn->fd, GAIM_INPUT_READ, + ic->inpa = b_input_add(bosconn->fd, B_EV_IO_READ, oscar_callback, bosconn); imcb_log(ic, _("Connection established, cookie sent")); @@ -651,6 +649,7 @@ static int gaim_parse_logout(aim_session_t *sess, aim_frame_t *fr, ...) { 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); @@ -663,7 +662,12 @@ static int conninitdone_chat(aim_session_t *sess, aim_frame_t *fr, ...) { chatcon = find_oscar_chat_by_conn(ic, fr->conn); chatcon->id = id; - chatcon->cnv = imcb_chat_new(ic, chatcon->show); + + 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; @@ -702,7 +706,7 @@ static gboolean oscar_chatnav_connect(gpointer data, gint source, b_input_condit } aim_conn_completeconnect(sess, tstconn); - odata->cnpa = b_input_add(tstconn->fd, GAIM_INPUT_READ, + odata->cnpa = b_input_add(tstconn->fd, B_EV_IO_READ, oscar_callback, tstconn); return FALSE; @@ -730,7 +734,7 @@ static gboolean oscar_auth_connect(gpointer data, gint source, b_input_condition } aim_conn_completeconnect(sess, tstconn); - odata->paspa = b_input_add(tstconn->fd, GAIM_INPUT_READ, + odata->paspa = b_input_add(tstconn->fd, B_EV_IO_READ, oscar_callback, tstconn); return FALSE; @@ -766,7 +770,7 @@ static gboolean oscar_chat_connect(gpointer data, gint source, b_input_condition aim_conn_completeconnect(sess, ccon->conn); ccon->inpa = b_input_add(tstconn->fd, - GAIM_INPUT_READ, + B_EV_IO_READ, oscar_callback, tstconn); odata->oscar_chats = g_slist_append(odata->oscar_chats, ccon); @@ -933,7 +937,7 @@ static int gaim_parse_oncoming(aim_session_t *sess, aim_frame_t *fr, ...) { tmp = normalize(info->sn); imcb_buddy_status(ic, tmp, flags, state_string, NULL); - /* imcb_buddy_times(ic, tmp, signon, time_idle); */ + imcb_buddy_times(ic, tmp, signon, time_idle); return 1; @@ -1059,8 +1063,7 @@ static void gaim_icq_authgrant(void *data_) { message = 0; aim_ssi_auth_reply(od->sess, od->conn, uin, 1, ""); // aim_send_im_ch4(od->sess, uin, AIM_ICQMSG_AUTHGRANTED, &message); - if(imcb_find_buddy(data->ic, uin) == NULL) - imcb_ask_add(data->ic, uin, NULL); + imcb_ask_add(data->ic, uin, NULL); g_free(uin); g_free(data); @@ -1821,11 +1824,13 @@ static void oscar_get_info(struct im_connection *g, char *name) { 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); } @@ -1952,7 +1957,7 @@ static int gaim_ssi_parserights(aim_session_t *sess, aim_frame_t *fr, ...) { 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; + struct aim_ssi_item *curitem, *curgroup; int tmp; char *nrm; @@ -1963,13 +1968,13 @@ static int gaim_ssi_parselist(aim_session_t *sess, aim_frame_t *fr, ...) { switch (curitem->type) { case 0x0000: /* Buddy */ - if ((curitem->name) && (!imcb_find_buddy(ic, nrm))) { + 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, NULL); + + imcb_add_buddy(ic, nrm, curgroup->gid == curitem->gid ? curgroup->name : NULL); if (realname) { imcb_buddy_nick_hint(ic, nrm, realname); @@ -1979,6 +1984,10 @@ static int gaim_ssi_parselist(aim_session_t *sess, aim_frame_t *fr, ...) { } break; + case 0x0001: /* Group */ + curgroup = curitem; + break; + case 0x0002: /* Permit buddy */ if (curitem->name) { GSList *list; @@ -2421,7 +2430,8 @@ void oscar_chat_msg(struct groupchat *c, char *message, int msgflags) guint16 flags; char *s; - ccon = c->data; + if (!(ccon = c->data)) + return; for (s = message; *s; s++) if (*s & 128) @@ -2462,7 +2472,10 @@ 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 = c->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); @@ -2487,44 +2500,59 @@ void oscar_chat_kill(struct im_connection *ic, struct chat_connection *cc) void oscar_chat_leave(struct groupchat *c) { + if (!c->data) + return; oscar_chat_kill(c->ic, c->data); } -struct groupchat *oscar_chat_join(struct im_connection * ic, const char * room, const char * nick, const char * password ) +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))) { int st; - st = aim_chatnav_createroom(od->sess, cur, room, 4); + st = aim_chatnav_createroom(od->sess, cur, room, exchange_number); - return NULL; + return ret; } else { struct create_room * cr = g_new0(struct create_room, 1); - cr->exchange = 4; + 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 NULL; + 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; + char * chatname, *s; + struct groupchat *c; chatname = g_strdup_printf("%s%s%d", isdigit(*ic->acc->user) ? "icq" : "", ic->acc->user, chat_id++); - - ret = oscar_chat_join(ic, chatname, NULL, NULL); - + + for (s = chatname; *s; s ++) + if (!isalnum(*s)) + *s = '0'; + + c = imcb_chat_new(ic, chatname); + ret = oscar_chat_join_internal(ic, chatname, NULL, NULL, 4); aim_chat_invite(od->sess, od->conn, who, "", 4, chatname, 0x0); g_free(chatname); @@ -2536,7 +2564,7 @@ void oscar_accept_chat(void *data) { struct aim_chat_invitation * inv = data; - oscar_chat_join(inv->ic, inv->name, NULL, NULL); + oscar_chat_join_internal(inv->ic, inv->name, NULL, NULL, 4); g_free(inv->name); g_free(inv); } @@ -2549,6 +2577,16 @@ void oscar_reject_chat(void *data) 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); @@ -2569,6 +2607,8 @@ void oscar_initmodule() 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; diff --git a/protocols/purple/Makefile b/protocols/purple/Makefile new file mode 100644 index 00000000..97a5bb6a --- /dev/null +++ b/protocols/purple/Makefile @@ -0,0 +1,44 @@ +########################### +## 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 + +### MAIN PROGRAM + +$(objects): ../../Makefile.settings Makefile + +$(objects): %.o: $(SRCDIR)%.c + @echo '*' Compiling $< + @$(CC) -c $(CFLAGS) $< -o $@ + +purple_mod.o: $(objects) + @echo '*' Linking purple_mod.o + $(LD) $(LFLAGS) $(objects) -o purple_mod.o diff --git a/protocols/purple/ft-direct.c b/protocols/purple/ft-direct.c new file mode 100644 index 00000000..98a16d75 --- /dev/null +++ b/protocols/purple/ft-direct.c @@ -0,0 +1,239 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* libpurple module - File transfer stuff * +* * +* Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License 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 <stdarg.h> + +#include <glib.h> +#include <purple.h> + +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; +} diff --git a/protocols/purple/ft.c b/protocols/purple/ft.c new file mode 100644 index 00000000..c4efc657 --- /dev/null +++ b/protocols/purple/ft.c @@ -0,0 +1,355 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* libpurple module - File transfer stuff * +* * +* Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License 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 <stdarg.h> + +#include <glib.h> +#include <purple.h> + +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, +}; diff --git a/protocols/purple/purple.c b/protocols/purple/purple.c new file mode 100644 index 00000000..c7cfcfda --- /dev/null +++ b/protocols/purple/purple.c @@ -0,0 +1,1201 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* libpurple module - Main file * +* * +* Copyright 2009-2010 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License 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 <stdarg.h> + +#include <glib.h> +#include <purple.h> + +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; + + help = g_string_new( "" ); + g_string_printf( help, "BitlBee libpurple module %s (%s).\n\nSupported settings:", + (char*) acc->prpl->name, prpl->info->name ); + + /* 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_usermsg( acc->irc, "Setting with unknown type: %s (%d) Expect stuff to break..\n", + 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; + } + + /* 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 ) + { + 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; + + return NULL; +} + +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 ); +} + +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 ); + } +} + +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; + PurpleConversation *conv; + + if( flags & OPT_TYPING ) + state = PURPLE_TYPING; + else if( flags & OPT_THINKING ) + state = PURPLE_TYPED; + + if( ( conv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, + who, ic->proto_data ) ) == NULL ) + { + purple_conv_im_set_typing_state( purple_conversation_get_im_data( conv ), state ); + return 1; + } + else + { + return 0; + } +} + +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 ) +{ + 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 ); + } + + 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 ); + + 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 ); +} + +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; + + if( B_EV_IO_READ != PURPLE_INPUT_READ || + B_EV_IO_WRITE != PURPLE_INPUT_WRITE ) + { + /* FIXME FIXME FIXME FIXME FIXME :-) */ + exit( 1 ); + } + + purple_util_set_user_dir( "/tmp" ); + 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(); + } + + /* This seems like stateful shit we don't want... */ + purple_set_blist( purple_blist_new() ); + purple_blist_load(); + + /* Meh? */ + purple_prefs_load(); + + 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; + + 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 <protocol name>\x02" ); + + /* 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 ); +} diff --git a/protocols/twitter/Makefile b/protocols/twitter/Makefile index ca1e4695..8a4b97f9 100644 --- a/protocols/twitter/Makefile +++ b/protocols/twitter/Makefile @@ -7,6 +7,9 @@ ### DEFINITIONS -include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/twitter/ +endif # [SH] Program variables objects = twitter.o twitter_http.o twitter_lib.o @@ -32,7 +35,7 @@ distclean: clean $(objects): ../../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index 2e3ab634..f718eeb7 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -118,7 +118,7 @@ static gboolean twitter_oauth_callback( struct oauth_info *info ) return FALSE; } - sprintf( name, "twitter_%s", ic->acc->user ); + sprintf( name, "%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 ); @@ -171,8 +171,21 @@ static gboolean twitter_length_check( struct im_connection *ic, gchar *msg ) static void twitter_init( account_t *acc ) { set_t *s; + char *def_url; + char *def_oauth; - s = set_add( &acc->set, "base_url", TWITTER_API_URL, NULL, acc ); + if( strcmp( acc->prpl->name, "twitter" ) == 0 ) + { + def_url = TWITTER_API_URL; + def_oauth = "true"; + } + else /* if( strcmp( acc->prpl->name, "identica" ) == 0 ) */ + { + def_url = IDENTICA_API_URL; + def_oauth = "false"; + } + + s = set_add( &acc->set, "base_url", def_url, NULL, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; s = set_add( &acc->set, "message_length", "140", set_eval_int, acc ); @@ -180,7 +193,7 @@ static void twitter_init( account_t *acc ) s = set_add( &acc->set, "mode", "one", set_eval_mode, acc ); s->flags |= ACC_SET_OFFLINE_ONLY; - s = set_add( &acc->set, "oauth", "true", set_eval_bool, acc ); + s = set_add( &acc->set, "oauth", def_oauth, set_eval_bool, acc ); } /** @@ -213,12 +226,16 @@ static void twitter_login( account_t *acc ) td->url_path = g_strdup( url.file ); else td->url_path = g_strdup( "" ); + if( g_str_has_suffix( url.host, ".com" ) ) + td->prefix = g_strndup( url.host, strlen( url.host ) - 4 ); + else + td->prefix = g_strdup( url.host ); td->user = acc->user; if( strstr( acc->pass, "oauth_token=" ) ) td->oauth_info = oauth_from_string( acc->pass, &twitter_oauth ); - sprintf( name, "twitter_%s", acc->user ); + sprintf( name, "%s_%s", td->prefix, acc->user ); imcb_add_buddy( ic, name, NULL ); imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL ); @@ -246,6 +263,7 @@ static void twitter_logout( struct im_connection *ic ) if( td ) { oauth_info_free( td->oauth_info ); + g_free( td->prefix ); g_free( td->url_host ); g_free( td->url_path ); g_free( td->pass ); @@ -261,9 +279,10 @@ static void twitter_logout( struct im_connection *ic ) 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, "twitter_", 8) == 0 && - g_strcasecmp(who + 8, ic->acc->user) == 0) + 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 ) @@ -315,8 +334,28 @@ static void twitter_remove_buddy( struct im_connection *ic, char *who, char *gro static void twitter_chat_msg( struct groupchat *c, char *message, int flags ) { - if( c && message && twitter_length_check(c->ic, message)) - twitter_post_status(c->ic, message); + if( c && message && twitter_length_check( c->ic, message ) ) + { + char *s, *new = NULL; + + if( ( s = strchr( message, ':' ) ) || + ( s = strchr( message, ',' ) ) ) + { + bee_user_t *bu; + + new = g_strdup( message ); + new[s-message] = '\0'; + if( ( bu = bee_user_by_handle( c->ic->bee, c->ic, new ) ) ) + { + sprintf( new, "@%s", bu->handle ); + new[s-message+1] = ' '; + message = new; + } + } + + twitter_post_status( c->ic, message ); + g_free( new ); + } } static void twitter_chat_invite( struct groupchat *c, char *who, char *message ) @@ -395,10 +434,14 @@ void twitter_initmodule() ret->rem_deny = twitter_rem_deny; ret->send_typing = twitter_send_typing; 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); // Initialise the twitter_connections GSList. twitter_connections = NULL; } - diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h index e61d32be..b7e41fc5 100644 --- a/protocols/twitter/twitter.h +++ b/protocols/twitter/twitter.h @@ -52,6 +52,8 @@ struct twitter_data int url_port; char *url_host; char *url_path; + + char *prefix; /* Used to generate contact + channel name. */ }; /** diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 0578c5e0..620850ab 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -116,7 +116,7 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char * struct twitter_data *td = ic->proto_data; // Check if the buddy is allready in the buddy list. - if (!imcb_find_buddy( ic, name )) + if (!bee_user_by_handle( ic->bee, ic, name )) { char *mode = set_getstr(&ic->acc->set, "mode"); @@ -124,7 +124,12 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char * imcb_add_buddy( ic, name, NULL ); imcb_rename_buddy( ic, name, fullname ); if (g_strcasecmp(mode, "chat") == 0) + { + /* Necessary so that nicks always get translated to the + exact Twitter username. */ + imcb_buddy_nick_hint( ic, name, name ); imcb_chat_add_buddy( td->home_timeline_gc, name ); + } else if (g_strcasecmp(mode, "many") == 0) imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL ); } @@ -458,7 +463,7 @@ static void twitter_groupchat_init(struct im_connection *ic) td->home_timeline_gc = gc = imcb_chat_new( ic, "home/timeline" ); - name_hint = g_strdup_printf( "Twitter_%s", ic->acc->user ); + name_hint = g_strdup_printf( "%s_%s", td->prefix, ic->acc->user ); imcb_chat_name_hint( gc, name_hint ); g_free( name_hint ); } @@ -518,7 +523,7 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList *list) if( mode_one ) { - g_snprintf( from, sizeof( from ) - 1, "twitter_%s", ic->acc->user ); + g_snprintf( from, sizeof( from ) - 1, "%s_%s", td->prefix, ic->acc->user ); from[MAX_STRING-1] = '\0'; } diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h index 6b90f9bb..5a3c3f68 100644 --- a/protocols/twitter/twitter_lib.h +++ b/protocols/twitter/twitter_lib.h @@ -29,6 +29,7 @@ #include "twitter_http.h" #define TWITTER_API_URL "http://twitter.com" +#define IDENTICA_API_URL "http://identi.ca/api" /* Status URLs */ #define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml" diff --git a/protocols/yahoo/Makefile b/protocols/yahoo/Makefile index b4fe56e2..e5374538 100644 --- a/protocols/yahoo/Makefile +++ b/protocols/yahoo/Makefile @@ -7,11 +7,14 @@ ### DEFINITIONS -include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/yahoo/ +endif # [SH] Program variables objects = yahoo.o crypt.o libyahoo2.o yahoo_fn.o yahoo_httplib.o yahoo_util.o -CFLAGS += -Wall -DSTDC_HEADERS -DHAVE_STRING_H -DHAVE_STRCHR -DHAVE_MEMCPY -DHAVE_GLIB +CFLAGS += -Wall -DSTDC_HEADERS -DHAVE_STRING_H -DHAVE_STRCHR -DHAVE_MEMCPY -DHAVE_GLIB -Wno-pointer-to-int-cast LFLAGS += -r # [SH] Phony targets @@ -32,7 +35,7 @@ distclean: clean $(objects): ../../Makefile.settings Makefile -$(objects): %.o: %.c +$(objects): %.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ diff --git a/protocols/yahoo/libyahoo2.c b/protocols/yahoo/libyahoo2.c index 1bfc2e59..bd111654 100644 --- a/protocols/yahoo/libyahoo2.c +++ b/protocols/yahoo/libyahoo2.c @@ -2,6 +2,8 @@ * libyahoo2: libyahoo2.c * * Some code copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net> + * YMSG16 code copyright (C) 2009, + * Siddhesh Poyarekar <siddhesh dot poyarekar at gmail dot com> * * Yahoo Search copyright (C) 2003, Konstantin Klyagin <konst AT konst.org.ua> * @@ -26,6 +28,8 @@ * Portions of Sylpheed copyright 2000-2002 Hiroyuki Yamamoto * <hiro-y@kcn.ne.jp> * + * 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 @@ -91,9 +95,9 @@ char *strchr (), *strrchr (); #include "http_client.h" #ifdef USE_STRUCT_CALLBACKS -struct yahoo_callbacks *yc=NULL; +struct yahoo_callbacks *yc = NULL; -void yahoo_register_callbacks(struct yahoo_callbacks * tyc) +void yahoo_register_callbacks(struct yahoo_callbacks *tyc) { yc = tyc; } @@ -103,21 +107,23 @@ void yahoo_register_callbacks(struct yahoo_callbacks * tyc) #define YAHOO_CALLBACK(x) x #endif -static int yahoo_send_data(int fd, void *data, int len); +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, ...) +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); + return YAHOO_CALLBACK(ext_yahoo_log) ("%s", out); } -int yahoo_connect(char * host, int port) +int yahoo_connect(char *host, int port) { - return YAHOO_CALLBACK(ext_yahoo_connect)(host, port); + return YAHOO_CALLBACK(ext_yahoo_connect) (host, port); } static enum yahoo_log_level log_level = YAHOO_LOG_NONE; @@ -135,97 +141,30 @@ int yahoo_set_log_level(enum yahoo_log_level level) } /* default values for servers */ -static char pager_host[] = "scs.msg.yahoo.com"; +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 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/"; -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, /* 0 - id and that's it?? */ - 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_STEALTH = 0xb9, - YAHOO_SERVICE_PICTURE_CHECKSUM = 0xbd, - YAHOO_SERVICE_PICTURE = 0xbe, - YAHOO_SERVICE_PICTURE_UPDATE = 0xc1, - YAHOO_SERVICE_PICTURE_UPLOAD = 0xc2, - YAHOO_SERVICE_Y6_VISIBILITY=0xc5, - YAHOO_SERVICE_Y6_STATUS_UPDATE=0xc6, - YAHOO_PHOTOSHARE_INIT=0xd2, - YAHOO_SERVICE_CONTACT_YMSG13=0xd6, - YAHOO_PHOTOSHARE_PREV=0xd7, - YAHOO_PHOTOSHARE_KEY=0xd8, - YAHOO_PHOTOSHARE_TRANS=0xda, - YAHOO_FILE_TRANSFER_INIT_YMSG13=0xdc, - YAHOO_FILE_TRANSFER_GET_YMSG13=0xdd, - YAHOO_FILE_TRANSFER_PUT_YMSG13=0xde, - YAHOO_SERVICE_YMSG15_STATUS=0xf0, - YAHOO_SERVICE_YMSG15_BUDDY_LIST=0xf1, +struct connect_callback_data { + struct yahoo_data *yd; + int tag; + int i; + int server_i; }; struct yahoo_pair { @@ -241,15 +180,15 @@ struct yahoo_packet { }; 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; + 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 { @@ -263,34 +202,52 @@ struct yahoo_input_data { struct yahoo_webcam_data *wcd; struct yahoo_search_state *ys; - int fd; + void *fd; enum yahoo_connection_type type; - - unsigned char *rxqueue; - int rxlen; - int read_tag; + + unsigned char *rxqueue; + int rxlen; + int read_tag; YList *txqueues; - int write_tag; + int write_tag; }; struct yahoo_server_settings { char *pager_host; - int pager_port; + int pager_port; char *filetransfer_host; - int filetransfer_port; + int filetransfer_port; char *webcam_host; - int webcam_port; + int webcam_port; char *webcam_description; char *local_host; - int conn_type; + int conn_type; + char **pager_host_list; }; -static void * _yahoo_default_server_settings() +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); + 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_host = strdup(pager_host); yss->pager_port = pager_port; yss->filetransfer_host = strdup(filetransfer_host); yss->filetransfer_port = filetransfer_port; @@ -303,54 +260,61 @@ static void * _yahoo_default_server_settings() return yss; } -static void * _yahoo_assign_server_settings(va_list ap) +static void *_yahoo_assign_server_settings(va_list ap) { struct yahoo_server_settings *yss = _yahoo_default_server_settings(); char *key; char *svalue; - int nvalue; + int nvalue; + char **pvalue; - while(1) { + while (1) { key = va_arg(ap, char *); - if(key == NULL) + if (key == NULL) break; - if(!strcmp(key, "pager_host")) { + if (!strcmp(key, "pager_host")) { svalue = va_arg(ap, char *); free(yss->pager_host); yss->pager_host = strdup(svalue); - } else if(!strcmp(key, "pager_port")) { + 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")) { + } 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")) { + } else if (!strcmp(key, "filetransfer_port")) { nvalue = va_arg(ap, int); yss->filetransfer_port = nvalue; - } else if(!strcmp(key, "webcam_host")) { + } 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")) { + } else if (!strcmp(key, "webcam_port")) { nvalue = va_arg(ap, int); yss->webcam_port = nvalue; - } else if(!strcmp(key, "webcam_description")) { + } 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")) { + } 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")) { + } 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")); + "perhaps you didn't terminate the list " + "with NULL")); } } @@ -359,7 +323,7 @@ static void * _yahoo_assign_server_settings(va_list ap) static void yahoo_free_server_settings(struct yahoo_server_settings *yss) { - if(!yss) + if (!yss) return; free(yss->pager_host); @@ -371,30 +335,33 @@ static void yahoo_free_server_settings(struct yahoo_server_settings *yss) free(yss); } -static YList *conns=NULL; -static YList *inputs=NULL; -static int last_id=0; +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) + +static struct yahoo_data *find_conn_by_id(int id) { YList *l; - for(l = conns; l; l = y_list_next(l)) { + for (l = conns; l; l = y_list_next(l)) { struct yahoo_data *yd = l->data; - if(yd->client_id == id) + 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; @@ -405,41 +372,45 @@ static struct yahoo_input_data * find_input_by_id(int id) } return NULL; } +*/ -static struct yahoo_input_data * find_input_by_id_and_webcam_user(int id, const char * who) +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)) { + 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))) + 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; } -static struct yahoo_input_data * find_input_by_id_and_type(int id, enum yahoo_connection_type type) +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)) { + 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) + 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, int fd) +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)) { + 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) + if (yid->fd == fd && yid->yd->client_id == id) return yid; } return NULL; @@ -447,36 +418,34 @@ static struct yahoo_input_data * find_input_by_id_and_fd(int id, int fd) static int count_inputs_with_id(int id) { - int c=0; + int c = 0; YList *l; LOG(("counting %d", id)); - for(l = inputs; l; l = y_list_next(l)) { + for (l = inputs; l; l = y_list_next(l)) { struct yahoo_input_data *yid = l->data; - if(yid->yd->client_id == id) + if (yid->yd->client_id == id) c++; } LOG(("%d", c)); return c; } - extern char *yahoo_crypt(char *, char *); /* Free a buddy list */ -static void yahoo_free_buddies(YList * list) +static void yahoo_free_buddies(YList *list) { YList *l; - for(l = list; l; l = l->next) - { + for (l = list; l; l = l->next) { struct yahoo_buddy *bud = l->data; - if(!bud) + if (!bud) continue; FREE(bud->group); FREE(bud->id); FREE(bud->real_name); - if(bud->yab_entry) { + if (bud->yab_entry) { FREE(bud->yab_entry->fname); FREE(bud->yab_entry->lname); FREE(bud->yab_entry->nname); @@ -495,7 +464,7 @@ static void yahoo_free_buddies(YList * list) } /* Free an identities list */ -static void yahoo_free_identities(YList * list) +static void yahoo_free_identities(YList *list) { while (list) { YList *n = list; @@ -524,6 +493,7 @@ static void yahoo_free_data(struct yahoo_data *yd) 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); @@ -539,8 +509,8 @@ static void yahoo_free_data(struct yahoo_data *yd) #define YAHOO_PACKET_HDRLEN (4 + 2 + 2 + 2 + 2 + 4 + 4) -static struct yahoo_packet *yahoo_packet_new(enum yahoo_service service, - enum yahoo_status status, int id) +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); @@ -551,7 +521,8 @@ static struct yahoo_packet *yahoo_packet_new(enum yahoo_service service, return pkt; } -static void yahoo_packet_hash(struct yahoo_packet *pkt, int key, const char *value) +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; @@ -596,7 +567,8 @@ static int yahoo_packet_length(struct yahoo_packet *pkt) (((*((buf)+2))&0xff)<< 8) + \ (((*((buf)+3))&0xff))) -static void yahoo_packet_read(struct yahoo_packet *pkt, unsigned char *data, int len) +static void yahoo_packet_read(struct yahoo_packet *pkt, unsigned char *data, + int len) { int pos = 0; @@ -649,7 +621,8 @@ static void yahoo_packet_read(struct yahoo_packet *pkt, unsigned char *data, int pair->value = strdup(value); FREE(value); pkt->hash = y_list_append(pkt->hash, pair); - DEBUG_MSG(("Key: %d \tValue: %s", pair->key, pair->value)); + DEBUG_MSG(("Key: %d \tValue: %s", pair->key, + pair->value)); } else { FREE(pair); } @@ -689,30 +662,29 @@ static void yahoo_dump_unhandled(struct yahoo_packet *pkt) } } - static void yahoo_packet_dump(unsigned char *data, int len) { - if(yahoo_get_log_level() >= YAHOO_LOG_DEBUG) { + 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)(" "); + 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"); + YAHOO_CALLBACK(ext_yahoo_log) ("%02x ", data[i]); } - YAHOO_CALLBACK(ext_yahoo_log)("\n"); + YAHOO_CALLBACK(ext_yahoo_log) ("\n"); for (i = 0; i < len; i++) { if ((i % 8 == 0) && i) - YAHOO_CALLBACK(ext_yahoo_log)(" "); + YAHOO_CALLBACK(ext_yahoo_log) (" "); if ((i % 16 == 0) && i) - YAHOO_CALLBACK(ext_yahoo_log)("\n"); + YAHOO_CALLBACK(ext_yahoo_log) ("\n"); if (isprint(data[i])) - YAHOO_CALLBACK(ext_yahoo_log)(" %c ", data[i]); + YAHOO_CALLBACK(ext_yahoo_log) (" %c ", data[i]); else - YAHOO_CALLBACK(ext_yahoo_log)(" . "); + YAHOO_CALLBACK(ext_yahoo_log) (" . "); } - YAHOO_CALLBACK(ext_yahoo_log)("\n"); + YAHOO_CALLBACK(ext_yahoo_log) ("\n"); } } @@ -722,7 +694,8 @@ 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) +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); @@ -731,15 +704,17 @@ static void yahoo_add_to_send_queue(struct yahoo_input_data *yid, void *data, in 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); + 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) +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; @@ -748,19 +723,20 @@ static void yahoo_send_packet(struct yahoo_input_data *yid, struct yahoo_packet data = y_new0(unsigned char, len + 1); - memcpy(data + pos, "YMSG", 4); pos += 4; - pos += yahoo_put16(data + pos, YAHOO_PROTO_VER); - pos += yahoo_put16(data + pos, 0x0000); - pos += yahoo_put16(data + pos, pktlen + extra_pad); - pos += yahoo_put16(data + pos, pkt->service); - pos += yahoo_put32(data + pos, pkt->status); - pos += yahoo_put32(data + pos, pkt->id); + 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 ) + + if (yid->type == YAHOO_CONNECTION_FT) yahoo_send_data(yid->fd, data, len); else yahoo_add_to_send_queue(yid, data, len); @@ -781,92 +757,94 @@ static void yahoo_packet_free(struct yahoo_packet *pkt) FREE(pkt); } -static int yahoo_send_data(int fd, void *data, int len) +static int yahoo_send_data(void *fd, void *data, int len) { int ret; int e; - if (fd < 0) + if (fd == NULL) return -1; yahoo_packet_dump(data, len); do { - ret = write(fd, data, len); - } while(ret == -1 && errno==EINTR); - e=errno; + ret = YAHOO_CALLBACK(ext_yahoo_write) (fd, data, len); + } while (ret == -1 && errno == EINTR); + e = errno; - if (ret == -1) { + if (ret == -1) { LOG(("wrote data: ERR %s", strerror(errno))); } else { LOG(("wrote data: OK")); } - errno=e; + errno = e; return ret; } -void yahoo_close(int id) +void yahoo_close(int id) { struct yahoo_data *yd = find_conn_by_id(id); - - if(!yd) + if (!yd) return; del_from_list(yd); yahoo_free_data(yd); - if(id == last_id) + if (id == last_id) last_id--; } -static void yahoo_input_close(struct yahoo_input_data *yid) +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); + 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) - close(yid->fd); + 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) { + 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) + if (yid->wcd) FREE(yid->wcd); - if(yid->ys) { + if (yid->ys) { FREE(yid->ys->lsearch_text); FREE(yid->ys); } FREE(yid); } -static int is_same_bud(const void * a, const void * b) { +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) +static char *getcookie(char *rawcookie) { - char * cookie=NULL; - char * tmpcookie; - char * cookieend; + char *cookie = NULL; + char *tmpcookie; + char *cookieend; - if (strlen(rawcookie) < 2) + if (strlen(rawcookie) < 2) return NULL; - tmpcookie = strdup(rawcookie+2); + tmpcookie = strdup(rawcookie + 2); cookieend = strchr(tmpcookie, ';'); - if(cookieend) + if (cookieend) *cookieend = '\0'; cookie = strdup(tmpcookie); @@ -876,18 +854,18 @@ static char * getcookie(char *rawcookie) return cookie; } -static char * getlcookie(char *cookie) +static char *getlcookie(char *cookie) { char *tmp; char *tmpend; char *login_cookie = NULL; tmpend = strstr(cookie, "n="); - if(tmpend) { - tmp = strdup(tmpend+2); + if (tmpend) { + tmp = strdup(tmpend + 2); tmpend = strchr(tmp, '&'); - if(tmpend) - *tmpend='\0'; + if (tmpend) + *tmpend = '\0'; login_cookie = strdup(tmp); FREE(tmp); } @@ -895,7 +873,8 @@ static char * getlcookie(char *cookie) return login_cookie; } -static void yahoo_process_notify(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_notify(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *msg = NULL; @@ -926,84 +905,31 @@ static void yahoo_process_notify(struct yahoo_input_data *yid, struct yahoo_pack 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); - else if (!strncasecmp(msg, "WEBCAMINVITE", strlen("WEBCAMINVITE"))) - { + + 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); + 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); + YAHOO_CALLBACK(ext_yahoo_webcam_invite_reply) (yd-> + client_id, to, from, accept); } - } - else + } else LOG(("Got unknown notification: %s", msg)); } -static void yahoo_process_filetransfer(struct yahoo_input_data *yid, struct yahoo_packet *pkt) -{ - struct yahoo_data *yd = yid->yd; - char *from=NULL; - char *to=NULL; - char *msg=NULL; - char *url=NULL; - long expires=0; - - char *service=NULL; - - char *filename=NULL; - unsigned long filesize=0L; - - 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 == 14) - msg = pair->value; - if (pair->key == 20) - url = pair->value; - if (pair->key == 38) - expires = atol(pair->value); - - if (pair->key == 27) - filename = pair->value; - if (pair->key == 28) - filesize = atol(pair->value); - - if (pair->key == 49) - service = pair->value; - } - - if(pkt->service == YAHOO_SERVICE_P2PFILEXFER) { - if(strcmp("FILEXFER", service) != 0) { - WARNING(("unhandled service 0x%02x", pkt->service)); - yahoo_dump_unhandled(pkt); - return; - } - } - - if(msg) { - char *tmp; - tmp = strchr(msg, '\006'); - if(tmp) - *tmp = '\0'; - } - if(url && from) - YAHOO_CALLBACK(ext_yahoo_got_file)(yd->client_id, to, from, url, expires, msg, filename, filesize); - -} - -static void yahoo_process_conference(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_conference(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *msg = NULL; @@ -1011,98 +937,102 @@ static void yahoo_process_conference(struct yahoo_input_data *yid, struct yahoo_ char *who = NULL; char *room = NULL; char *id = NULL; - int utf8 = 0; + 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 */ + + if (pair->key == 52) { /* invite */ members = y_list_append(members, strdup(pair->value)); } - if (pair->key == 53) /* logon */ + if (pair->key == 53) /* logon */ who = pair->value; - if (pair->key == 54) /* decline */ + if (pair->key == 54) /* decline */ who = pair->value; - if (pair->key == 55) /* unavailable (status == 2) */ + if (pair->key == 55) /* unavailable (status == 2) */ who = pair->value; - if (pair->key == 56) /* logoff */ + if (pair->key == 56) /* logoff */ who = pair->value; if (pair->key == 57) room = pair->value; - if (pair->key == 58) /* join message */ + if (pair->key == 58) /* join message */ msg = pair->value; - if (pair->key == 14) /* decline/conf message */ + if (pair->key == 14) /* decline/conf message */ msg = pair->value; - if (pair->key == 13) - ; - if (pair->key == 16) /* error */ + if (pair->key == 13) ; + if (pair->key == 16) /* error */ msg = pair->value; - if (pair->key == 1) /* my id */ + if (pair->key == 1) /* my id */ id = pair->value; - if (pair->key == 3) /* message sender */ + if (pair->key == 3) /* message sender */ who = pair->value; if (pair->key == 97) utf8 = atoi(pair->value); } - if(!room) + if (!room) return; - if(host) { - for(l = members; l; l = l->next) { - char * w = l->data; - if(!strcmp(w, host)) + if (host) { + for (l = members; l; l = l->next) { + char *w = l->data; + if (!strcmp(w, host)) break; } - if(!l) + if (!l) members = y_list_append(members, strdup(host)); } /* invite, decline, join, left, message -> status == 1 */ - switch(pkt->service) { + 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); + 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 == 2) - ; - else - YAHOO_CALLBACK(ext_yahoo_got_conf_invite)(yd->client_id, id, host, room, msg, members); + 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); + 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); + 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); + 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); + 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) +static void yahoo_process_chat(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { char *msg = NULL; char *id = NULL; @@ -1111,13 +1041,13 @@ static void yahoo_process_chat(struct yahoo_input_data *yid, struct yahoo_packet 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; + 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; @@ -1147,7 +1077,8 @@ static void yahoo_process_chat(struct yahoo_input_data *yid, struct yahoo_packet who = pair->value; if (pkt->service == YAHOO_SERVICE_CHATJOIN) { - currentmember = y_new0(struct yahoo_chat_member, 1); + currentmember = + y_new0(struct yahoo_chat_member, 1); currentmember->id = strdup(pair->value); members = y_list_append(members, currentmember); } @@ -1177,7 +1108,6 @@ static void yahoo_process_chat(struct yahoo_input_data *yid, struct yahoo_packet currentmember->location = strdup(pair->value); } - if (pair->key == 130) { /* first join */ firstjoin = 1; @@ -1195,17 +1125,19 @@ static void yahoo_process_chat(struct yahoo_input_data *yid, struct yahoo_packet 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); + 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 (!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); + if (pkt->service == YAHOO_SERVICE_COMMENT && chaterr) { + YAHOO_CALLBACK(ext_yahoo_chat_yahooerror) (yid->yd-> + client_id, id); return; } @@ -1213,103 +1145,156 @@ static void yahoo_process_chat(struct yahoo_input_data *yid, struct yahoo_packet return; } - switch(pkt->service) { + switch (pkt->service) { case YAHOO_SERVICE_CHATJOIN: - if(y_list_length(members) != membercount) { + 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) { + 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) { + while (members) { YList *n = members->next; currentmember = members->data; - YAHOO_CALLBACK(ext_yahoo_chat_userjoin)(yid->yd->client_id, id, room, currentmember); + YAHOO_CALLBACK(ext_yahoo_chat_userjoin) (yid-> + yd->client_id, id, room, currentmember); y_list_free_1(members); - members=n; + members = n; } } break; case YAHOO_SERVICE_CHATEXIT: - if(who) { - YAHOO_CALLBACK(ext_yahoo_chat_userleave)(yid->yd->client_id, id, room, who); + 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); + 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) +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; + YList *messages = NULL; struct m { - int i_31; - int i_32; + int i_31; + int i_32; char *to; char *from; long tm; char *msg; - int utf8; + 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) + if (pair->key == 1 || pair->key == 4) { + if (!message->from) message->from = pair->value; - } - else if (pair->key == 5) + } 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); - /* user message */ /* sys message */ + /* 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) { + 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) + } 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)); + 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) { + 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->msg); + YAHOO_CALLBACK(ext_yahoo_system_message) (yd->client_id, + message->to, message->from, message->msg); } else if (pkt->status <= 2 || pkt->status == 5) { - YAHOO_CALLBACK(ext_yahoo_got_im)(yd->client_id, message->to, message->from, message->msg, message->tm, pkt->status, message->utf8); + /* 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, "<ding>")) + 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); + YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id, + message->msg, 0, E_SYSTEM); } - free(message); + 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) { @@ -1326,10 +1311,12 @@ static void yahoo_process_status(struct yahoo_input_data *yid, 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). */ + /* + * 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) { @@ -1593,17 +1580,20 @@ static void yahoo_process_list(struct yahoo_input_data *yid, YAHOO_CALLBACK(ext_yahoo_got_cookies) (yd->client_id); } -static void yahoo_process_verify(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_verify(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; - if(pkt->status != 0x01) { + 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, ""); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + YAHOO_LOGIN_LOCK, ""); return; } - pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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); @@ -1612,7 +1602,8 @@ static void yahoo_process_verify(struct yahoo_input_data *yid, struct yahoo_pack } -static void yahoo_process_picture_checksum( struct yahoo_input_data *yid, struct yahoo_packet *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; @@ -1620,30 +1611,30 @@ static void yahoo_process_picture_checksum( struct yahoo_input_data *yid, struct int checksum = 0; YList *l; - for(l = pkt->hash; l; l = l->next) - { + 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; + 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); + 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) +static void yahoo_process_picture(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *url = NULL; @@ -1652,607 +1643,108 @@ static void yahoo_process_picture(struct yahoo_input_data *yid, struct yahoo_pac int status = 0; int checksum = 0; YList *l; - - for(l = pkt->hash; l; l = l->next) - { + + for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; - switch(pair->key) - { + switch (pair->key) { case 1: - case 4: /* sender */ + case 4: /* sender */ from = pair->value; break; - case 5: /* we */ + case 5: /* we */ to = pair->value; break; - case 13: /* request / sending */ - status = atoi( pair->value ); + case 13: /* request / sending */ + status = atoi(pair->value); break; - case 20: /* url */ + case 20: /* url */ url = pair->value; break; case 192: /*checksum */ - checksum = atoi( pair->value ); + 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; + 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) +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 ) + 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); -} - -static void yahoo_process_auth_pre_0x0b(struct yahoo_input_data *yid, - const char *seed, const char *sn) -{ - struct yahoo_data *yd = yid->yd; - - /* So, Yahoo has stopped supporting its older clients in India, and - * undoubtedly will soon do so in the rest of the world. - * - * The new clients use this authentication method. I warn you in - * advance, it's bizzare, convoluted, inordinately complicated. - * It's also no more secure than crypt() was. The only purpose this - * scheme could serve is to prevent third part clients from connecting - * to their servers. - * - * Sorry, Yahoo. - */ - - struct yahoo_packet *pack; - - md5_byte_t result[16]; - md5_state_t ctx; - char *crypt_result; - unsigned char *password_hash = malloc(25); - unsigned char *crypt_hash = malloc(25); - unsigned char *hash_string_p = malloc(50 + strlen(sn)); - unsigned char *hash_string_c = malloc(50 + strlen(sn)); - - char checksum; - - int sv; - - unsigned char *result6 = malloc(25); - unsigned char *result96 = malloc(25); - - sv = seed[15]; - sv = (sv % 8) % 5; - - md5_init(&ctx); - md5_append(&ctx, (md5_byte_t *)yd->password, strlen(yd->password)); - md5_finish(&ctx, result); - to_y64(password_hash, result, 16); - - md5_init(&ctx); - crypt_result = yahoo_crypt(yd->password, "$1$_2S43d5f$"); - md5_append(&ctx, (md5_byte_t *)crypt_result, strlen(crypt_result)); - md5_finish(&ctx, result); - to_y64(crypt_hash, result, 16); - free(crypt_result); - - switch (sv) { - case 0: - checksum = seed[seed[7] % 16]; - snprintf((char *)hash_string_p, strlen(sn) + 50, - "%c%s%s%s", checksum, password_hash, yd->user, seed); - snprintf((char *)hash_string_c, strlen(sn) + 50, - "%c%s%s%s", checksum, crypt_hash, yd->user, seed); - break; - case 1: - checksum = seed[seed[9] % 16]; - snprintf((char *)hash_string_p, strlen(sn) + 50, - "%c%s%s%s", checksum, yd->user, seed, password_hash); - snprintf((char *)hash_string_c, strlen(sn) + 50, - "%c%s%s%s", checksum, yd->user, seed, crypt_hash); - break; - case 2: - checksum = seed[seed[15] % 16]; - snprintf((char *)hash_string_p, strlen(sn) + 50, - "%c%s%s%s", checksum, seed, password_hash, yd->user); - snprintf((char *)hash_string_c, strlen(sn) + 50, - "%c%s%s%s", checksum, seed, crypt_hash, yd->user); - break; - case 3: - checksum = seed[seed[1] % 16]; - snprintf((char *)hash_string_p, strlen(sn) + 50, - "%c%s%s%s", checksum, yd->user, password_hash, seed); - snprintf((char *)hash_string_c, strlen(sn) + 50, - "%c%s%s%s", checksum, yd->user, crypt_hash, seed); - break; - case 4: - checksum = seed[seed[3] % 16]; - snprintf((char *)hash_string_p, strlen(sn) + 50, - "%c%s%s%s", checksum, password_hash, seed, yd->user); - snprintf((char *)hash_string_c, strlen(sn) + 50, - "%c%s%s%s", checksum, crypt_hash, seed, yd->user); - break; - } - - md5_init(&ctx); - md5_append(&ctx, (md5_byte_t *)hash_string_p, strlen((char *)hash_string_p)); - md5_finish(&ctx, result); - to_y64(result6, result, 16); - - md5_init(&ctx); - md5_append(&ctx, (md5_byte_t *)hash_string_c, strlen((char *)hash_string_c)); - md5_finish(&ctx, result); - to_y64(result96, result, 16); - - pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->initial_status, yd->session_id); - yahoo_packet_hash(pack, 0, yd->user); - yahoo_packet_hash(pack, 6, (char *)result6); - yahoo_packet_hash(pack, 96, (char *)result96); - yahoo_packet_hash(pack, 1, yd->user); - - yahoo_send_packet(yid, pack, 0); - - FREE(result6); - FREE(result96); - FREE(password_hash); - FREE(crypt_hash); - FREE(hash_string_p); - FREE(hash_string_c); - - yahoo_packet_free(pack); - -} - -/* - * New auth protocol cracked by Cerulean Studios and sent in to Gaim - */ -static void yahoo_process_auth_0x0b(struct yahoo_input_data *yid, const char *seed, const char *sn) -{ - struct yahoo_packet *pack = NULL; - struct yahoo_data *yd = yid->yd; - - md5_byte_t result[16]; - md5_state_t ctx; - - sha1_state_t ctx1; - sha1_state_t ctx2; - - char *alphabet1 = "FBZDWAGHrJTLMNOPpRSKUVEXYChImkwQ"; - char *alphabet2 = "F0E1D2C3B4A59687abcdefghijklmnop"; - - char *challenge_lookup = "qzec2tb3um1olpar8whx4dfgijknsvy5"; - char *operand_lookup = "+|&%/*^-"; - char *delimit_lookup = ",;"; - - unsigned char *password_hash = malloc(25); - unsigned char *crypt_hash = malloc(25); - char *crypt_result = NULL; - unsigned char pass_hash_xor1[64]; - unsigned char pass_hash_xor2[64]; - unsigned char crypt_hash_xor1[64]; - unsigned char crypt_hash_xor2[64]; - unsigned char chal[7]; - char resp_6[100]; - char resp_96[100]; - - unsigned char digest1[20]; - unsigned char digest2[20]; - unsigned char magic_key_char[4]; - const unsigned char *magic_ptr; - - unsigned int magic[64]; - unsigned int magic_work=0; - - char comparison_src[20]; - - int x, j, i; - int cnt = 0; - int magic_cnt = 0; - int magic_len; - int depth =0, table =0; - - memset(&pass_hash_xor1, 0, 64); - memset(&pass_hash_xor2, 0, 64); - memset(&crypt_hash_xor1, 0, 64); - memset(&crypt_hash_xor2, 0, 64); - memset(&digest1, 0, 20); - memset(&digest2, 0, 20); - memset(&magic, 0, 64); - memset(&resp_6, 0, 100); - memset(&resp_96, 0, 100); - memset(&magic_key_char, 0, 4); - - /* - * Magic: Phase 1. Generate what seems to be a 30 - * byte value (could change if base64 - * ends up differently? I don't remember and I'm - * tired, so use a 64 byte buffer. - */ - - magic_ptr = (unsigned char *)seed; - - while (*magic_ptr != 0) { - char *loc; - - /* Ignore parentheses. */ - - if (*magic_ptr == '(' || *magic_ptr == ')') { - magic_ptr++; - continue; - } - - /* Characters and digits verify against - the challenge lookup. - */ - - if (isalpha(*magic_ptr) || isdigit(*magic_ptr)) { - loc = strchr(challenge_lookup, *magic_ptr); - if (!loc) { - /* This isn't good */ - continue; - } - - /* Get offset into lookup table and lsh 3. */ - - magic_work = loc - challenge_lookup; - magic_work <<= 3; - - magic_ptr++; - continue; - } else { - unsigned int local_store; - - loc = strchr(operand_lookup, *magic_ptr); - if (!loc) { - /* Also not good. */ - continue; - } - - local_store = loc - operand_lookup; - - /* Oops; how did this happen? */ - if (magic_cnt >= 64) - break; - - magic[magic_cnt++] = magic_work | local_store; - magic_ptr++; - continue; - } - } - - magic_len = magic_cnt; - magic_cnt = 0; - - /* Magic: Phase 2. Take generated magic value and - * sprinkle fairy dust on the values. */ - - for (magic_cnt = magic_len-2; magic_cnt >= 0; magic_cnt--) { - unsigned char byte1; - unsigned char byte2; - - /* Bad. Abort. - */ - if (magic_cnt >= magic_len) { - WARNING(("magic_cnt(%d) magic_len(%d)", magic_cnt, magic_len)) - break; - } - - byte1 = magic[magic_cnt]; - byte2 = magic[magic_cnt+1]; - - byte1 *= 0xcd; - byte1 ^= byte2; - - magic[magic_cnt+1] = byte1; - } - - /* Magic: Phase 3. This computes 20 bytes. The first 4 bytes are used as our magic - * key (and may be changed later); the next 16 bytes are an MD5 sum of the magic key - * plus 3 bytes. The 3 bytes are found by looping, and they represent the offsets - * into particular functions we'll later call to potentially alter the magic key. - * - * %-) - */ - - magic_cnt = 1; - x = 0; - - do { - unsigned int bl = 0; - unsigned int cl = magic[magic_cnt++]; - - if (magic_cnt >= magic_len) - break; - - if (cl > 0x7F) { - if (cl < 0xe0) - bl = cl = (cl & 0x1f) << 6; - else { - bl = magic[magic_cnt++]; - cl = (cl & 0x0f) << 6; - bl = ((bl & 0x3f) + cl) << 6; - } - - cl = magic[magic_cnt++]; - bl = (cl & 0x3f) + bl; - } else - bl = cl; - - comparison_src[x++] = (bl & 0xff00) >> 8; - comparison_src[x++] = bl & 0xff; - } while (x < 20); - - /* Dump magic key into a char for SHA1 action. */ - - - for(x = 0; x < 4; x++) - magic_key_char[x] = comparison_src[x]; - - /* Compute values for recursive function table! */ - memcpy( chal, magic_key_char, 4 ); - x = 1; - for( i = 0; i < 0xFFFF && x; i++ ) - { - for( j = 0; j < 5 && x; j++ ) - { - chal[4] = i; - chal[5] = i >> 8; - chal[6] = j; - md5_init( &ctx ); - md5_append( &ctx, chal, 7 ); - md5_finish( &ctx, result ); - if( memcmp( comparison_src + 4, result, 16 ) == 0 ) - { - depth = i; - table = j; - x = 0; - } - } - } - - /* Transform magic_key_char using transform table */ - x = magic_key_char[3] << 24 | magic_key_char[2] << 16 - | magic_key_char[1] << 8 | magic_key_char[0]; - x = yahoo_xfrm( table, depth, x ); - x = yahoo_xfrm( table, depth, x ); - magic_key_char[0] = x & 0xFF; - magic_key_char[1] = x >> 8 & 0xFF; - magic_key_char[2] = x >> 16 & 0xFF; - magic_key_char[3] = x >> 24 & 0xFF; - - /* Get password and crypt hashes as per usual. */ - md5_init(&ctx); - md5_append(&ctx, (md5_byte_t *)yd->password, strlen(yd->password)); - md5_finish(&ctx, result); - to_y64(password_hash, result, 16); - - md5_init(&ctx); - crypt_result = yahoo_crypt(yd->password, "$1$_2S43d5f$"); - md5_append(&ctx, (md5_byte_t *)crypt_result, strlen(crypt_result)); - md5_finish(&ctx, result); - to_y64(crypt_hash, result, 16); - free(crypt_result); - - /* Our first authentication response is based off - * of the password hash. */ - - for (x = 0; x < (int)strlen((char *)password_hash); x++) - pass_hash_xor1[cnt++] = password_hash[x] ^ 0x36; - - if (cnt < 64) - memset(&(pass_hash_xor1[cnt]), 0x36, 64-cnt); - - cnt = 0; - - for (x = 0; x < (int)strlen((char *)password_hash); x++) - pass_hash_xor2[cnt++] = password_hash[x] ^ 0x5c; - - if (cnt < 64) - memset(&(pass_hash_xor2[cnt]), 0x5c, 64-cnt); - - sha1_init(&ctx1); - sha1_init(&ctx2); - - /* The first context gets the password hash XORed - * with 0x36 plus a magic value - * which we previously extrapolated from our - * challenge. */ - - sha1_append(&ctx1, pass_hash_xor1, 64); - if (j >= 3 ) - ctx1.Length_Low = 0x1ff; - sha1_append(&ctx1, magic_key_char, 4); - sha1_finish(&ctx1, digest1); - - /* The second context gets the password hash XORed - * with 0x5c plus the SHA-1 digest - * of the first context. */ - - sha1_append(&ctx2, pass_hash_xor2, 64); - sha1_append(&ctx2, digest1, 20); - sha1_finish(&ctx2, digest2); - - /* Now that we have digest2, use it to fetch - * characters from an alphabet to construct - * our first authentication response. */ - - for (x = 0; x < 20; x += 2) { - unsigned int val = 0; - unsigned int lookup = 0; - char byte[6]; - - memset(&byte, 0, 6); - - /* First two bytes of digest stuffed - * together. - */ - - val = digest2[x]; - val <<= 8; - val += digest2[x+1]; + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; - lookup = (val >> 0x0b); - lookup &= 0x1f; - if (lookup >= strlen(alphabet1)) + switch (pair->key) { + case 5: /* we */ break; - sprintf(byte, "%c", alphabet1[lookup]); - strcat(resp_6, byte); - strcat(resp_6, "="); - - lookup = (val >> 0x06); - lookup &= 0x1f; - if (lookup >= strlen(alphabet2)) + case 20: /* url */ + url = pair->value; break; - sprintf(byte, "%c", alphabet2[lookup]); - strcat(resp_6, byte); - - lookup = (val >> 0x01); - lookup &= 0x1f; - if (lookup >= strlen(alphabet2)) + case 27: /* local filename */ break; - sprintf(byte, "%c", alphabet2[lookup]); - strcat(resp_6, byte); - - lookup = (val & 0x01); - if (lookup >= strlen(delimit_lookup)) + case 38: /* time */ break; - sprintf(byte, "%c", delimit_lookup[lookup]); - strcat(resp_6, byte); + } } - /* Our second authentication response is based off - * of the crypto hash. */ - - cnt = 0; - memset(&digest1, 0, 20); - memset(&digest2, 0, 20); - - for (x = 0; x < (int)strlen((char *)crypt_hash); x++) - crypt_hash_xor1[cnt++] = crypt_hash[x] ^ 0x36; - - if (cnt < 64) - memset(&(crypt_hash_xor1[cnt]), 0x36, 64-cnt); - - cnt = 0; - - for (x = 0; x < (int)strlen((char *)crypt_hash); x++) - crypt_hash_xor2[cnt++] = crypt_hash[x] ^ 0x5c; - - if (cnt < 64) - memset(&(crypt_hash_xor2[cnt]), 0x5c, 64-cnt); - - sha1_init(&ctx1); - sha1_init(&ctx2); - - /* The first context gets the password hash XORed - * with 0x36 plus a magic value - * which we previously extrapolated from our - * challenge. */ - - sha1_append(&ctx1, crypt_hash_xor1, 64); - if (j >= 3 ) - ctx1.Length_Low = 0x1ff; - sha1_append(&ctx1, magic_key_char, 4); - sha1_finish(&ctx1, digest1); - - /* The second context gets the password hash XORed - * with 0x5c plus the SHA-1 digest - * of the first context. */ - - sha1_append(&ctx2, crypt_hash_xor2, 64); - sha1_append(&ctx2, digest1, 20); - sha1_finish(&ctx2, digest2); - - /* Now that we have digest2, use it to fetch - * characters from an alphabet to construct - * our first authentication response. */ - - for (x = 0; x < 20; x += 2) { - unsigned int val = 0; - unsigned int lookup = 0; + YAHOO_CALLBACK(ext_yahoo_buddyicon_uploaded) (yd->client_id, url); +} - char byte[6]; +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; - memset(&byte, 0, 6); + char *host; - /* First two bytes of digest stuffed - * together. */ + 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); - val = digest2[x]; - val <<= 8; - val += digest2[x+1]; + yd->initial_status = initial; + yss = yd->server_settings; - lookup = (val >> 0x0b); - lookup &= 0x1f; - if (lookup >= strlen(alphabet1)) - break; - sprintf(byte, "%c", alphabet1[lookup]); - strcat(resp_96, byte); - strcat(resp_96, "="); + ccd = y_new0(struct connect_callback_data, 1); + ccd->yd = yd; - lookup = (val >> 0x06); - lookup &= 0x1f; - if (lookup >= strlen(alphabet2)) - break; - sprintf(byte, "%c", alphabet2[lookup]); - strcat(resp_96, byte); + host = yss->pager_host; - lookup = (val >> 0x01); - lookup &= 0x1f; - if (lookup >= strlen(alphabet2)) - break; - sprintf(byte, "%c", alphabet2[lookup]); - strcat(resp_96, byte); + if (!host) + host = yss->pager_host_list[0]; - lookup = (val & 0x01); - if (lookup >= strlen(delimit_lookup)) - break; - sprintf(byte, "%c", delimit_lookup[lookup]); - strcat(resp_96, byte); - } + tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->client_id, + host, yss->pager_port, yahoo_connected, ccd, 0); - pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->initial_status, yd->session_id); - yahoo_packet_hash(pack, 0, sn); - yahoo_packet_hash(pack, 6, resp_6); - yahoo_packet_hash(pack, 96, resp_96); - yahoo_packet_hash(pack, 1, sn); - yahoo_send_packet(yid, pack, 0); - yahoo_packet_free(pack); - - free(password_hash); - free(crypt_hash); + /* + * 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 @@ -2302,7 +1794,7 @@ static enum yahoo_status yahoo_https_status_parse(int code) } } -static void yahoo_process_auth_0x10(struct yahoo_input_data *yid, const char *seed, const char *sn) +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); @@ -2352,17 +1844,17 @@ static void yahoo_https_auth_token_finish(struct http_request *req) yd = yid->yd; if (req->status_code != 200) { - YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, 2000 + req->status_code, NULL); + 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); + 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); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3001, NULL); goto fail; } @@ -2408,19 +1900,19 @@ static void yahoo_https_auth_finish(struct http_request *req) unsigned char yhash[32]; if (req->status_code != 200) { - YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, 2000 + req->status_code, NULL); + 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); + 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); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3002, NULL); goto fail; } @@ -2453,52 +1945,53 @@ fail: g_free(had); } -static void yahoo_process_auth(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_auth(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { char *seed = NULL; - char *sn = 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; - if (pair->key == 94) + + switch (pair->key) { + case 94: seed = pair->value; - if (pair->key == 1) + break; + case 1: sn = pair->value; - if (pair->key == 13) + break; + case 13: m = atoi(pair->value); + break; + } l = l->next; } - if (!seed) + if (!seed) return; - switch (m) { - case 0: - yahoo_process_auth_pre_0x0b(yid, seed, sn); - break; - case 1: - yahoo_process_auth_0x0b(yid, seed, sn); - break; - case 2: - yahoo_process_auth_0x10(yid, seed, sn); - break; - default: - /* call error */ - WARNING(("unknown auth type %d", m)); - yahoo_process_auth_0x0b(yid, seed, sn); - break; + 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) +static void yahoo_process_auth_resp(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *login_id; char *handle; - char *url=NULL; - int login_status=0; + char *url = NULL; + int login_status = -1; YList *l; @@ -2514,13 +2007,15 @@ static void yahoo_process_auth_resp(struct yahoo_input_data *yid, struct yahoo_p login_status = atoi(pair->value); } - if(pkt->status == 0xffffffff) { - YAHOO_CALLBACK(ext_yahoo_login_response)(yd->client_id, login_status, url); - /* yahoo_logoff(yd->client_id);*/ + 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) +static void yahoo_process_mail(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *who = NULL; @@ -2546,12 +2041,46 @@ static void yahoo_process_mail(struct yahoo_input_data *yid, struct yahoo_packet 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); + 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_contact(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +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; @@ -2560,7 +2089,7 @@ static void yahoo_process_contact(struct yahoo_input_data *yid, struct yahoo_pac char *name = NULL; long tm = 0L; int state = YAHOO_STATUS_AVAILABLE; - int online = FALSE; + int online = 0; int away = 0; int idle = 0; int mobile = 0; @@ -2589,18 +2118,21 @@ static void yahoo_process_contact(struct yahoo_input_data *yid, struct yahoo_pac 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); + 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); + 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) +static void yahoo_process_buddyadd(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *who = NULL; @@ -2608,7 +2140,7 @@ static void yahoo_process_buddyadd(struct yahoo_input_data *yid, struct yahoo_pa int status = 0; char *me = NULL; - struct yahoo_buddy *bud=NULL; + struct yahoo_buddy *bud = NULL; YList *l; for (l = pkt->hash; l; l = l->next) { @@ -2623,51 +2155,30 @@ static void yahoo_process_buddyadd(struct yahoo_input_data *yid, struct yahoo_pa status = strtol(pair->value, NULL, 10); } - yahoo_dump_unhandled(pkt); - - if(!who) + if (!who) return; - if(!where) + if (!where) where = "Unknown"; - /* status: 0 == Successful, 1 == Error (does not exist), 2 == Already in list */ - if( status == 0 ) { - 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); - - /* Possibly called already, but at least the call above doesn't - seem to happen every time (not anytime I tried). */ - YAHOO_CALLBACK(ext_yahoo_contact_added)(yd->client_id, me, who, NULL); - } + bud = y_new0(struct yahoo_buddy, 1); + bud->id = strdup(who); + bud->group = strdup(where); + bud->real_name = NULL; -/* YAHOO_CALLBACK(ext_yahoo_status_changed)(yd->client_id, who, status, NULL, (status==YAHOO_STATUS_AVAILABLE?0:1)); */ -} + yd->buddies = y_list_append(yd->buddies, bud); -static void yahoo_process_contact_ymsg13(struct yahoo_input_data *yid, struct yahoo_packet *pkt) -{ - char* who=NULL; - char* me=NULL; - char* msg=NULL; - 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 - DEBUG_MSG(("unknown key: %d = %s", pair->key, pair->value)); + /* 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); } - - if(pkt->status==3) - YAHOO_CALLBACK(ext_yahoo_contact_auth_request)(yid->yd->client_id, me, who, msg); } -static void yahoo_process_buddydel(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_buddydel(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { struct yahoo_data *yd = yid->yd; char *who = NULL; @@ -2690,12 +2201,13 @@ static void yahoo_process_buddydel(struct yahoo_input_data *yid, struct yahoo_pa else if (pair->key == 66) unk_66 = strtol(pair->value, NULL, 10); else - DEBUG_MSG(("unknown key: %d = %s", pair->key, pair->value)); + DEBUG_MSG(("unknown key: %d = %s", pair->key, + pair->value)); } - if(!who || !where) + if (!who || !where) return; - + bud = y_new0(struct yahoo_buddy, 1); bud->id = strdup(who); bud->group = strdup(where); @@ -2706,7 +2218,7 @@ static void yahoo_process_buddydel(struct yahoo_input_data *yid, struct yahoo_pa FREE(bud->group); FREE(bud); - if(buddy) { + if (buddy) { bud = buddy->data; yd->buddies = y_list_remove_link(yd->buddies, buddy); y_list_free_1(buddy); @@ -2716,16 +2228,17 @@ static void yahoo_process_buddydel(struct yahoo_input_data *yid, struct yahoo_pa FREE(bud->real_name); FREE(bud); - bud=NULL; + bud = NULL; } } -static void yahoo_process_ignore(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_process_ignore(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { char *who = NULL; - int status = 0; + int status = 0; char *me = NULL; - int un_ignore = 0; + int un_ignore = 0; YList *l; for (l = pkt->hash; l; l = l->next) { @@ -2734,27 +2247,27 @@ static void yahoo_process_ignore(struct yahoo_input_data *yid, struct yahoo_pack who = pair->value; if (pair->key == 1) me = pair->value; - if (pair->key == 13) /* 1 == ignore, 2 == unignore */ + if (pair->key == 13) /* 1 == ignore, 2 == unignore */ un_ignore = strtol(pair->value, NULL, 10); - if (pair->key == 66) + if (pair->key == 66) status = strtol(pair->value, NULL, 10); } - /* * 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 + * 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) +static void yahoo_process_voicechat(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { char *who = NULL; char *me = NULL; @@ -2769,12 +2282,13 @@ static void yahoo_process_voicechat(struct yahoo_input_data *yid, struct yahoo_p if (pair->key == 5) me = pair->value; if (pair->key == 13) - voice_room=pair->value; - if (pair->key == 57) - room=pair->value; + voice_room = pair->value; + if (pair->key == 57) + room = pair->value; } - NOTICE(("got voice chat invite from %s in %s to identity %s", who, room, me)); + 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 @@ -2786,33 +2300,59 @@ static void yahoo_process_voicechat(struct yahoo_input_data *yid, struct yahoo_p */ } -static void yahoo_process_ping(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +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); + YAHOO_CALLBACK(ext_yahoo_got_ping) (yid->yd->client_id, errormsg); } -static void _yahoo_webcam_get_server_connected(int fd, int error, void *d) +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 magic_nr[] = { 0, 1, 0 }; unsigned char header_len = 8; unsigned int len = 0; unsigned int pos = 0; - if(error || fd <= 0) { + if (error || !fd) { FREE(who); FREE(yid); return; @@ -2820,7 +2360,7 @@ static void _yahoo_webcam_get_server_connected(int fd, int error, void *d) yid->fd = fd; inputs = y_list_prepend(inputs, yid); - + /* send initial packet */ if (who) data = strdup("<RVWCFG>"); @@ -2830,8 +2370,7 @@ static void _yahoo_webcam_get_server_connected(int fd, int error, void *d) FREE(data); /* send data */ - if (who) - { + if (who) { data = strdup("g="); data = y_string_append(data, who); data = y_string_append(data, "\r\n"); @@ -2850,10 +2389,13 @@ static void _yahoo_webcam_get_server_connected(int fd, int error, void *d) FREE(packet); FREE(data); - yid->read_tag=YAHOO_CALLBACK(ext_yahoo_add_handler)(yid->yd->client_id, fd, YAHOO_INPUT_READ, yid); + 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) +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; @@ -2861,34 +2403,36 @@ static void yahoo_webcam_get_server(struct yahoo_input_data *y, char *who, char 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->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); + 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) +static YList *webcam_queue = NULL; +static void yahoo_process_webcam_key(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { char *me = NULL; char *key = NULL; char *who = NULL; YList *l; - // yahoo_dump_unhandled(pkt); + yahoo_dump_unhandled(pkt); for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 5) me = pair->value; - if (pair->key == 61) - key=pair->value; + if (pair->key == 61) + key = pair->value; } l = webcam_queue; - if(!l) + if (!l) return; who = l->data; webcam_queue = y_list_remove_link(webcam_queue, webcam_queue); @@ -2897,12 +2441,11 @@ static void yahoo_process_webcam_key(struct yahoo_input_data *yid, struct yahoo_ FREE(who); } -static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_packet *pkt) +static void yahoo_packet_process(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) { DEBUG_MSG(("yahoo_packet_process: 0x%02x", pkt->service)); - yahoo_dump_unhandled(pkt); - switch (pkt->service) - { + switch (pkt->service) { case YAHOO_SERVICE_USERSTAT: case YAHOO_SERVICE_LOGON: case YAHOO_SERVICE_LOGOFF: @@ -2913,7 +2456,7 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack case YAHOO_SERVICE_IDACT: case YAHOO_SERVICE_IDDEACT: case YAHOO_SERVICE_Y6_STATUS_UPDATE: - case YAHOO_SERVICE_YMSG15_STATUS: + case YAHOO_SERVICE_Y8_STATUS: yahoo_process_status(yid, pkt); break; case YAHOO_SERVICE_NOTIFY: @@ -2927,7 +2470,9 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack case YAHOO_SERVICE_NEWMAIL: yahoo_process_mail(yid, pkt); break; - case YAHOO_SERVICE_REJECTCONTACT: + case YAHOO_SERVICE_Y7_AUTHORIZATION: + yahoo_process_new_contact(yid, pkt); + break; case YAHOO_SERVICE_NEWCONTACT: yahoo_process_contact(yid, pkt); break; @@ -2962,15 +2507,18 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack yahoo_process_chat(yid, pkt); break; case YAHOO_SERVICE_P2PFILEXFER: - case YAHOO_SERVICE_FILETRANSFER: + 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_CONTACT_YMSG13: - yahoo_process_contact_ymsg13(yid,pkt); - break; case YAHOO_SERVICE_REMBUDDY: yahoo_process_buddydel(yid, pkt); break; @@ -2986,6 +2534,9 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack 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: @@ -2999,6 +2550,7 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack 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); @@ -3011,9 +2563,10 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack break; case YAHOO_SERVICE_PICTURE_UPLOAD: yahoo_process_picture_upload(yid, pkt); - break; - case YAHOO_SERVICE_YMSG15_BUDDY_LIST: /* Buddy List */ + 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); @@ -3021,14 +2574,14 @@ static void yahoo_packet_process(struct yahoo_input_data *yid, struct yahoo_pack } } -static struct yahoo_packet * yahoo_getdata(struct yahoo_input_data * yid) +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) + if (!yd) return NULL; DEBUG_MSG(("rxlen is %d", yid->rxlen)); @@ -3037,13 +2590,13 @@ static struct yahoo_packet * yahoo_getdata(struct yahoo_input_data * yid) return NULL; } - pos += 4; /* YMSG */ + 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)); + 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")); @@ -3055,11 +2608,14 @@ static struct yahoo_packet * yahoo_getdata(struct yahoo_input_data * yid) 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; + 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; + pkt->status)); + pkt->id = yahoo_get32(yid->rxqueue + pos); + pos += 4; yd->session_id = pkt->id; @@ -3067,12 +2623,13 @@ static struct yahoo_packet * yahoo_getdata(struct yahoo_input_data * yid) 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); + 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)); + DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, + yid->rxqueue)); } else { DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); FREE(yid->rxqueue); @@ -3081,136 +2638,166 @@ static struct yahoo_packet * yahoo_getdata(struct yahoo_input_data * yid) return pkt; } -static void yahoo_yab_read(struct yab *yab, unsigned char *d, int len) +static struct yab *yahoo_yab_read(unsigned char *d, int len) { char *st, *en; char *data = (char *)d; - data[len]='\0'; + struct yab *yab = NULL; + + data[len] = '\0'; DEBUG_MSG(("Got yab: %s", data)); - st = en = strstr(data, "userid=\""); - if(st) { - st += strlen("userid=\""); - en = strchr(st, '"'); *en++ = '\0'; - yab->id = yahoo_xmldecode(st); + 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); } - st = strstr(en, "fname=\""); - if(st) { - st += strlen("fname=\""); - en = strchr(st, '"'); *en++ = '\0'; + 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, "lname=\""); - if(st) { - st += strlen("lname=\""); - en = strchr(st, '"'); *en++ = '\0'; + st = strstr(en, "ln=\""); + if (st) { + st += strlen("ln=\""); + en = strchr(st, '"'); + *en++ = '\0'; yab->lname = yahoo_xmldecode(st); } - st = strstr(en, "nname=\""); - if(st) { - st += strlen("nname=\""); - en = strchr(st, '"'); *en++ = '\0'; + st = strstr(en, "nn=\""); + if (st) { + st += strlen("nn=\""); + en = strchr(st, '"'); + *en++ = '\0'; yab->nname = yahoo_xmldecode(st); } - st = strstr(en, "email=\""); - if(st) { - st += strlen("email=\""); - en = strchr(st, '"'); *en++ = '\0'; - yab->email = 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) { + if (st) { st += strlen("hphone=\""); - en = strchr(st, '"'); *en++ = '\0'; + en = strchr(st, '"'); + *en++ = '\0'; yab->hphone = yahoo_xmldecode(st); } st = strstr(en, "wphone=\""); - if(st) { + if (st) { st += strlen("wphone=\""); - en = strchr(st, '"'); *en++ = '\0'; + en = strchr(st, '"'); + *en++ = '\0'; yab->wphone = yahoo_xmldecode(st); } st = strstr(en, "mphone=\""); - if(st) { + if (st) { st += strlen("mphone=\""); - en = strchr(st, '"'); *en++ = '\0'; + en = strchr(st, '"'); + *en++ = '\0'; yab->mphone = yahoo_xmldecode(st); } st = strstr(en, "dbid=\""); - if(st) { + if (st) { st += strlen("dbid=\""); - en = strchr(st, '"'); *en++ = '\0'; + en = strchr(st, '"'); + *en++ = '\0'; yab->dbid = atoi(st); } + + return yab; } -static struct yab * yahoo_getyab(struct yahoo_input_data *yid) +static struct yab *yahoo_getyab(struct yahoo_input_data *yid) { struct yab *yab = NULL; - int pos = 0, end=0; + int pos = 0, end = 0; struct yahoo_data *yd = yid->yd; - if(!yd) - return NULL; - - DEBUG_MSG(("rxlen is %d", yid->rxlen)); - - if(yid->rxlen <= strlen("<record")) + if (!yd) return NULL; - /* start with <record */ - while(pos < yid->rxlen-strlen("<record")+1 - && memcmp(yid->rxqueue + pos, "<record", strlen("<record"))) - pos++; - - if(pos >= yid->rxlen-1) - return NULL; - - end = pos+2; - /* end with /> */ - while(end < yid->rxlen-strlen("/>")+1 && memcmp(yid->rxqueue + end, "/>", strlen("/>"))) - end++; - - if(end >= yid->rxlen-1) - return NULL; - - yab = y_new0(struct yab, 1); - yahoo_yab_read(yab, 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); - } + do { + DEBUG_MSG(("rxlen is %d", yid->rxlen)); + + if (yid->rxlen <= strlen("<ct")) + return NULL; + + /* start with <ct */ + while (pos < yid->rxlen - strlen("<ct") + 1 + && memcmp(yid->rxqueue + pos, "<ct", strlen("<ct"))) + pos++; + + if (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; } -static char * yahoo_getwebcam_master(struct yahoo_input_data *yid) +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; + unsigned int pos = 0; + unsigned int len = 0; + unsigned int status = 0; + char *server = NULL; struct yahoo_data *yd = yid->yd; - if(!yid || !yd) + if (!yid || !yd) return NULL; DEBUG_MSG(("rxlen is %d", yid->rxlen)); @@ -3222,14 +2809,11 @@ static char * yahoo_getwebcam_master(struct yahoo_input_data *yid) /* 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); + if (status == 0) { + pos += 2; /* skip next 2 bytes */ + server = y_memdup(yid->rxqueue + pos, 16); pos += 16; - } - else if (status == 6) - { + } else if (status == 6) { YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd->client_id, yid->wcm->user, 4); } @@ -3238,11 +2822,12 @@ static char * yahoo_getwebcam_master(struct yahoo_input_data *yid) yid->rxlen -= len; DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue)); - if (yid->rxlen>0) { + 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)); + DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, + yid->rxqueue)); } else { DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); FREE(yid->rxqueue); @@ -3253,35 +2838,33 @@ static char * yahoo_getwebcam_master(struct yahoo_input_data *yid) 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; + 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; + int connect = 0; struct yahoo_data *yd = yid->yd; - if(!yd) + if (!yd) return -1; - if(!yid->wcm || !yid->wcd || !yid->rxlen) + 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->wcd->to_read) { + header_len = yid->rxqueue[pos++]; + yid->wcd->packet_type = 0; if (yid->rxlen < header_len) return 0; - if (header_len >= 8) - { + if (header_len >= 8) { reason = yid->rxqueue[pos++]; /* next 2 bytes should always be 05 00 */ pos += 2; @@ -3289,8 +2872,7 @@ static int yahoo_get_webcam_data(struct yahoo_input_data *yid) pos += 4; yid->wcd->to_read = yid->wcd->data_size; } - if (header_len >= 13) - { + if (header_len >= 13) { yid->wcd->packet_type = yid->rxqueue[pos++]; yid->wcd->timestamp = yahoo_get32(yid->rxqueue + pos); pos += 4; @@ -3302,7 +2884,8 @@ static int yahoo_get_webcam_data(struct yahoo_input_data *yid) begin = pos; pos += yid->wcd->to_read; - if (pos > yid->rxlen) pos = yid->rxlen; + 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) { @@ -3315,93 +2898,95 @@ static int yahoo_get_webcam_data(struct yahoo_input_data *yid) } DEBUG_MSG(("packet type %.2X, data length %d", yid->wcd->packet_type, - yid->wcd->data_size)); + 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); - } + 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); + 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 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: /* ?? */ + 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) { + 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)); + DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen, + yid->rxqueue)); } else { DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue)); FREE(yid->rxqueue); @@ -3414,60 +2999,64 @@ static int yahoo_get_webcam_data(struct yahoo_input_data *yid) return 0; } -int yahoo_write_ready(int id, int fd, void *data) +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=%d data=%p", id, fd, data)); - if(!yid || !yid->txqueues || !find_conn_by_id(id)) + 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) + if (len == -1 && errno == EAGAIN) return 1; - if(len <= 0) { + if (len <= 0) { int e = errno; DEBUG_MSG(("len == %d (<= 0)", len)); - while(yid->txqueues) { - YList *l=yid->txqueues; + 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); + yid->txqueues = + y_list_remove_link(yid->txqueues, + yid->txqueues); y_list_free_1(l); } - LOG(("yahoo_write_ready(%d, %d) len < 0", id, fd)); - YAHOO_CALLBACK(ext_yahoo_remove_handler)(id, yid->write_tag); + 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; + errno = e; return 0; } tx->len -= len; - if(tx->len > 0) { + 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; + YList *l = yid->txqueues; free(tx->queue); free(tx); - yid->txqueues = y_list_remove_link(yid->txqueues, yid->txqueues); + 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, %d) !yxqueues", id, fd)); - YAHOO_CALLBACK(ext_yahoo_remove_handler)(id, yid->write_tag); + 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; } } @@ -3475,17 +3064,18 @@ int yahoo_write_ready(int id, int fd, void *data) return 1; } -static void yahoo_process_pager_connection(struct yahoo_input_data *yid, int over) +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) + if (over) return; - while (find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER) - && (pkt = yahoo_getdata(yid)) != NULL) { + while (find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER) + && (pkt = yahoo_getdata(yid)) != NULL) { yahoo_packet_process(yid, pkt); @@ -3493,17 +3083,15 @@ static void yahoo_process_pager_connection(struct yahoo_input_data *yid, int ove } } -static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over) -{ -} - -static void yahoo_process_chatcat_connection(struct yahoo_input_data *yid, int over) +static void yahoo_process_chatcat_connection(struct yahoo_input_data *yid, + int over) { - if(over) + if (over) return; - if (strstr((char*)yid->rxqueue+(yid->rxlen-20), "</content>")) { - YAHOO_CALLBACK(ext_yahoo_chat_cat_xml)(yid->yd->client_id, (char*)yid->rxqueue); + if (strstr((char *)yid->rxqueue + (yid->rxlen - 20), "</content>")) { + YAHOO_CALLBACK(ext_yahoo_chat_cat_xml) (yid->yd->client_id, + (char *)yid->rxqueue); } } @@ -3512,111 +3100,143 @@ 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 changed = 0; int id = yd->client_id; + int yab_used = 0; + + LOG(("Got data for YAB")); - if(over) + if (over) return; - while(find_input_by_id_and_type(id, YAHOO_CONNECTION_YAB) - && (yab = yahoo_getyab(yid)) != NULL) { - if(!yab->id) + while (find_input_by_id_and_type(id, YAHOO_CONNECTION_YAB) + && (yab = yahoo_getyab(yid)) != NULL) { + if (!yab->id) continue; - changed=1; - for(buds = yd->buddies; buds; buds=buds->next) { - struct yahoo_buddy * bud = buds->data; - if(!strcmp(bud->id, yab->id)) { + + 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) { + 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 - ); + } 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) { + yab->fname, yab->lname); + } else if (yab->fname) { bud->real_name = strdup(yab->fname); } - break; /* for */ + 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); + if (changed) + YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id, + yd->buddies); } -static void yahoo_process_search_connection(struct yahoo_input_data *yid, int over) +static void yahoo_process_search_connection(struct yahoo_input_data *yid, + int over) { - struct yahoo_found_contact *yct=NULL; + 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); + 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) + if (!over || !pyid) return; - if(p && (p=strstr(p, "\r\n\r\n"))) { + if (p && (p = strstr(p, "\r\n\r\n"))) { p += 4; - for(k = 0; (p = strchr(p, 4)) && (k < 4); k++) { + 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; + 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) + if (p) p++; - k=0; - while(p && *p) { + k = 0; + while (p && *p) { cp = p; np = strchr(p, 4); - if(!np) + 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: - if(strcmp(cp, "5") != 0) - yct->location = cp; - k = 0; - break; + 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); + YAHOO_CALLBACK(ext_yahoo_got_search_result) (yid->yd->client_id, found, + start, total, contacts); - while(contacts) { + while (contacts) { YList *node = contacts; contacts = y_list_remove_link(contacts, node); free(node->data); @@ -3624,20 +3244,20 @@ static void yahoo_process_search_connection(struct yahoo_input_data *yid, int ov } } -static void _yahoo_webcam_connected(int fd, int error, void *d) +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 <= 0) { + 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; } @@ -3647,74 +3267,70 @@ static void _yahoo_webcam_connected(int fd, int error, void *d) LOG(("Connected")); /* send initial packet */ - switch (wcm->direction) - { - case YAHOO_WEBCAM_DOWNLOAD: - data = strdup("<REQIMG>"); - break; - case YAHOO_WEBCAM_UPLOAD: - data = strdup("<SNDIMG>"); - break; - default: - return; + switch (wcm->direction) { + case YAHOO_WEBCAM_DOWNLOAD: + data = strdup("<REQIMG>"); + break; + case YAHOO_WEBCAM_UPLOAD: + data = strdup("<SNDIMG>"); + 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; + 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; + 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) - { + if (wcm->direction == YAHOO_WEBCAM_UPLOAD) { memcpy(packet + pos, magic_nr, sizeof(magic_nr)); pos += sizeof(magic_nr); } @@ -3723,7 +3339,9 @@ static void _yahoo_webcam_connected(int fd, int error, void *d) FREE(packet); FREE(data); - yid->read_tag=YAHOO_CALLBACK(ext_yahoo_add_handler)(yid->yd->client_id, yid->fd, YAHOO_INPUT_READ, yid); + 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) @@ -3748,23 +3366,23 @@ static void yahoo_webcam_connect(struct yahoo_input_data *y) 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); + 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) +static void yahoo_process_webcam_master_connection(struct yahoo_input_data *yid, + int over) { - char* server; + char *server; struct yahoo_server_settings *yss; - if(over) + if (over) return; server = yahoo_getwebcam_master(yid); - if (server) - { + if (server) { yss = yid->yd->server_settings; yid->wcm->server = strdup(server); yid->wcm->port = yss->webcam_port; @@ -3777,72 +3395,74 @@ static void yahoo_process_webcam_master_connection(struct yahoo_input_data *yid, } } -static void yahoo_process_webcam_connection(struct yahoo_input_data *yid, int over) +static void yahoo_process_webcam_connection(struct yahoo_input_data *yid, + int over) { int id = yid->yd->client_id; - int fd = yid->fd; + void *fd = yid->fd; - if(over) + 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, - yahoo_process_yab_connection, - yahoo_process_webcam_master_connection, - yahoo_process_webcam_connection, - yahoo_process_chatcat_connection, - yahoo_process_search_connection, -}; + 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, + 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, int fd, void *data) +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=%d data=%p", id, fd, data)); - if(!yid) + LOG(("read callback: id=%d fd=%p data=%p", id, fd, data)); + if (!yid) return -2; - do { - len = read(fd, buf, sizeof(buf)); - } while(len == -1 && errno == EINTR); + 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 */ + 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_error)(yid->yd->client_id, "Connection closed by server", 1, E_CONNECTION); + 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_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) + if (len == 0) return 1; - errno=e; + errno = e; LOG(("read error: %s", strerror(errno))); return -1; } - yid->rxqueue = y_renew(unsigned char, yid->rxqueue, len + yid->rxlen); + 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); + yahoo_process_connection[yid->type] (yid, 0); return len; } @@ -3854,7 +3474,7 @@ int yahoo_init_with_attributes(const char *username, const char *password, ...) yd = y_new0(struct yahoo_data, 1); - if(!yd) + if (!yd) return 0; yd->user = strdup(username); @@ -3879,13 +3499,7 @@ int yahoo_init(const char *username, const char *password) return yahoo_init_with_attributes(username, password, NULL); } -struct connect_callback_data { - struct yahoo_data *yd; - int tag; - int i; -}; - -static void yahoo_connected(int fd, int error, void *data) +static void yahoo_connected(void *fd, int error, void *data) { struct connect_callback_data *ccd = data; struct yahoo_data *yd = ccd->yd; @@ -3893,114 +3507,112 @@ static void yahoo_connected(int fd, int error, void *data) struct yahoo_input_data *yid; struct yahoo_server_settings *yss = yd->server_settings; - if(error) { - if(fallback_ports[ccd->i]) { - int tag; - yss->pager_port = fallback_ports[ccd->i++]; - tag = YAHOO_CALLBACK(ext_yahoo_connect_async)(yd->client_id, yss->pager_host, - yss->pager_port, yahoo_connected, ccd); + if (error) { + int tag; + if (fallback_ports[ccd->i]) { + char *host = yss->pager_host; + + if (!host) + host = yss->pager_host_list[ccd->server_i]; - if(tag > 0) - ccd->tag=tag; + 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); + YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, + YAHOO_LOGIN_SOCK, NULL); } return; } FREE(ccd); - /* fd < 0 && error == 0 means connect was cancelled */ - if(fd < 0) + /* fd == NULL && error == 0 means connect was cancelled */ + if (!fd) return; - pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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 = y_new0(struct yahoo_input_data, 1); - yid->yd = yd; + yid = find_input_by_id_and_type(yd->client_id, YAHOO_CONNECTION_PAGER); yid->fd = fd; - inputs = y_list_prepend(inputs, yid); 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); + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, + yid->fd, YAHOO_INPUT_READ, yid); } -void yahoo_login(int id, int initial) +void *yahoo_get_fd(int id) { - struct yahoo_data *yd = find_conn_by_id(id); - struct connect_callback_data *ccd; - struct yahoo_server_settings *yss; - int tag; - - if(!yd) - return; - - yss = yd->server_settings; - - yd->initial_status = initial; - - ccd = y_new0(struct connect_callback_data, 1); - ccd->yd = yd; - tag = YAHOO_CALLBACK(ext_yahoo_connect_async)(yd->client_id, yss->pager_host, yss->pager_port, - yahoo_connected, ccd); - - /* - * 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); -} - - -int yahoo_get_fd(int id) -{ - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); - if(!yid) + struct yahoo_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + if (!yid) return 0; else return yid->fd; } -void yahoo_send_im(int id, const char *from, const char *who, const char *what, int utf8, int picture) +void yahoo_send_buzz(int id, const char *from, const char *who) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + yahoo_send_im(id, from, who, "<ding>", 1, 0); +} + +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) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE, yd->session_id); + 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)) + + 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, 1, from ? from : yd->user); yahoo_packet_hash(pkt, 5, who); yahoo_packet_hash(pkt, 14, what); - if(utf8) + 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); @@ -4008,17 +3620,19 @@ void yahoo_send_im(int id, const char *from, const char *who, const char *what, 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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; - if(!yid) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YAHOO_STATUS_NOTIFY, yd->session_id); + 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, 1, from ? from : yd->user); yahoo_packet_hash(pkt, 14, " "); yahoo_packet_hash(pkt, 13, typ ? "1" : "0"); yahoo_packet_hash(pkt, 49, "TYPING"); @@ -4030,22 +3644,25 @@ void yahoo_send_typing(int id, const char *from, const char *who, int typ) 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_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) + 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_VISIBILITY, YAHOO_STATUS_AVAILABLE, 0); + 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); @@ -4053,7 +3670,8 @@ void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away) return; } - pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE, yd->current_status, yd->session_id); + 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 : ""); @@ -4061,8 +3679,9 @@ void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away) yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); - if(old_status == YAHOO_STATUS_INVISIBLE) { - pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBILITY, YAHOO_STATUS_AVAILABLE, 0); + 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); @@ -4071,21 +3690,23 @@ void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away) void yahoo_logoff(int id) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + 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) + if (!yid) return; yd = yid->yd; LOG(("yahoo_logoff: current status: %d", yd->current_status)); - if(yd->current_status != -1 && 0) { + 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, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_LOGOFF, + YPACKET_STATUS_DEFAULT, yd->session_id); yd->current_status = -1; if (pkt) { @@ -4094,22 +3715,25 @@ void yahoo_logoff(int id) } } - do { +/* do { yahoo_input_close(yid); - } while((yid = find_input_by_id(id))); + } while((yid = find_input_by_id(id)));*/ + } void yahoo_get_list(int id) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + 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) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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); @@ -4117,139 +3741,136 @@ void yahoo_get_list(int id) } } -static void _yahoo_http_connected(int id, int fd, int error, void *data) +static void _yahoo_http_connected(int id, void *fd, int error, void *data) { struct yahoo_input_data *yid = data; - if(fd <= 0) { + 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); + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, + YAHOO_INPUT_READ, yid); } +/* 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[1024]; + char buff[2048]; - if(!yd) + if (!yd) return; yid = y_new0(struct yahoo_input_data, 1); yid->yd = yd; yid->type = YAHOO_CONNECTION_YAB; - snprintf(url, 1024, "http://insider.msg.yahoo.com/ycontent/?ab2=0"); + 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); + 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, - _yahoo_http_connected, 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); } -void yahoo_set_yab(int id, struct yab * yab) +/* 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 *temp; - int size = sizeof(url)-1; + char post[1024]; + int size = 0; - if(!yd) + if (!yd) return; yid = y_new0(struct yahoo_input_data, 1); yid->type = YAHOO_CONNECTION_YAB; yid->yd = yd; - strncpy(url, "http://insider.msg.yahoo.com/ycontent/?addab2=0", size); - - if(yab->dbid) { - /* change existing yab */ - char tmp[32]; - strncat(url, "&ee=1&ow=1&id=", size - strlen(url)); - snprintf(tmp, sizeof(tmp), "%d", yab->dbid); - strncat(url, tmp, size - strlen(url)); - } - - if(yab->fname) { - strncat(url, "&fn=", size - strlen(url)); - temp = yahoo_urlencode(yab->fname); - strncat(url, temp, size - strlen(url)); - free(temp); - } - if(yab->lname) { - strncat(url, "&ln=", size - strlen(url)); - temp = yahoo_urlencode(yab->lname); - strncat(url, temp, size - strlen(url)); - free(temp); - } - strncat(url, "&yid=", size - strlen(url)); - temp = yahoo_urlencode(yab->id); - strncat(url, temp, size - strlen(url)); - free(temp); - if(yab->nname) { - strncat(url, "&nn=", size - strlen(url)); - temp = yahoo_urlencode(yab->nname); - strncat(url, temp, size - strlen(url)); - free(temp); - } - if(yab->email) { - strncat(url, "&e=", size - strlen(url)); - temp = yahoo_urlencode(yab->email); - strncat(url, temp, size - strlen(url)); - free(temp); - } - if(yab->hphone) { - strncat(url, "&hp=", size - strlen(url)); - temp = yahoo_urlencode(yab->hphone); - strncat(url, temp, size - strlen(url)); - free(temp); - } - if(yab->wphone) { - strncat(url, "&wp=", size - strlen(url)); - temp = yahoo_urlencode(yab->wphone); - strncat(url, temp, size - strlen(url)); - free(temp); - } - if(yab->mphone) { - strncat(url, "&mp=", size - strlen(url)); - temp = yahoo_urlencode(yab->mphone); - strncat(url, temp, size - strlen(url)); - free(temp); - } - strncat(url, "&pp=0", size - strlen(url)); - - snprintf(buff, sizeof(buff), "Y=%s; T=%s", - yd->cookie_y, yd->cookie_t); + if(yab->yid) + size = snprintf(post, sizeof(post), "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<ab k=\"%s\" cc=\"%d\">" + "<ct id=\"%d\" e=\"1\" yi=\"%s\" nn=\"%s\" />" + "</ab>", yd->user, 9, yab->yid, /* Don't know why */ + yab->id, yab->nname?yab->nname:""); + else + size = snprintf(post, sizeof(post), "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<ab k=\"%s\" cc=\"%d\">" + "<ct a=\"1\" yi=\"%s\" nn=\"%s\" />" + "</ab>", 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_get(yid->yd->client_id, url, buff, - _yahoo_http_connected, 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) +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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; - if(!yid) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(active?YAHOO_SERVICE_IDACT:YAHOO_SERVICE_IDDEACT, - YAHOO_STATUS_AVAILABLE, yd->session_id); + 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); @@ -4259,15 +3880,17 @@ void yahoo_set_identity_status(int id, const char * identity, int active) void yahoo_refresh(int id) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + 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) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_USERSTAT, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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); @@ -4276,54 +3899,59 @@ void yahoo_refresh(int id) void yahoo_keepalive(int id) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); + 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) + struct yahoo_packet *pkt = NULL; + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YPACKET_STATUS_DEFAULT, + yd->session_id); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } -void yahoo_chat_keepalive (int id) +void yahoo_chat_keepalive(int id) { - struct yahoo_input_data *yid = find_input_by_id_and_type (id, YAHOO_CONNECTION_PAGER); + 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; + return; yd = yid->yd; - pkt = yahoo_packet_new (YAHOO_SERVICE_CHATPING, YAHOO_STATUS_AVAILABLE, yd->session_id); - yahoo_send_packet (yid, pkt, 0); - yahoo_packet_free (pkt); + pkt = yahoo_packet_new(YAHOO_SERVICE_CHATPING, YPACKET_STATUS_DEFAULT, + yd->session_id); + yahoo_send_packet(yid, pkt, 0); + yahoo_packet_free(pkt); } -void yahoo_add_buddy(int id, const char *who, const char *group, const char *msg) +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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - if(!yid) + 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" */ + 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, 14, ""); yahoo_packet_hash(pkt, 65, group); yahoo_packet_hash(pkt, 97, "1"); yahoo_packet_hash(pkt, 1, yd->user); @@ -4333,23 +3961,23 @@ void yahoo_add_buddy(int id, const char *who, const char *group, const char *msg 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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; - if(!yid) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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); @@ -4358,151 +3986,129 @@ void yahoo_remove_buddy(int id, const char *who, const char *group) yahoo_packet_free(pkt); } -void yahoo_accept_buddy_ymsg13(int id,const char* me,const char* who){ - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); - struct yahoo_data *yd; - - if(!yid) - return; - yd = yid->yd; - - struct yahoo_packet* pkt=NULL; - pkt= yahoo_packet_new(YAHOO_SERVICE_CONTACT_YMSG13,YAHOO_STATUS_AVAILABLE,0); - - yahoo_packet_hash(pkt,1,me ?: yd->user); - yahoo_packet_hash(pkt,5,who); - yahoo_packet_hash(pkt,13,"1"); - yahoo_packet_hash(pkt,334,"0"); - yahoo_send_packet(yid, pkt, 0); - yahoo_packet_free(pkt); -} - -void yahoo_reject_buddy_ymsg13(int id,const char* me,const char* who,const char* msg){ - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); - struct yahoo_data *yd; - - if(!yid) - return; - yd = yid->yd; - - struct yahoo_packet* pkt=NULL; - pkt= yahoo_packet_new(YAHOO_SERVICE_CONTACT_YMSG13,YAHOO_STATUS_AVAILABLE,0); - - yahoo_packet_hash(pkt,1,me ?: yd->user); - yahoo_packet_hash(pkt,5,who); -// yahoo_packet_hash(pkt,241,YAHOO_PROTO_VER); - yahoo_packet_hash(pkt,13,"2"); - yahoo_packet_hash(pkt,334,"0"); - yahoo_packet_hash(pkt,97,"1"); - yahoo_packet_hash(pkt,14,msg?:""); - - yahoo_send_packet(yid, pkt, 0); - yahoo_packet_free(pkt); - -} - -void yahoo_reject_buddy(int id, const char *who, const char *msg) +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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - if(!yid) + if (!yid) return; yd = yid->yd; if (!yd->logged_in) return; - pkt = yahoo_packet_new(YAHOO_SERVICE_REJECTCONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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, 7, who); - yahoo_packet_hash(pkt, 14, msg); + 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); } 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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - if(!yid) + if (!yid) return; yd = yid->yd; if (!yd->logged_in) return; - pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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_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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - if(!yid) + if (!yid) return; yd = yid->yd; if (!yd->logged_in) return; - pkt = yahoo_packet_new(YAHOO_SERVICE_STEALTH, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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, 31, unstealth ? "2" : "1"); yahoo_packet_hash(pkt, 13, "2"); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } -void yahoo_change_buddy_group(int id, const char *who, const char *old_group, const char *new_group) +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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; - if(!yid) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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, 65, new_group); - yahoo_packet_hash(pkt, 14, " "); - - yahoo_send_packet(yid, pkt, 0); - yahoo_packet_free(pkt); + 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"); - pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, yd->session_id); - yahoo_packet_hash(pkt, 1, yd->user); - yahoo_packet_hash(pkt, 7, who); - yahoo_packet_hash(pkt, 65, old_group); yahoo_send_packet(yid, pkt, 0); yahoo_packet_free(pkt); } 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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt = NULL; - if(!yid) + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_GROUPRENAME, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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); @@ -4511,24 +4117,27 @@ void yahoo_group_rename(int id, const char *old_group, const char *new_group) 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) +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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CONFADDINVITE, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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, 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) { + for (; members; members = members->next) { yahoo_packet_hash(pkt, 52, (char *)members->data); yahoo_packet_hash(pkt, 53, (char *)members->data); } @@ -4539,21 +4148,24 @@ void yahoo_conference_addinvite(int id, const char * from, const char *who, cons yahoo_packet_free(pkt); } -void yahoo_conference_invite(int id, const char * from, YList *who, const char *room, const char *msg) +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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CONFINVITE, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 50, yd->user); - for(; who; who = who->next) { + for (; who; who = who->next) { yahoo_packet_hash(pkt, 52, (char *)who->data); } yahoo_packet_hash(pkt, 57, room); @@ -4565,45 +4177,51 @@ void yahoo_conference_invite(int id, const char * from, YList *who, const char * yahoo_packet_free(pkt); } -void yahoo_conference_logon(int id, const char *from, YList *who, const char *room) +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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGON, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGON, YPACKET_STATUS_DEFAULT, + yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); - for(; who; who = who->next) { - yahoo_packet_hash(pkt, 3, (char *)who->data); - } + 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) +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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CONFDECLINE, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFDECLINE, + YPACKET_STATUS_DEFAULT, yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); - for(; who; who = who->next) { + 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); @@ -4612,22 +4230,26 @@ void yahoo_conference_decline(int id, const char * from, YList *who, const char yahoo_packet_free(pkt); } -void yahoo_conference_logoff(int id, const char * from, YList *who, const char *room) +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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGOFF, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGOFF, YPACKET_STATUS_DEFAULT, + yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); - for(; who; who = who->next) { + 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); @@ -4635,26 +4257,30 @@ void yahoo_conference_logoff(int id, const char * from, YList *who, const char * yahoo_packet_free(pkt); } -void yahoo_conference_message(int id, const char * from, YList *who, const char *room, const char *msg, int utf8) +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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CONFMSG, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CONFMSG, YPACKET_STATUS_DEFAULT, + yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); - for(; who; who = who->next) { + 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) + if (utf8) yahoo_packet_hash(pkt, 97, "1"); yahoo_send_packet(yid, pkt, 0); @@ -4669,7 +4295,7 @@ void yahoo_get_chatrooms(int id, int chatroomid) char url[1024]; char buff[1024]; - if(!yd) + if (!yd) return; yid = y_new0(struct yahoo_input_data, 1); @@ -4677,32 +4303,39 @@ void yahoo_get_chatrooms(int id, int chatroomid) yid->type = YAHOO_CONNECTION_CHATCAT; if (chatroomid == 0) { - snprintf(url, 1024, "http://insider.msg.yahoo.com/ycontent/?chatcat=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(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, _yahoo_http_connected, 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) +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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 109, yd->user); yahoo_packet_hash(pkt, 6, "abcde"); @@ -4710,41 +4343,44 @@ void yahoo_chat_logon(int id, const char *from, const char *room, const char *ro yahoo_packet_free(pkt); - pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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, 1, (from ? from : yd->user)); yahoo_packet_hash(pkt, 104, room); yahoo_packet_hash(pkt, 129, roomid); - yahoo_packet_hash(pkt, 62, "2"); /* ??? */ + 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) +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_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) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_COMMENT, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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, 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) + if (utf8) yahoo_packet_hash(pkt, 97, "1"); yahoo_send_packet(yid, pkt, 0); @@ -4752,21 +4388,22 @@ void yahoo_chat_message(int id, const char *from, const char *room, const char 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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_CHATLOGOUT, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_CHATLOGOUT, YPACKET_STATUS_DEFAULT, + yd->session_id); - yahoo_packet_hash(pkt, 1, (from?from:yd->user)); + yahoo_packet_hash(pkt, 1, (from ? from : yd->user)); yahoo_send_packet(yid, pkt, 0); @@ -4775,16 +4412,18 @@ void yahoo_chat_logoff(int id, const char *from) 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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - if( !yid ) + if (!yid) return; yd = yid->yd; - - pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YAHOO_STATUS_AVAILABLE, 0); + + 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"); @@ -4793,21 +4432,24 @@ void yahoo_buddyicon_request(int id, const char *who) yahoo_packet_free(pkt); } -void yahoo_send_picture_info(int id, const char *who, const char *url, int checksum) +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_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 ) + if (!yid) return; yd = yid->yd; snprintf(checksum_str, sizeof(checksum_str), "%d", checksum); - pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YAHOO_STATUS_AVAILABLE, 0); + 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); @@ -4821,19 +4463,21 @@ void yahoo_send_picture_info(int id, const char *who, const char *url, int check 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_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 ) + if (!yid) return; yd = yid->yd; snprintf(type_str, sizeof(type_str), "%d", type); - pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_UPDATE, YAHOO_STATUS_AVAILABLE, 0); + 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); @@ -4844,21 +4488,23 @@ void yahoo_send_picture_update(int id, const char *who, int type) 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_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 ) + if (!yid) return; yd = yid->yd; - + snprintf(checksum_str, sizeof(checksum_str), "%d", checksum); - pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_CHECKSUM, YAHOO_STATUS_AVAILABLE, 0); + pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_CHECKSUM, + YPACKET_STATUS_DEFAULT, 0); yahoo_packet_hash(pkt, 1, yd->user); - if( who != 0 ) + if (who != 0) yahoo_packet_hash(pkt, 5, who); yahoo_packet_hash(pkt, 192, checksum_str); yahoo_packet_hash(pkt, 212, "1"); @@ -4869,19 +4515,21 @@ void yahoo_send_picture_checksum(int id, const char *who, int checksum) void yahoo_webcam_close_feed(int id, const char *who) { - struct yahoo_input_data *yid = find_input_by_id_and_webcam_user(id, who); + struct yahoo_input_data *yid = + find_input_by_id_and_webcam_user(id, who); - if(yid) + 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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_data *yd; struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; /* @@ -4891,11 +4539,12 @@ void yahoo_webcam_get_feed(int id, const char *who) * 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); + webcam_queue = y_list_append(webcam_queue, who ? strdup(who) : NULL); yd = yid->yd; - pkt = yahoo_packet_new(YAHOO_SERVICE_WEBCAM, YAHOO_STATUS_AVAILABLE, yd->session_id); + pkt = yahoo_packet_new(YAHOO_SERVICE_WEBCAM, YPACKET_STATUS_DEFAULT, + yd->session_id); yahoo_packet_hash(pkt, 1, yd->user); if (who != NULL) @@ -4905,9 +4554,11 @@ void yahoo_webcam_get_feed(int id, const char *who) yahoo_packet_free(pkt); } -void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length, unsigned int timestamp) +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); + 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; @@ -4919,10 +4570,10 @@ void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length, packet[pos++] = header_len; packet[pos++] = 0; - packet[pos++] = 5; /* version byte?? */ + packet[pos++] = 5; /* version byte?? */ packet[pos++] = 0; pos += yahoo_put32(packet + pos, length); - packet[pos++] = 2; /* packet type, image */ + packet[pos++] = 2; /* packet type, image */ pos += yahoo_put32(packet + pos, timestamp); yahoo_add_to_send_queue(yid, packet, header_len); FREE(packet); @@ -4931,9 +4582,10 @@ void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length, yahoo_add_to_send_queue(yid, image, length); } -void yahoo_webcam_accept_viewer(int id, const char* who, int accept) +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); + 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; @@ -4944,17 +4596,17 @@ void yahoo_webcam_accept_viewer(int id, const char* who, int accept) return; data = strdup("u="); - data = y_string_append(data, (char*)who); + 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++] = 5; /* version byte?? */ packet[pos++] = 0; pos += yahoo_put32(packet + pos, len); - packet[pos++] = 0; /* packet type */ + packet[pos++] = 0; /* packet type */ pos += yahoo_put32(packet + pos, accept); memcpy(packet + pos, data, len); FREE(data); @@ -4964,13 +4616,15 @@ void yahoo_webcam_accept_viewer(int id, const char* who, int accept) 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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_packet *pkt; - - if(!yid) + + if (!yid) return; - pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YAHOO_STATUS_NOTIFY, yid->yd->session_id); + 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, " "); @@ -4982,7 +4636,8 @@ void yahoo_webcam_invite(int id, const char *who) 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) +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; @@ -4990,7 +4645,7 @@ static void yahoo_search_internal(int id, int t, const char *text, int g, int ar char buff[1024]; char *ctext, *p; - if(!yd) + if (!yd) return; yid = y_new0(struct yahoo_input_data, 1); @@ -4998,38 +4653,43 @@ static void yahoo_search_internal(int id, int t, const char *text, int g, int ar 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+ - */ + 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); + snprintf(buff, sizeof(buff), "&.sq=%%20&.tt=%d&.ss=%d", total, + startpos); ctext = strdup(text); - while((p = strchr(ctext, ' '))) + 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 : ""); + 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, _yahoo_http_connected, 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) +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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_search_state *yss; - if(!yid) + if (!yid) return; - if(!yid->ys) + if (!yid->ys) yid->ys = y_new0(struct yahoo_search_state, 1); yss = yid->ys; @@ -5047,273 +4707,708 @@ void yahoo_search(int id, enum yahoo_search_type t, const char *text, enum yahoo 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_input_data *yid = + find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER); struct yahoo_search_state *yss; - if(!yid || !yid->ys) + if (!yid || !yid->ys) return; yss = yid->ys; - if(start == -1) + 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); + 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 */ } +/* 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 { - struct yahoo_packet *pkt; + 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 *user_data; + void *data; }; -static void _yahoo_send_picture_connected(int id, int fd, int error, void *data) +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); +} + +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) +{ + 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 yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_FT); struct send_file_data *sfd = data; - struct yahoo_packet *pkt = sfd->pkt; - unsigned char buff[1024]; + struct yahoo_input_data *yid = sfd->yid; - if(fd <= 0) { - sfd->callback(id, fd, error, sfd->user_data); - FREE(sfd); - yahoo_packet_free(pkt); + if (!fd) { inputs = y_list_remove(inputs, yid); FREE(yid); return; } + sfd->callback(id, fd, error, sfd->data); + yid->fd = fd; - yahoo_send_packet(yid, pkt, 8); - yahoo_packet_free(pkt); + yid->read_tag = + YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd, + YAHOO_INPUT_READ, yid); +} - snprintf((char *)buff, sizeof(buff), "29"); - buff[2] = 0xc0; - buff[3] = 0x80; - - write(yid->fd, buff, 4); +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; - /* YAHOO_CALLBACK(ext_yahoo_add_handler)(nyd->fd, YAHOO_INPUT_READ); */ + struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1); - sfd->callback(id, fd, error, sfd->user_data); - FREE(sfd); - inputs = y_list_remove(inputs, yid); - /* - while(yahoo_tcp_readline(buff, sizeof(buff), nyd->fd) > 0) { - if(!strcmp(buff, "")) - break; + 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); } - */ - yahoo_input_close(yid); +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); } -void yahoo_send_picture(int id, const char *name, unsigned long size, - yahoo_get_fd_callback callback, void *data) +static void yahoo_file_transfer_accept(struct yahoo_input_data *yid, + struct send_file_data *sfd) { - struct yahoo_data *yd = find_conn_by_id(id); - struct yahoo_input_data *yid; - struct yahoo_server_settings *yss; - struct yahoo_packet *pkt = NULL; - char size_str[10]; - char expire_str[10]; - long content_length=0; - unsigned char buff[1024]; - char url[255]; + 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 *who = NULL; + char *filename = NULL; + char *id = NULL; + char *token = NULL; - if(!yd) - return; + 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 249: + break; + case 265: + id = pair->value; + break; + case 251: + token = pair->value; + break; + case 27: + filename = pair->value; + break; + } + } - yss = yd->server_settings; + sfd = yahoo_get_active_transfer(id); - yid = y_new0(struct yahoo_input_data, 1); - yid->yd = yd; - yid->type = YAHOO_CONNECTION_FT; + if (sfd) { + sfd->token = strdup(token); - pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_UPLOAD, YAHOO_STATUS_AVAILABLE, yd->session_id); + yahoo_file_transfer_upload(yid->yd, sfd); + } + else { + YAHOO_CALLBACK(ext_yahoo_file_transfer_done) + (yid->yd->client_id, YAHOO_FILE_TRANSFER_UNKNOWN, + sfd->data); - snprintf(size_str, sizeof(size_str), "%ld", size); - snprintf(expire_str, sizeof(expire_str), "%ld", (long)604800); + yahoo_remove_active_transfer(sfd); + } +} - yahoo_packet_hash(pkt, 0, yd->user); - yahoo_packet_hash(pkt, 1, yd->user); - yahoo_packet_hash(pkt, 14, ""); - yahoo_packet_hash(pkt, 27, name); - yahoo_packet_hash(pkt, 28, size_str); - yahoo_packet_hash(pkt, 38, expire_str); - +static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid, + struct yahoo_packet *pkt) +{ + YList *l; + char *who = NULL; + char *filename = NULL; + char *id = NULL; + char *token = NULL; + char *ip_addr = NULL; - content_length = YAHOO_PACKET_HDRLEN + yahoo_packet_length(pkt); + struct send_file_data *sfd; - snprintf(url, sizeof(url), "http://%s:%d/notifyft", - yss->filetransfer_host, yss->filetransfer_port); - snprintf((char *)buff, sizeof(buff), "Y=%s; T=%s", - yd->cookie_y, yd->cookie_t); - inputs = y_list_prepend(inputs, yid); + for (l = pkt->hash; l; l = l->next) { + struct yahoo_pair *pair = l->data; + switch (pair->key) { + case 1: + case 4: + who = pair->value; + 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 = pair->value; + break; + } + } - sfd = y_new0(struct send_file_data, 1); - sfd->pkt = pkt; - sfd->callback = callback; - sfd->user_data = data; - yahoo_http_post(yid->yd->client_id, url, (char *)buff, content_length+4+size, - _yahoo_send_picture_connected, sfd); + 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->data); + + yahoo_remove_active_transfer(sfd); + } } -static void _yahoo_send_file_connected(int id, int fd, int error, void *data) +static void yahoo_send_filetransferinfo(struct yahoo_data *yd, + struct send_file_data *sfd) { - struct yahoo_input_data *yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_FT); - struct send_file_data *sfd = data; - struct yahoo_packet *pkt = sfd->pkt; - unsigned char buff[1024]; + 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); - if(fd <= 0) { - sfd->callback(id, fd, error, sfd->user_data); - FREE(sfd); - yahoo_packet_free(pkt); - inputs = y_list_remove(inputs, yid); - FREE(yid); return; } - yid->fd = fd; - yahoo_send_packet(yid, pkt, 8); + 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); +} - snprintf((char *)buff, sizeof(buff), "29"); - buff[2] = 0xc0; - buff[3] = 0x80; - - write(yid->fd, buff, 4); +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; -/* YAHOO_CALLBACK(ext_yahoo_add_handler)(nyd->fd, YAHOO_INPUT_READ); */ + struct send_file_data *sfd; - sfd->callback(id, fd, error, sfd->user_data); - FREE(sfd); - inputs = y_list_remove(inputs, yid); - /* - while(yahoo_tcp_readline(buff, sizeof(buff), nyd->fd) > 0) { - if(!strcmp(buff, "")) + 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); - */ - yahoo_input_close(yid); + 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->data); + + yahoo_remove_active_transfer(sfd); + } + } } -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) +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_data *yd = find_conn_by_id(id); - struct yahoo_input_data *yid; - struct yahoo_server_settings *yss; struct yahoo_packet *pkt = NULL; char size_str[10]; - long content_length=0; - unsigned char buff[1024]; - char url[255]; + 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); - if(!yd) - return; + 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; - yss = yd->server_settings; + yahoo_add_active_transfer(sfd); - yid = y_new0(struct yahoo_input_data, 1); - yid->yd = yd; - yid->type = YAHOO_CONNECTION_FT; + if (!yd) + return; - pkt = yahoo_packet_new(YAHOO_SERVICE_FILETRANSFER, YAHOO_STATUS_AVAILABLE, yd->session_id); + 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, 0, yd->user); + yahoo_packet_hash(pkt, 1, yd->user); yahoo_packet_hash(pkt, 5, who); - yahoo_packet_hash(pkt, 14, msg); + 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"); - content_length = YAHOO_PACKET_HDRLEN + yahoo_packet_length(pkt); + yahoo_send_packet(yid, pkt, 0); - snprintf(url, sizeof(url), "http://%s:%d/notifyft", - yss->filetransfer_host, yss->filetransfer_port); - snprintf((char *)buff, sizeof(buff), "Y=%s; T=%s", - yd->cookie_y, yd->cookie_t); - inputs = y_list_prepend(inputs, yid); + yahoo_packet_free(pkt); +} - sfd = y_new0(struct send_file_data, 1); - sfd->pkt = pkt; - sfd->callback = callback; - sfd->user_data = data; - yahoo_http_post(yid->yd->client_id, url, (char *)buff, content_length+4+size, - _yahoo_send_file_connected, sfd); +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); +} + +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 */ enum yahoo_status yahoo_current_status(int id) { struct yahoo_data *yd = find_conn_by_id(id); - if(!yd) + if (!yd) return YAHOO_STATUS_OFFLINE; return yd->current_status; } -const YList * yahoo_get_buddylist(int id) +const YList *yahoo_get_buddylist(int id) { struct yahoo_data *yd = find_conn_by_id(id); - if(!yd) + if (!yd) return NULL; return yd->buddies; } -const YList * yahoo_get_ignorelist(int id) +const YList *yahoo_get_ignorelist(int id) { struct yahoo_data *yd = find_conn_by_id(id); - if(!yd) + if (!yd) return NULL; return yd->ignore; } -const YList * yahoo_get_identities(int id) +const YList *yahoo_get_identities(int id) { struct yahoo_data *yd = find_conn_by_id(id); - if(!yd) + if (!yd) return NULL; return yd->identities; } -const char * yahoo_get_cookie(int id, const char *which) +const char *yahoo_get_cookie(int id, const char *which) { struct yahoo_data *yd = find_conn_by_id(id); - if(!yd) + if (!yd) return NULL; - if(!strncasecmp(which, "y", 1)) + if (!strncasecmp(which, "y", 1)) return yd->cookie_y; - if(!strncasecmp(which, "t", 1)) + if (!strncasecmp(which, "b", 1)) + return yd->cookie_b; + if (!strncasecmp(which, "t", 1)) return yd->cookie_t; - if(!strncasecmp(which, "c", 1)) + if (!strncasecmp(which, "c", 1)) return yd->cookie_c; - if(!strncasecmp(which, "login", 5)) + if (!strncasecmp(which, "login", 5)) return yd->login_cookie; return NULL; } -void yahoo_get_url_handle(int id, const char *url, - yahoo_get_url_handle_callback callback, void *data) -{ - struct yahoo_data *yd = find_conn_by_id(id); - if(!yd) - return; - - yahoo_get_url_fd(id, url, yd, callback, data); -} - -const char * yahoo_get_profile_url( void ) +const char *yahoo_get_profile_url(void) { return profile_url; } - diff --git a/protocols/yahoo/yahoo.c b/protocols/yahoo/yahoo.c index e4d541d5..dfd2e70f 100644 --- a/protocols/yahoo/yahoo.c +++ b/protocols/yahoo/yahoo.c @@ -1,7 +1,7 @@ /* * libyahoo2 wrapper to BitlBee * - * Mostly Copyright 2004 Wilmer van der Gaast <wilmer@gaast.net> + * Mostly Copyright 2004-2010 Wilmer van der Gaast <wilmer@gaast.net> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -157,7 +157,7 @@ static void byahoo_logout( struct im_connection *ic ) GSList *l; while( ic->groupchats ) - imcb_chat_free( ic->groupchats ); + imcb_chat_free( ic->groupchats->data ); for( l = yd->buddygroups; l; l = l->next ) { @@ -270,8 +270,32 @@ static void byahoo_keepalive( struct im_connection *ic ) 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; - yahoo_add_buddy( yd->y2_id, who, group ? group : BYAHOO_DEFAULT_GROUP, NULL ); + 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 ) @@ -340,14 +364,14 @@ static void byahoo_auth_allow( struct im_connection *ic, const char *who ) { struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; - yahoo_accept_buddy_ymsg13( yd->y2_id, NULL, who ); + 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_reject_buddy_ymsg13( yd->y2_id, NULL, who, NULL ); + yahoo_confirm_buddy( yd->y2_id, who, 1, "" ); } void byahoo_initmodule( ) @@ -420,7 +444,7 @@ void byahoo_connect_callback( gpointer data, gint source, b_input_condition cond return; } - d->callback( d->fd, 0, d->data ); + d->callback( NULL + d->fd, 0, d->data ); g_free( d ); } @@ -440,7 +464,7 @@ gboolean byahoo_read_ready_callback( gpointer data, gint source, b_input_conditi /* WTF doesn't libyahoo clean this up? */ return FALSE; - yahoo_read_ready( d->id, d->fd, d->data ); + yahoo_read_ready( d->id, NULL + d->fd, d->data ); return TRUE; } @@ -457,7 +481,7 @@ gboolean byahoo_write_ready_callback( gpointer data, gint source, b_input_condit { struct byahoo_write_ready_data *d = data; - return yahoo_write_ready( d->id, d->fd, 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 ) @@ -486,7 +510,7 @@ void ext_yahoo_login_response( int id, int succ, const char *url ) else { char *errstr; - int allow_reconnect = TRUE; + int allow_reconnect = FALSE; yd->logged_in = FALSE; @@ -496,13 +520,15 @@ void ext_yahoo_login_response( int id, int succ, const char *url ) 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"; - allow_reconnect = FALSE; - } else if( succ == YAHOO_LOGIN_SOCK ) + { errstr = "Socket problem"; + allow_reconnect = TRUE; + } else errstr = "Unknown error"; @@ -605,17 +631,16 @@ void ext_yahoo_status_changed( int id, const char *who, int stat, const char *ms state_string = "Offline"; flags = 0; break; - case YAHOO_STATUS_NOTIFY: - state_string = "Notify"; - break; } imcb_buddy_status( ic, who, flags, state_string, msg ); - /* Not implemented yet... if( stat == YAHOO_STATUS_IDLE ) - imcb_buddy_times( ic, who, 0, away ); - */ + 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 ) @@ -631,15 +656,22 @@ void ext_yahoo_got_im( int id, const char *me, const char *who, const char *msg, } } -void ext_yahoo_got_file( int id, - const char *ignored, - const char *who, const char *url, long expires, const char *msg, const char *fname, unsigned long fesize ) +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 ); @@ -650,7 +682,7 @@ void ext_yahoo_typing_notify( int id, const char *ignored, const char *who, int imcb_buddy_typing( ic, (char*) who, 0 ); } -void ext_yahoo_system_message( int id, const char *msg ) +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 ); @@ -672,9 +704,10 @@ void ext_yahoo_error( int id, const char *err, int fatal, int num ) } /* TODO: Clear up the mess of inp and d structures */ -int ext_yahoo_add_handler( int id, int fd, yahoo_input_condition cond, void *data ) +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 = (int) fd_; if( cond == YAHOO_INPUT_READ ) { @@ -685,7 +718,7 @@ int ext_yahoo_add_handler( int id, int fd, yahoo_input_condition cond, void *dat d->data = data; inp->d = d; - d->tag = inp->h = b_input_add( fd, GAIM_INPUT_READ, (b_event_handler) byahoo_read_ready_callback, (gpointer) 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 ) { @@ -696,17 +729,17 @@ int ext_yahoo_add_handler( int id, int fd, yahoo_input_condition cond, void *dat d->data = data; inp->d = d; - d->tag = inp->h = b_input_add( fd, GAIM_INPUT_WRITE, (b_event_handler) byahoo_write_ready_callback, (gpointer) 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 ); + return -1; /* Panic... */ } byahoo_inputs = g_slist_append( byahoo_inputs, inp ); - return( inp->h ); + return inp->h; } void ext_yahoo_remove_handler( int id, int tag ) @@ -730,7 +763,7 @@ void ext_yahoo_remove_handler( int id, int tag ) b_event_remove( tag ); } -int ext_yahoo_connect_async( int id, const char *host, int port, yahoo_connect_callback callback, void *data ) +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; @@ -746,48 +779,41 @@ int ext_yahoo_connect_async( int id, const char *host, int port, yahoo_connect_c d->data = data; d->id = id; - return( fd ); + return fd; } -/* Because we don't want asynchronous connects in BitlBee, and because - libyahoo doesn't seem to use this one anyway, this one is now defunct. */ -int ext_yahoo_connect(const char *host, int port) +char *ext_yahoo_get_ip_addr( const char *domain ) { -#if 0 - struct sockaddr_in serv_addr; - static struct hostent *server; - static char last_host[256]; - int servfd; - char **p; + return NULL; +} - if(last_host[0] || g_strcasecmp(last_host, host)!=0) { - if(!(server = gethostbyname(host))) { - return -1; - } - strncpy(last_host, host, 255); - } +int ext_yahoo_write( void *fd, char *buf, int len ) +{ + return write( (int) fd, buf, len ); +} - if((servfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - return -1; - } +int ext_yahoo_read( void *fd, char *buf, int len ) +{ + return read( (int) fd, buf, len ); +} - for (p = server->h_addr_list; *p; p++) - { - memset(&serv_addr, 0, sizeof(serv_addr)); - serv_addr.sin_family = AF_INET; - memcpy(&serv_addr.sin_addr.s_addr, *p, server->h_length); - serv_addr.sin_port = htons(port); - - if(connect(servfd, (struct sockaddr *) &serv_addr, - sizeof(serv_addr)) == -1) { - return -1; - } else { - return servfd; - } - } +void ext_yahoo_close( void *fd ) +{ + close( (int) fd ); +} - closesocket(servfd); -#endif +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 ); +} + +/* Because we don't want asynchronous connects in BitlBee, and because + libyahoo doesn't seem to use this one anyway, this one is now defunct. */ +int ext_yahoo_connect(const char *host, int port) +{ return -1; } @@ -795,10 +821,14 @@ static void byahoo_accept_conf( void *data ) { struct byahoo_conf_invitation *inv = data; struct groupchat *b; + GSList *l; - for( b = inv->ic->groupchats; b; b = b->next ) + for( l = inv->ic->groupchats; l; l = l->next ) + { + b = l->data; if( b == inv->c ) break; + } if( b != NULL ) { @@ -864,9 +894,7 @@ void ext_yahoo_conf_userdecline( int id, const char *ignored, const char *who, c 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; - - for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next ); + struct groupchat *c = bee_chat_by_title( ic->bee, ic, room ); if( c ) imcb_chat_add_buddy( c, (char*) who ); @@ -876,9 +904,7 @@ void ext_yahoo_conf_userleave( int id, const char *ignored, const char *who, con { struct im_connection *ic = byahoo_get_ic_by_id( id ); - struct groupchat *c; - - for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next ); + struct groupchat *c = bee_chat_by_title( ic->bee, ic, room ); if( c ) imcb_chat_remove_buddy( c, (char*) who, "" ); @@ -888,9 +914,7 @@ void ext_yahoo_conf_message( int id, const char *ignored, const char *who, const { struct im_connection *ic = byahoo_get_ic_by_id( id ); char *m = byahoo_strip( msg ); - struct groupchat *c; - - for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next ); + struct groupchat *c = bee_chat_by_title( ic->bee, ic, room ); if( c ) imcb_chat_msg( c, (char*) who, (char*) m, 0, 0 ); @@ -901,7 +925,7 @@ 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, int fd ) +void ext_yahoo_chat_join( int id, const char *who, const char *room, const char *topic, YList *members, void *fd ) { } @@ -929,25 +953,18 @@ void ext_yahoo_chat_yahooerror( int id, const char *me ) { } -void ext_yahoo_contact_auth_request( 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, NULL ); -} - 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_add_buddy( ic, (char*) who, NULL ); + 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 ) +void ext_yahoo_game_notify( int id, const char *me, const char *who, int stat, const char *msg ) { } @@ -992,7 +1009,7 @@ void ext_yahoo_got_webcam_image( int id, const char * who, const unsigned char * { } -void ext_yahoo_got_ping( int id, const char *msg) +void ext_yahoo_got_ping( int id, const char *msg ) { } diff --git a/protocols/yahoo/yahoo2.h b/protocols/yahoo/yahoo2.h index 2184a321..589aaa5a 100644 --- a/protocols/yahoo/yahoo2.h +++ b/protocols/yahoo/yahoo2.h @@ -50,19 +50,18 @@ extern "C" { #include "yahoo2_types.h" -/* returns the socket descriptor for a given pager connection. shouldn't be needed */ -int yahoo_get_fd(int id); +/* 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 ); + 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 */ @@ -87,101 +86,129 @@ enum yahoo_log_level yahoo_get_log_level( void ); * * You should set at least local_host if you intend to use webcams */ -int yahoo_init_with_attributes(const char *username, const char *password, ...); + 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); - - + 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); + 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); + void yahoo_login(int id, int initial); + void yahoo_logoff(int id); /* reloads status of all buddies */ -void yahoo_refresh(int id); + void yahoo_refresh(int id); /* activates/deactivates an identity */ -void yahoo_set_identity_status(int id, const char * identity, int active); + 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); + void yahoo_get_list(int id); /* download buddy contact information from your yahoo addressbook */ -void yahoo_get_yab(int id); + 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); + 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_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); + 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_reject_buddy(int id, const char *who, const char *msg); -void yahoo_stealth_buddy(int id, const char *who, int unstealth); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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, + 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, + 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 @@ -189,40 +216,32 @@ void yahoo_search(int id, enum yahoo_search_type t, const char *text, enum yahoo * * where the above three are passed to ext_yahoo_got_search_result */ -void yahoo_search_again(int id, int start); - -/* returns a socket fd to a url for downloading a file. */ -void yahoo_get_url_handle(int id, const char *url, - yahoo_get_url_handle_callback callback, void *data); + 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, int fd, void *data); -int yahoo_write_ready(int id, int fd, void *data); + 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); + 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); + 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); + const char *yahoo_get_profile_url(void); -void yahoo_accept_buddy_ymsg13(int,const char*,const char*); -void yahoo_reject_buddy_ymsg13(int,const char*,const char*,const char*); + void yahoo_buddyicon_request(int id, const char *who); #include "yahoo_httplib.h" #ifdef __cplusplus } #endif - #endif diff --git a/protocols/yahoo/yahoo2_callbacks.h b/protocols/yahoo/yahoo2_callbacks.h index e2c8ea42..0dccf188 100644 --- a/protocols/yahoo/yahoo2_callbacks.h +++ b/protocols/yahoo/yahoo2_callbacks.h @@ -29,7 +29,6 @@ * declared in this file and defined in libyahoo2.c */ - #ifndef YAHOO2_CALLBACKS_H #define YAHOO2_CALLBACKS_H @@ -45,25 +44,26 @@ extern "C" { * Callback interface for libyahoo2 */ -typedef enum { - YAHOO_INPUT_READ = 1 << 0, - YAHOO_INPUT_WRITE = 1 << 1, - YAHOO_INPUT_EXCEPTION = 1 << 2 -} yahoo_input_condition; + 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 that has been connected, or -1 on error + * 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)(int fd, int error, void *callback_data); - + typedef void (*yahoo_connect_callback) (void *fd, int error, + void *callback_data); /* * The following functions need to be implemented in the client @@ -93,8 +93,8 @@ struct yahoo_callbacks { * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_login_response) (int id, int succ, + const char *url); /* * Name: ext_yahoo_got_buddies @@ -103,8 +103,7 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_login_response)(int id, int succ, const char * id - the id that identifies the server connection * buds - the buddy list */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddies)(int id, YList * buds); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddies) (int id, YList *buds); /* * Name: ext_yahoo_got_ignore @@ -113,8 +112,7 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddies)(int id, YList * buds); * id - the id that identifies the server connection * igns - the ignore list */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ignore)(int id, YList * igns); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ignore) (int id, YList *igns); /* * Name: ext_yahoo_got_identities @@ -123,8 +121,7 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ignore)(int id, YList * igns); * id - the id that identifies the server connection * ids - the identity list */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_identities)(int id, YList * ids); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_identities) (int id, YList *ids); /* * Name: ext_yahoo_got_cookies @@ -132,8 +129,7 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_identities)(int id, YList * ids); * Params: * id - the id that identifies the server connection */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_got_cookies)(int id); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_cookies) (int id); /* * Name: ext_yahoo_got_ping @@ -142,8 +138,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_cookies)(int id); * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ping) (int id, + const char *errormsg); /* * Name: ext_yahoo_status_changed @@ -158,8 +154,21 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_ping)(int id, const char *errormsg); * 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); + 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 @@ -176,8 +185,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_status_changed)(int id, const char *who, int * 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); - + 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 @@ -190,8 +199,9 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_im)(int id, const char *me, const char *w * 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); - + 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 @@ -203,8 +213,9 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_conf_invite)(int id, const char *me, cons * 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); - + 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 @@ -215,8 +226,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userdecline)(int id, const char *me, con * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userjoin) (int id, + const char *me, const char *who, const char *room); /* * Name: ext_yahoo_conf_userleave @@ -227,8 +238,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userjoin)(int id, const char *me, const * 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); - + 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 @@ -237,8 +248,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_userleave)(int id, const char *me, const * id - the id that identifies the server connection * xml - ? */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_cat_xml)(int id, const char *xml); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_cat_xml) (int id, + const char *xml); /* * Name: ext_yahoo_chat_join @@ -251,10 +262,10 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_cat_xml)(int id, const char *xml); * 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 socket where the connection is coming from (for tracking) + * 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, int fd); - + 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 @@ -265,8 +276,9 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_join)(int id, const char *me, const char * 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); - + 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 @@ -277,8 +289,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userjoin)(int id, const char *me, const * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userleave) (int id, + const char *me, const char *room, const char *who); /* * Name: ext_yahoo_chat_message @@ -293,8 +305,9 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_userleave)(int id, const char *me, const * 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); - + 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); /* * @@ -309,8 +322,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_message)(int id, const char *me, const c * Returns: * nothing. */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahoologout)(int id, const char *me); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahoologout) (int id, + const char *me); /* * @@ -326,8 +339,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahoologout)(int id, const char *me); * Returns: * nothing. */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahooerror)(int id, const char *me); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahooerror) (int id, + const char *me); /* * Name: ext_yahoo_conf_message @@ -340,8 +353,9 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_chat_yahooerror)(int id, const char *me); * 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); - + 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 @@ -350,26 +364,42 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_conf_message)(int id, const char *me, const c * id - the id that identifies the server connection * me - the identity the file was sent to * who - the user who sent the file - * url - the file url - * expires - the expiry date of the file on the server (timestamp) * 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 *url, long expires, const char *msg, const char *fname, unsigned long fesize); - + 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_contact_auth_request - * Called when a contact wants to add you to his/her contact list + * 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 - * myid - the identity s/he added - * who - who did it - * msg - any message sent + * in - The data + * len - Length of the data + * data - callback data */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_auth_request)(int id, const char *myid, const char *who, const char *msg); + 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 @@ -380,8 +410,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_auth_request)(int id, const char *myi * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_added) (int id, + const char *myid, const char *who, const char *msg); /* * Name: ext_yahoo_rejected @@ -391,8 +421,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_contact_added)(int id, const char *myid, cons * who - who rejected you * msg - any message sent */ -void YAHOO_CALLBACK_TYPE(ext_yahoo_rejected)(int id, const char *who, const char *msg); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_rejected) (int id, const char *who, + const char *msg); /* * Name: ext_yahoo_typing_notify @@ -403,8 +433,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_rejected)(int id, const char *who, const char * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_typing_notify) (int id, + const char *me, const char *who, int stat); /* * Name: ext_yahoo_game_notify @@ -414,9 +444,10 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_typing_notify)(int id, const char *me, const * 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); - + 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 @@ -427,17 +458,20 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_game_notify)(int id, const char *me, const ch * 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); - + 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 *msg); + void YAHOO_CALLBACK_TYPE(ext_yahoo_system_message) (int id, + const char *me, const char *who, const char *msg); /* * Name: ext_yahoo_got_buddyicon @@ -449,7 +483,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_system_message)(int id, const char *msg); * 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); + 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 @@ -460,7 +495,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon)(int id, const char *me, const * 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); + 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 @@ -470,7 +506,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_checksum)(int id, const char *m * 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); + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_request) (int id, + const char *me, const char *who); /* * Name: ext_yahoo_got_buddyicon_request @@ -479,7 +516,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_buddyicon_request)(int id, const char *me * 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); + void YAHOO_CALLBACK_TYPE(ext_yahoo_buddyicon_uploaded) (int id, + const char *url); /* * Name: ext_yahoo_got_webcam_image @@ -504,11 +542,11 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_buddyicon_uploaded)(int id, const char *url); * 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, + 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 @@ -517,8 +555,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_webcam_image)(int id, const char * who, * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite) (int id, + const char *me, const char *from); /* * Name: ext_yahoo_webcam_invite_reply @@ -529,8 +567,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite)(int id, const char *me, const * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite_reply) (int id, + const char *me, const char *from, int accept); /* * Name: ext_yahoo_webcam_closed @@ -544,8 +582,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_invite_reply)(int id, const char *me, * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_closed) (int id, + const char *who, int reason); /* * Name: ext_yahoo_got_search_result @@ -559,8 +597,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_closed)(int id, const char *who, int r * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_got_search_result) (int id, + int found, int start, int total, YList *contacts); /* * Name: ext_yahoo_error @@ -571,8 +609,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_got_search_result)(int id, int found, int sta * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_error) (int id, const char *err, + int fatal, int num); /* * Name: ext_yahoo_webcam_viewer @@ -582,8 +620,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_error)(int id, const char *err, int fatal, in * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_viewer) (int id, + const char *who, int connect); /* * Name: ext_yahoo_webcam_data_request @@ -592,8 +630,8 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_viewer)(int id, const char *who, int c * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_data_request) (int id, + int send); /* * Name: ext_yahoo_log @@ -603,8 +641,7 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_webcam_data_request)(int id, int send); * Returns: * 0 */ -int YAHOO_CALLBACK_TYPE(ext_yahoo_log)(const char *fmt, ...); - + int YAHOO_CALLBACK_TYPE(ext_yahoo_log) (const char *fmt, ...); /* * Name: ext_yahoo_add_handler @@ -613,14 +650,14 @@ int YAHOO_CALLBACK_TYPE(ext_yahoo_log)(const char *fmt, ...); * when a YAHOO_INPUT_WRITE fd is ready. * Params: * id - the id that identifies the server connection - * fd - the fd on which to listen + * 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, int fd, yahoo_input_condition cond, void *data); - + int YAHOO_CALLBACK_TYPE(ext_yahoo_add_handler) (int id, void *fd, + yahoo_input_condition cond, void *data); /* * Name: ext_yahoo_remove_handler @@ -629,8 +666,7 @@ int YAHOO_CALLBACK_TYPE(ext_yahoo_add_handler)(int id, int fd, yahoo_input_condi * 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); - + void YAHOO_CALLBACK_TYPE(ext_yahoo_remove_handler) (int id, int tag); /* * Name: ext_yahoo_connect @@ -641,12 +677,11 @@ void YAHOO_CALLBACK_TYPE(ext_yahoo_remove_handler)(int id, int tag); * Returns: * a unix file descriptor to the socket */ -int YAHOO_CALLBACK_TYPE(ext_yahoo_connect)(const char *host, int port); - + 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 + * 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 @@ -659,11 +694,74 @@ int YAHOO_CALLBACK_TYPE(ext_yahoo_connect)(const char *host, int port); * 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 unix file descriptor to the socket + * 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 */ -int YAHOO_CALLBACK_TYPE(ext_yahoo_connect_async)(int id, const char *host, int port, - yahoo_connect_callback callback, void *callback_data); + 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 }; @@ -672,7 +770,7 @@ int YAHOO_CALLBACK_TYPE(ext_yahoo_connect_async)(int id, const char *host, int p * if using a callback structure, call yahoo_register_callbacks * before doing anything else */ -void yahoo_register_callbacks(struct yahoo_callbacks * tyc); +void yahoo_register_callbacks(struct yahoo_callbacks *tyc); #undef YAHOO_CALLBACK_TYPE @@ -683,4 +781,3 @@ void yahoo_register_callbacks(struct yahoo_callbacks * tyc); #endif #endif - diff --git a/protocols/yahoo/yahoo2_types.h b/protocols/yahoo/yahoo2_types.h index f05acb3c..bbade5d8 100644 --- a/protocols/yahoo/yahoo2_types.h +++ b/protocols/yahoo/yahoo2_types.h @@ -28,74 +28,175 @@ extern "C" { #endif -enum yahoo_status { - YAHOO_STATUS_DISCONNECTED = -1, - 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_WEBLOGIN = 0x5a55aa55, - YAHOO_STATUS_OFFLINE = 0x5a55aa56, /* don't ask */ - YAHOO_STATUS_NOTIFY = 0x16 /* TYPING */ -}; -#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 = 2, - YAHOO_LOGIN_UNAME = 3, - YAHOO_LOGIN_PASSWD = 13, - YAHOO_LOGIN_LOCK = 14, - YAHOO_LOGIN_DUPL = 99, - YAHOO_LOGIN_SOCK = -1, -}; - -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 -}; - -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_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 @@ -120,171 +221,176 @@ enum yahoo_log_level { #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 -}; + 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 *login_cookie; - - 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 { - 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; -}; + 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, int fd, int error, void *data); + 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, int 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 */ -}; + 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 diff --git a/protocols/yahoo/yahoo_fn.h b/protocols/yahoo/yahoo_fn.h index 3f79f524..5400e5d0 100644 --- a/protocols/yahoo/yahoo_fn.h +++ b/protocols/yahoo/yahoo_fn.h @@ -18,16 +18,15 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#define IDENT 1 /* identify function */ -#define XOR 2 /* xor with arg1 */ -#define MULADD 3 /* multipy by arg1 then add arg2 */ -#define LOOKUP 4 /* lookup each byte in the table pointed to by arg1 */ -#define BITFLD 5 /* reorder bits according to table pointed to by arg1 */ +#define IDENT 1 /* identify function */ +#define XOR 2 /* xor with arg1 */ +#define MULADD 3 /* multipy by arg1 then add arg2 */ +#define LOOKUP 4 /* lookup each byte in the table pointed to by arg1 */ +#define BITFLD 5 /* reorder bits according to table pointed to by arg1 */ -struct yahoo_fn -{ - int type; +struct yahoo_fn { + int type; long arg1, arg2; }; -int yahoo_xfrm( int table, int depth, int seed ); +int yahoo_xfrm(int table, int depth, int seed); diff --git a/protocols/yahoo/yahoo_httplib.c b/protocols/yahoo/yahoo_httplib.c index 1b084992..6bb8923d 100644 --- a/protocols/yahoo/yahoo_httplib.c +++ b/protocols/yahoo/yahoo_httplib.c @@ -29,14 +29,13 @@ # define strchr index # define strrchr rindex # endif -char *strchr (), *strrchr (); +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 <errno.h> #ifndef _WIN32 #include <unistd.h> @@ -62,7 +61,7 @@ extern struct yahoo_callbacks *yc; extern enum yahoo_log_level log_level; -int yahoo_tcp_readline(char *ptr, int maxlen, int fd) +int yahoo_tcp_readline(char *ptr, int maxlen, void *fd) { int n, rc; char c; @@ -70,11 +69,11 @@ int yahoo_tcp_readline(char *ptr, int maxlen, int fd) for (n = 1; n < maxlen; n++) { do { - rc = read(fd, &c, 1); - } while(rc == -1 && (errno == EINTR || errno == EAGAIN)); /* this is bad - it should be done asynchronously */ + 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 */ + if (c == '\r') /* get rid of \r */ continue; *ptr = c; if (c == '\n') @@ -82,9 +81,9 @@ int yahoo_tcp_readline(char *ptr, int maxlen, int fd) ptr++; } else if (rc == 0) { if (n == 1) - return (0); /* EOF, no data */ + return (0); /* EOF, no data */ else - break; /* EOF, w/ data */ + break; /* EOF, w/ data */ } else { return -1; } @@ -95,12 +94,12 @@ int yahoo_tcp_readline(char *ptr, int maxlen, int fd) } static int url_to_host_port_path(const char *url, - char *host, int *port, char *path) + char *host, int *port, char *path, int *ssl) { - char *urlcopy=NULL; - char *slash=NULL; - char *colon=NULL; - + char *urlcopy = NULL; + char *slash = NULL; + char *colon = NULL; + /* * http://hostname * http://hostname/ @@ -110,10 +109,14 @@ static int url_to_host_port_path(const char *url, * 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); + 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; @@ -122,14 +125,17 @@ static int url_to_host_port_path(const char *url, slash = strchr(urlcopy, '/'); colon = strchr(urlcopy, ':'); - if(!colon || (slash && slash < colon)) { - *port = 80; + if (!colon || (slash && slash < colon)) { + if (*ssl) + *port = 443; + else + *port = 80; } else { *colon = 0; - *port = atoi(colon+1); + *port = atoi(colon + 1); } - if(!slash) { + if (!slash) { strcpy(path, "/"); } else { strcpy(path, slash); @@ -137,7 +143,7 @@ static int url_to_host_port_path(const char *url, } strcpy(host, urlcopy); - + FREE(urlcopy); return 1; @@ -145,135 +151,135 @@ static int url_to_host_port_path(const char *url, static int isurlchar(unsigned char c) { - return (isalnum(c) || '-' == c || '_' == c); + return (isalnum(c)); } char *yahoo_urlencode(const char *instr) { - int ipos=0, bpos=0; + int ipos = 0, bpos = 0; char *str = NULL; int len = strlen(instr); - if(!(str = y_new(char, 3*len + 1) )) - return ""; + if (!(str = y_new(char, 3 *len + 1))) + return ""; - while(instr[ipos]) { - while(isurlchar(instr[ipos])) + while (instr[ipos]) { + while (isurlchar(instr[ipos])) str[bpos++] = instr[ipos++]; - if(!instr[ipos]) + if (!instr[ipos]) break; - - snprintf(&str[bpos], 4, "%%%.2x", instr[ipos]); - bpos+=3; + + snprintf(&str[bpos], 4, "%%%02x", instr[ipos] & 0xff); + bpos += 3; ipos++; } - str[bpos]='\0'; + str[bpos] = '\0'; /* free extra alloc'ed mem. */ len = strlen(str); - str = y_renew(char, str, len+1); + str = y_renew(char, str, len + 1); return (str); } char *yahoo_urldecode(const char *instr) { - int ipos=0, bpos=0; + int ipos = 0, bpos = 0; char *str = NULL; - char entity[3]={0,0,0}; + char entity[3] = { 0, 0, 0 }; unsigned dec; int len = strlen(instr); - if(!(str = y_new(char, len+1) )) - return ""; + if (!(str = y_new(char, len + 1))) + return ""; - while(instr[ipos]) { - while(instr[ipos] && instr[ipos]!='%') - if(instr[ipos]=='+') { - str[bpos++]=' '; + while (instr[ipos]) { + while (instr[ipos] && instr[ipos] != '%') + if (instr[ipos] == '+') { + str[bpos++] = ' '; ipos++; } else str[bpos++] = instr[ipos++]; - if(!instr[ipos]) + if (!instr[ipos]) break; - - if(instr[ipos+1] && instr[ipos+2]) { + + if (instr[ipos + 1] && instr[ipos + 2]) { ipos++; - entity[0]=instr[ipos++]; - entity[1]=instr[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'; + str[bpos] = '\0'; /* free extra alloc'ed mem. */ len = strlen(str); - str = y_renew(char, str, len+1); + str = y_renew(char, str, len + 1); return (str); } char *yahoo_xmldecode(const char *instr) { - int ipos=0, bpos=0, epos=0; + int ipos = 0, bpos = 0, epos = 0; char *str = NULL; - char entity[4]={0,0,0,0}; - char *entitymap[5][2]={ - {"amp;", "&"}, + char entity[4] = { 0, 0, 0, 0 }; + char *entitymap[5][2] = { + {"amp;", "&"}, {"quot;", "\""}, - {"lt;", "<"}, - {"gt;", "<"}, + {"lt;", "<"}, + {"gt;", "<"}, {"nbsp;", " "} }; unsigned dec; int len = strlen(instr); - if(!(str = y_new(char, len+1) )) - return ""; + if (!(str = y_new(char, len + 1))) + return ""; - while(instr[ipos]) { - while(instr[ipos] && instr[ipos]!='&') - if(instr[ipos]=='+') { - str[bpos++]=' '; + 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]) + if (!instr[ipos] || !instr[ipos + 1]) break; ipos++; - if(instr[ipos] == '#') { + if (instr[ipos] == '#') { ipos++; - epos=0; - while(instr[ipos] != ';') - entity[epos++]=instr[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]; + 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'; + str[bpos] = '\0'; /* free extra alloc'ed mem. */ len = strlen(str); - str = y_renew(char, str, len+1); + str = y_renew(char, str, len + 1); return (str); } -typedef void (*http_connected)(int id, int fd, int error); +typedef void (*http_connected) (int id, void *fd, int error); struct callback_data { int id; @@ -282,150 +288,117 @@ struct callback_data { void *user_data; }; -static void connect_complete(int fd, int error, void *data) +static void connect_complete(void *fd, int error, void *data) { struct callback_data *ccd = data; - if(error == 0 && fd > 0) - write(fd, ccd->request, strlen(ccd->request)); - FREE(ccd->request); + 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) +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); + 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); + + 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) +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]; - - if(!url_to_host_port_path(url, host, &port, path)) - return; + int ssl = 0; - snprintf(buff, sizeof(buff), - "POST %s HTTP/1.0\r\n" - "Content-length: %ld\r\n" - "User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n" - "Host: %s:%d\r\n" - "Cookie: %s\r\n" - "\r\n", - path, content_length, - host, port, - cookies); + if (!url_to_host_port_path(url, host, &port, path, &ssl)) + return; - yahoo_send_http_request(id, host, port, buff, callback, data); + /* 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, - 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) { char host[255]; int port = 80; char path[255]; - char buff[1024]; - - if(!url_to_host_port_path(url, host, &port, path)) - return; + char buff[2048]; + char cookiebuff[1024]; + int ssl = 0; - snprintf(buff, sizeof(buff), - "GET %s HTTP/1.0\r\n" - "Host: %s:%d\r\n" - "User-Agent: Mozilla/4.5 [en] (" PACKAGE "/" VERSION ")\r\n" - "Cookie: %s\r\n" - "\r\n", - path, host, port, cookies); + if (!url_to_host_port_path(url, host, &port, path, &ssl)) + return; - yahoo_send_http_request(id, host, port, buff, callback, data); + /* 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); } -struct url_data { - yahoo_get_url_handle_callback callback; - void *user_data; -}; - -static void yahoo_got_url_fd(int id, int fd, int error, void *data) +void yahoo_http_head(int id, const char *url, const char *cookies, int len, + char *payload, yahoo_get_fd_callback callback, void *data) { - char *tmp=NULL; - char buff[1024]; - unsigned long filesize=0; - char *filename=NULL; - int n; - - struct url_data *ud = data; + char host[255]; + int port = 80; + char path[255]; + char buff[2048]; + char cookiebuff[1024]; + int ssl = 0; - if(error || fd < 0) { - ud->callback(id, fd, error, filename, filesize, ud->user_data); - FREE(ud); + if (!url_to_host_port_path(url, host, &port, path, &ssl)) return; - } - - while((n=yahoo_tcp_readline(buff, sizeof(buff), fd)) > 0) { - LOG(("Read:%s:\n", buff)); - if(!strcmp(buff, "")) - break; - - if( !strncasecmp(buff, "Content-length:", - strlen("Content-length:")) ) { - tmp = strrchr(buff, ' '); - if(tmp) - filesize = atol(tmp); - } - - if( !strncasecmp(buff, "Content-disposition:", - strlen("Content-disposition:")) ) { - tmp = strstr(buff, "name="); - if(tmp) { - tmp+=strlen("name="); - if(tmp[0] == '"') { - char *tmp2; - tmp++; - tmp2 = strchr(tmp, '"'); - if(tmp2) - *tmp2 = '\0'; - } else { - char *tmp2; - tmp2 = strchr(tmp, ';'); - if(!tmp2) - tmp2 = strchr(tmp, '\r'); - if(!tmp2) - tmp2 = strchr(tmp, '\n'); - if(tmp2) - *tmp2 = '\0'; - } - filename = strdup(tmp); - } - } - } - - LOG(("n == %d\n", n)); - LOG(("Calling callback, filename:%s, size: %ld\n", filename, filesize)); - ud->callback(id, fd, error, filename, filesize, ud->user_data); - FREE(ud); - FREE(filename); -} - -void yahoo_get_url_fd(int id, const char *url, const struct yahoo_data *yd, - yahoo_get_url_handle_callback callback, void *data) -{ - char buff[1024]; - struct url_data *ud = y_new0(struct url_data, 1); - snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t); - ud->callback = callback; - ud->user_data = data; - yahoo_http_get(id, url, buff, yahoo_got_url_fd, ud); + /* 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); } diff --git a/protocols/yahoo/yahoo_httplib.h b/protocols/yahoo/yahoo_httplib.h index fd28ad48..ab699b20 100644 --- a/protocols/yahoo/yahoo_httplib.h +++ b/protocols/yahoo/yahoo_httplib.h @@ -28,21 +28,21 @@ extern "C" { #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, int 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, - yahoo_get_fd_callback callback, void *data); -void yahoo_get_url_fd(int id, const char *url, const struct yahoo_data *yd, - yahoo_get_url_handle_callback callback, void *data); - + 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 diff --git a/protocols/yahoo/yahoo_list.h b/protocols/yahoo/yahoo_list.h index 0d335acd..c2e5ad18 100644 --- a/protocols/yahoo/yahoo_list.h +++ b/protocols/yahoo/yahoo_list.h @@ -23,7 +23,7 @@ #ifndef __YLIST_H__ #define __YLIST_H__ -/* GLib has linked list already, so I don't see why libyahoo2 has to copy this... */ +/* BitlBee already uses GLib so use it. */ typedef GList YList; diff --git a/protocols/yahoo/yahoo_util.c b/protocols/yahoo/yahoo_util.c index 5375205f..33a12674 100644 --- a/protocols/yahoo/yahoo_util.c +++ b/protocols/yahoo/yahoo_util.c @@ -35,12 +35,12 @@ char *strchr (), *strrchr (); #include "yahoo_util.h" -char * y_string_append(char * string, char * append) +char *y_string_append(char *string, char *append) { int size = strlen(string) + strlen(append) + 1; - char * new_string = y_renew(char, string, size); + char *new_string = y_renew(char, string, size); - if(new_string == NULL) { + if (new_string == NULL) { new_string = y_new(char, size); strcpy(new_string, string); FREE(string); diff --git a/protocols/yahoo/yahoo_util.h b/protocols/yahoo/yahoo_util.h index 0046fe16..8cb721c1 100644 --- a/protocols/yahoo/yahoo_util.h +++ b/protocols/yahoo/yahoo_util.h @@ -60,17 +60,19 @@ # 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); +void *y_memdup(const void *addr, int n); +char **y_strsplit(char *str, char *sep, int nelem); +void y_strfreev(char **vector); -int strncasecmp(const char * s1, const char * s2, size_t n); -int strcasecmp(const char * s1, const char * s2); +#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); +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 @@ -94,9 +96,9 @@ int vsnprintf(char *str, size_t size, const char *format, va_list ap); * 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); +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 @@ -30,7 +30,8 @@ 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, void *data ) + query_callback yes, query_callback no, query_callback free, + void *data ) { query_t *q = g_new0( query_t, 1 ); @@ -38,6 +39,7 @@ query_t *query_add( irc_t *irc, struct im_connection *ic, char *question, q->question = g_strdup( question ); q->yes = yes; q->no = no; + q->free = free; q->data = data; if( strchr( irc->umode, 'b' ) != NULL ) @@ -63,7 +65,7 @@ query_t *query_add( irc_t *irc, struct im_connection *ic, char *question, irc->queries = q; } - if( g_strcasecmp( set_getstr( &irc->set, "query_order" ), "lifo" ) == 0 || irc->queries == q ) + if( g_strcasecmp( set_getstr( &irc->b->set, "query_order" ), "lifo" ) == 0 || irc->queries == q ) query_display( irc, q ); return( q ); @@ -93,7 +95,8 @@ void query_del( irc_t *irc, query_t *q ) } g_free( q->question ); - if( q->data ) g_free( q->data ); /* Memory leak... */ + if( q->free && q->data ) + q->free( q->data ); g_free( q ); } @@ -178,7 +181,7 @@ static query_t *query_default( irc_t *irc ) { query_t *q; - if( g_strcasecmp( set_getstr( &irc->set, "query_order" ), "fifo" ) == 0 ) + 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 ); @@ -32,13 +32,14 @@ typedef struct query { struct im_connection *ic; char *question; - query_callback yes, no; + 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, void *data ); + 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 ); diff --git a/root_commands.c b/root_commands.c index e4e07605..69aa3e98 100644 --- a/root_commands.c +++ b/root_commands.c @@ -25,57 +25,13 @@ #define BITLBEE_CORE #include "commands.h" -#include "crypting.h" #include "bitlbee.h" #include "help.h" -#include "chat.h" +#include "ipc.h" -#include <string.h> - -void root_command_string( irc_t *irc, user_t *u, char *command, int flags ) +void root_command_string( irc_t *irc, char *command ) { - char *cmd[IRC_MAX_ARGS]; - char *s; - int k; - char q = 0; - - memset( cmd, 0, sizeof( cmd ) ); - cmd[0] = command; - k = 1; - for( s = command; *s && k < ( IRC_MAX_ARGS - 1 ); 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; - } - cmd[k] = NULL; - - root_command( irc, cmd ); + root_command( irc, split_command_parts( command ) ); } #define MIN_ARGS( x, y... ) \ @@ -92,14 +48,20 @@ void root_command_string( irc_t *irc, user_t *u, char *command, int flags ) void root_command( irc_t *irc, char *cmd[] ) { - int i; + int i, len; if( !cmd[0] ) return; + len = strlen( cmd[0] ); for( i = 0; commands[i].command; i++ ) - if( g_strcasecmp( commands[i].command, cmd[0] ) == 0 ) + if( g_strncasecmp( commands[i].command, cmd[0], len ) == 0 ) { + if( commands[i+1].command && + g_strncasecmp( commands[i+1].command, cmd[0], len ) == 0 ) + /* Only match on the first letters if the match is unique. */ + break; + MIN_ARGS( commands[i].required_parameters ); commands[i].execute( irc, cmd ); @@ -140,15 +102,45 @@ static void cmd_account( irc_t *irc, char **cmd ); static void cmd_identify( irc_t *irc, char **cmd ) { - storage_status_t status = storage_load( irc, cmd[1] ); - char *account_on[] = { "account", "on", NULL }; + storage_status_t status; + gboolean load = TRUE; + char *password = cmd[1]; - if( strchr( irc->umode, 'R' ) != NULL ) + if( irc->status & USTATUS_IDENTIFIED ) { irc_usermsg( irc, "You're already logged in." ); return; } + if( strncmp( cmd[1], "-no", 3 ) == 0 ) + { + load = FALSE; + password = cmd[2]; + } + else if( strncmp( cmd[1], "-force", 6 ) == 0 ) + { + password = cmd[2]; + } + else if( irc->b->accounts != NULL ) + { + irc_usermsg( 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 ) + { + MIN_ARGS( 2 ); + } + + if( load ) + status = storage_load( irc, password ); + else + status = storage_check_pass( irc->user->nick, password ); + switch (status) { case STORAGE_INVALID_PASSWORD: irc_usermsg( irc, "Incorrect password" ); @@ -157,12 +149,37 @@ static void cmd_identify( irc_t *irc, char **cmd ) irc_usermsg( irc, "The nick is (probably) not registered" ); break; case STORAGE_OK: - irc_usermsg( irc, "Password accepted, settings and accounts loaded" ); - irc_setpass( irc, cmd[1] ); + irc_usermsg( irc, "Password accepted%s", + load ? ", settings and accounts loaded" : "" ); + irc_setpass( irc, password ); irc->status |= USTATUS_IDENTIFIED; irc_umode_set( irc, "+R", 1 ); - if( set_getbool( &irc->set, "auto_connect" ) ) - cmd_account( irc, account_on ); + + /* 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 && + set_getbool( &irc->b->set, "auto_connect" ) ) + cmd_identify_finish( irc, 0, 0 ); + break; case STORAGE_OTHER_ERROR: default: @@ -171,6 +188,18 @@ static void cmd_identify( irc_t *irc, char **cmd ) } } +gboolean cmd_identify_finish( gpointer data, gint fd, b_input_condition cond ) +{ + char *account_on[] = { "account", "on", NULL }; + irc_t *irc = data; + + 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 ) { if( global.conf->authmode == AUTHMODE_REGISTERED ) @@ -201,7 +230,7 @@ static void cmd_drop( irc_t *irc, char **cmd ) { storage_status_t status; - status = storage_remove (irc->nick, cmd[1]); + status = storage_remove (irc->user->nick, cmd[1]); switch (status) { case STORAGE_NO_SUCH_USER: irc_usermsg( irc, "That account does not exist" ); @@ -213,7 +242,7 @@ static void cmd_drop( irc_t *irc, char **cmd ) irc_setpass( irc, NULL ); irc->status &= ~USTATUS_IDENTIFIED; irc_umode_set( irc, "-R", 1 ); - irc_usermsg( irc, "Account `%s' removed", irc->nick ); + irc_usermsg( irc, "Account `%s' removed", irc->user->nick ); break; default: irc_usermsg( irc, "Error: `%d'", status ); @@ -221,38 +250,14 @@ static void cmd_drop( irc_t *irc, char **cmd ) } } -struct cmd_account_del_data -{ - account_t *a; - irc_t *irc; -}; - -void cmd_account_del_yes( void *data ) +static void cmd_save( irc_t *irc, char **cmd ) { - struct cmd_account_del_data *cad = data; - account_t *a; - - for( a = cad->irc->accounts; a && a != cad->a; a = a->next ); - - if( a == NULL ) - { - irc_usermsg( cad->irc, "Account already deleted" ); - } - else if( a->ic ) - { - irc_usermsg( cad->irc, "Account is still logged in, can't delete" ); - } + if( ( irc->status & USTATUS_IDENTIFIED ) == 0 ) + irc_usermsg( irc, "Please create an account first" ); + else if( storage_save( irc, NULL, TRUE ) == STORAGE_OK ) + irc_usermsg( irc, "Configuration saved" ); else - { - account_del( cad->irc, a ); - irc_usermsg( cad->irc, "Account deleted" ); - } - g_free( data ); -} - -void cmd_account_del_no( void *data ) -{ - g_free( data ); + irc_usermsg( irc, "Configuration could not be saved!" ); } static void cmd_showset( irc_t *irc, set_t **head, char *key ) @@ -268,49 +273,24 @@ static void cmd_showset( irc_t *irc, set_t **head, char *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, cmd_set_findhead findhead, cmd_set_checkflags checkflags ) +static int cmd_set_real( irc_t *irc, char **cmd, set_t **head, cmd_set_checkflags checkflags ) { - char *set_full = NULL, *set_name = NULL, *tmp; - set_t **head; + char *set_name = NULL, *value = NULL; + gboolean del = FALSE; if( cmd[1] && g_strncasecmp( cmd[1], "-del", 4 ) == 0 ) { MIN_ARGS( 2, 0 ); - set_full = cmd[2]; + set_name = cmd[2]; + del = TRUE; } else - set_full = cmd[1]; - - if( findhead == NULL ) { - set_name = set_full; - - head = &irc->set; - } - else - { - char *id; - - if( ( tmp = strchr( set_full, '/' ) ) ) - { - id = g_strndup( set_full, ( tmp - set_full ) ); - set_name = tmp + 1; - } - else - { - id = g_strdup( set_full ); - } - - if( ( head = findhead( irc, id ) ) == NULL ) - { - g_free( id ); - irc_usermsg( irc, "Could not find setting." ); - return 0; - } - g_free( id ); + set_name = cmd[1]; + value = cmd[2]; } - if( cmd[1] && cmd[2] && set_name ) + if( set_name && ( value || del ) ) { set_t *s = set_find( head, set_name ); int st; @@ -318,13 +298,16 @@ static int cmd_set_real( irc_t *irc, char **cmd, cmd_set_findhead findhead, cmd_ if( s && checkflags && checkflags( irc, s ) == 0 ) return 0; - if( g_strncasecmp( cmd[1], "-del", 4 ) == 0 ) + if( del ) st = set_reset( head, set_name ); else - st = set_setstr( head, set_name, cmd[2] ); + st = set_setstr( head, set_name, value ); if( set_getstr( head, set_name ) == NULL ) { + /* This happens when changing the passwd, for example. + Showing these msgs instead gives slightly clearer + feedback. */ if( st ) irc_usermsg( irc, "Setting changed successfully" ); else @@ -352,16 +335,6 @@ static int cmd_set_real( irc_t *irc, char **cmd, cmd_set_findhead findhead, cmd_ return 1; } -static set_t **cmd_account_set_findhead( irc_t *irc, char *id ) -{ - account_t *a; - - if( ( a = account_get( irc, id ) ) ) - return &a->set; - else - return NULL; -} - static int cmd_account_set_checkflags( irc_t *irc, set_t *s ) { account_t *a = s->data; @@ -383,6 +356,7 @@ static int cmd_account_set_checkflags( irc_t *irc, set_t *s ) static void cmd_account( irc_t *irc, char **cmd ) { account_t *a; + int len; if( global.conf->authmode == AUTHMODE_REGISTERED && !( irc->status & USTATUS_IDENTIFIED ) ) { @@ -390,7 +364,9 @@ static void cmd_account( irc_t *irc, char **cmd ) return; } - if( g_strcasecmp( cmd[1], "add" ) == 0 ) + len = strlen( cmd[1] ); + + if( len >= 1 && g_strncasecmp( cmd[1], "add", len ) == 0 ) { struct prpl *prpl; @@ -403,8 +379,14 @@ static void cmd_account( irc_t *irc, char **cmd ) irc_usermsg( irc, "Unknown protocol" ); return; } - - a = account_add( irc, prpl, cmd[3], cmd[4] ); + + for( a = irc->b->accounts; a; a = a->next ) + if( a->prpl == prpl && prpl->handle_cmp( a->user, cmd[3] ) == 0 ) + irc_usermsg( 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] ); if( cmd[5] ) { irc_usermsg( irc, "Warning: Passing a servername/other flags to `account add' " @@ -413,45 +395,17 @@ static void cmd_account( irc_t *irc, char **cmd ) } irc_usermsg( irc, "Account successfully added" ); + + return; } - else if( g_strcasecmp( cmd[1], "del" ) == 0 ) - { - MIN_ARGS( 2 ); - - if( !( a = account_get( irc, cmd[2] ) ) ) - { - irc_usermsg( irc, "Invalid account" ); - } - else if( a->ic ) - { - irc_usermsg( irc, "Account is still logged in, can't delete" ); - } - else - { - struct cmd_account_del_data *cad; - char *msg; - - cad = g_malloc( sizeof( struct cmd_account_del_data ) ); - cad->a = a; - cad->irc = irc; - - msg = g_strdup_printf( "If you remove this account (%s(%s)), BitlBee will " - "also forget all your saved nicknames. If you want " - "to change your username/password, use the `account " - "set' command. Are you sure you want to delete this " - "account?", a->prpl->name, a->user ); - query_add( irc, NULL, msg, cmd_account_del_yes, cmd_account_del_no, cad ); - g_free( msg ); - } - } - else if( g_strcasecmp( cmd[1], "list" ) == 0 ) + else if( len >= 1 && g_strncasecmp( cmd[1], "list", len ) == 0 ) { int i = 0; if( strchr( irc->umode, 'b' ) ) irc_usermsg( irc, "Account list:" ); - for( a = irc->accounts; a; a = a->next ) + for( a = irc->b->accounts; a; a = a->next ) { char *con; @@ -464,95 +418,180 @@ static void cmd_account( irc_t *irc, char **cmd ) else con = ""; - irc_usermsg( irc, "%2d. %s, %s%s", i, a->prpl->name, a->user, con ); + irc_usermsg( irc, "%2d (%s): %s, %s%s", i, a->tag, a->prpl->name, a->user, con ); i ++; } irc_usermsg( irc, "End of account list" ); + + return; + } + else if( cmd[2] ) + { + /* Try the following two only if cmd[2] == NULL */ } - else if( g_strcasecmp( cmd[1], "on" ) == 0 ) + else if( len >= 2 && g_strncasecmp( cmd[1], "on", len ) == 0 ) { - if( cmd[2] ) + if ( irc->b->accounts ) { - if( ( a = account_get( irc, cmd[2] ) ) ) - { - if( a->ic ) - { - irc_usermsg( irc, "Account already online" ); - return; - } - else - { - account_on( irc, a ); - } - } - else - { - irc_usermsg( irc, "Invalid account" ); - return; - } - } + irc_usermsg( irc, "Trying to get all accounts connected..." ); + + for( a = irc->b->accounts; a; a = a->next ) + if( !a->ic && a->auto_connect ) + account_on( irc->b, a ); + } else { - if ( irc->accounts ) { - irc_usermsg( irc, "Trying to get all accounts connected..." ); - - for( a = irc->accounts; a; a = a->next ) - if( !a->ic && a->auto_connect ) - account_on( irc, a ); - } - else - { - irc_usermsg( irc, "No accounts known. Use `account add' to add one." ); - } + irc_usermsg( irc, "No accounts known. Use `account add' to add one." ); } + + return; } - else if( g_strcasecmp( cmd[1], "off" ) == 0 ) + else if( len >= 2 && g_strncasecmp( cmd[1], "off", len ) == 0 ) { - if( !cmd[2] ) - { - irc_usermsg( irc, "Deactivating all active (re)connections..." ); - - for( a = irc->accounts; a; a = a->next ) - { - if( a->ic ) - account_off( irc, a ); - else if( a->reconnect ) - cancel_auto_reconnect( a ); - } - } - else if( ( a = account_get( irc, cmd[2] ) ) ) + irc_usermsg( irc, "Deactivating all active (re)connections..." ); + + for( a = irc->b->accounts; a; a = a->next ) { if( a->ic ) - { - account_off( irc, a ); - } + account_off( irc->b, a ); else if( a->reconnect ) - { cancel_auto_reconnect( a ); - irc_usermsg( irc, "Reconnect cancelled" ); - } - else - { - irc_usermsg( irc, "Account already offline" ); - return; - } + } + + 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_usermsg( irc, "Could not find account `%s'. Note that the syntax " + "of the account command changed, see \x02help account\x02.", cmd[1] ); + + return; + } + + if( len >= 1 && g_strncasecmp( cmd[2], "del", len ) == 0 ) + { + if( a->ic ) + { + irc_usermsg( irc, "Account is still logged in, can't delete" ); } else { - irc_usermsg( irc, "Invalid account" ); - return; + account_del( irc->b, a ); + irc_usermsg( irc, "Account deleted" ); } } - else if( g_strcasecmp( cmd[1], "set" ) == 0 ) + else if( len >= 2 && g_strncasecmp( cmd[2], "on", len ) == 0 ) { - MIN_ARGS( 2 ); + if( a->ic ) + irc_usermsg( irc, "Account already online" ); + 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_usermsg( irc, "Reconnect cancelled" ); + } + else + { + irc_usermsg( 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_usermsg( 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; - cmd_set_real( irc, cmd + 1, cmd_account_set_findhead, cmd_account_set_checkflags ); + if( strchr( irc->umode, 'b' ) ) + irc_usermsg( irc, "Channel list:" ); + + for( l = irc->channels; l; l = l->next ) + { + irc_channel_t *ic = l->data; + + irc_usermsg( irc, "%2d. %s, %s channel%s", i, ic->name, + set_getstr( &ic->set, "type" ), + ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "" ); + + i ++; + } + irc_usermsg( 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( ( len = strlen( cmd[1] ) ) && + g_strncasecmp( cmd[1], "set", len ) == 0 && + ( ic = irc_channel_by_name( irc, irc->last_root_cmd ) ) ) + cmd_set_real( irc, cmd + 1, &ic->set, NULL ); + else + irc_usermsg( 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_usermsg( irc, "Channel %s deleted.", ic->name ); + irc_channel_free( ic ); + } + else + irc_usermsg( irc, "Couldn't remove channel (main channel %s or " + "channels you're still in cannot be deleted).", + irc->default_channel->name ); } else { - irc_usermsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "account", cmd[1] ); + irc_usermsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel", cmd[1] ); } } @@ -568,7 +607,7 @@ static void cmd_add( irc_t *irc, char **cmd ) cmd ++; } - if( !( a = account_get( irc, cmd[1] ) ) ) + if( !( a = account_get( irc->b, cmd[1] ) ) ) { irc_usermsg( irc, "Invalid account" ); return; @@ -586,26 +625,70 @@ static void cmd_add( irc_t *irc, char **cmd ) irc_usermsg( irc, "The requested nick `%s' is invalid", cmd[3] ); return; } - else if( user_find( irc, cmd[3] ) ) + else if( irc_user_by_name( irc, cmd[3] ) ) { irc_usermsg( irc, "The requested nick `%s' already exists", cmd[3] ); return; } else { - nick_set( a, cmd[2], cmd[3] ); + nick_set_raw( a, cmd[2], cmd[3] ); } } if( add_on_server ) - a->ic->acc->prpl->add_buddy( a->ic, cmd[2], NULL ); + { + irc_channel_t *ic; + char *s, *group = NULL;; + + if( ( ic = irc_channel_by_name( irc, irc->last_root_cmd ) ) && + ( s = set_getstr( &ic->set, "fill_by" ) ) && + strcmp( s, "group" ) == 0 && + ( group = set_getstr( &ic->set, "group" ) ) ) + irc_usermsg( irc, "Adding `%s' to contact list (group %s)", + cmd[2], group ); + else + irc_usermsg( irc, "Adding `%s' to contact list", cmd[2] ); + + a->prpl->add_buddy( a->ic, cmd[2], group ); + } else - /* Yeah, officially this is a call-*back*... So if we just - called add_buddy, we'll wait for the IM server to respond - before we do this. */ - imcb_add_buddy( a->ic, cmd[2], NULL ); + { + 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_usermsg( irc, "Temporarily assigned nickname `%s' " + "to contact `%s'", iu->nick, cmd[2] ); + } + +} + +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_usermsg( irc, "Buddy `%s' not found", cmd[1] ); + return; + } + s = g_strdup( bu->handle ); - irc_usermsg( irc, "Adding `%s' to your contact list", cmd[2] ); + 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_usermsg( 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 ) @@ -615,16 +698,16 @@ static void cmd_info( irc_t *irc, char **cmd ) if( !cmd[2] ) { - user_t *u = user_find( irc, cmd[1] ); - if( !u || !u->ic ) + irc_user_t *iu = irc_user_by_name( irc, cmd[1] ); + if( !iu || !iu->bu ) { irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] ); return; } - ic = u->ic; - cmd[2] = u->handle; + ic = iu->bu->ic; + cmd[2] = iu->bu->handle; } - else if( !( a = account_get( irc, cmd[1] ) ) ) + else if( !( a = account_get( irc->b, cmd[1] ) ) ) { irc_usermsg( irc, "Invalid account" ); return; @@ -647,56 +730,44 @@ static void cmd_info( irc_t *irc, char **cmd ) static void cmd_rename( irc_t *irc, char **cmd ) { - user_t *u; + irc_user_t *iu, *old; - if( g_strcasecmp( cmd[1], irc->nick ) == 0 ) - { - irc_usermsg( irc, "Nick `%s' can't be changed", cmd[1] ); - } - else if( g_strcasecmp( cmd[1], irc->channel ) == 0 ) + iu = irc_user_by_name( irc, cmd[1] ); + + if( iu == NULL ) { - if( strchr( CTYPES, cmd[2][0] ) && nick_ok( cmd[2] + 1 ) ) - { - u = user_find( irc, irc->nick ); - - irc_part( irc, u, irc->channel ); - g_free( irc->channel ); - irc->channel = g_strdup( cmd[2] ); - irc_join( irc, u, irc->channel ); - - if( strcmp( cmd[0], "set_rename" ) != 0 ) - set_setstr( &irc->set, "control_channel", cmd[2] ); - } + irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] ); } - else if( user_find( irc, cmd[2] ) && ( nick_cmp( cmd[1], cmd[2] ) != 0 ) ) + else if( iu == irc->user ) { - irc_usermsg( irc, "Nick `%s' already exists", cmd[2] ); + irc_usermsg( irc, "Use /nick to change your own nickname" ); } else if( !nick_ok( cmd[2] ) ) { irc_usermsg( irc, "Nick `%s' is invalid", cmd[2] ); } - else if( !( u = user_find( irc, cmd[1] ) ) ) + else if( ( old = irc_user_by_name( irc, cmd[2] ) ) && old != iu ) { - irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] ); + irc_usermsg( irc, "Nick `%s' already exists", cmd[2] ); } else { - user_rename( irc, cmd[1], cmd[2] ); - irc_write( irc, ":%s!%s@%s NICK %s", cmd[1], u->user, u->host, cmd[2] ); - if( g_strcasecmp( cmd[1], irc->mynick ) == 0 ) + if( !irc_user_set_nick( iu, cmd[2] ) ) + { + irc_usermsg( irc, "Error while changing nick" ); + return; + } + + if( iu == irc->root ) { - g_free( irc->mynick ); - irc->mynick = g_strdup( cmd[2] ); - /* 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->set, "root_nick", cmd[2] ); + set_setstr( &irc->b->set, "root_nick", cmd[2] ); } - else if( u->send_handler == buddy_send_handler ) + else if( iu->bu ) { - nick_set( u->ic->acc, u->handle, cmd[2] ); + nick_set( iu->bu, cmd[2] ); } irc_usermsg( irc, "Nick successfully changed" ); @@ -707,50 +778,14 @@ char *set_eval_root_nick( set_t *set, char *new_nick ) { irc_t *irc = set->data; - if( strcmp( irc->mynick, new_nick ) != 0 ) - { - char *cmd[] = { "set_rename", irc->mynick, new_nick, NULL }; - - cmd_rename( irc, cmd ); - } - - return strcmp( irc->mynick, new_nick ) == 0 ? new_nick : SET_INVALID; -} - -char *set_eval_control_channel( set_t *set, char *new_name ) -{ - irc_t *irc = set->data; - - if( strcmp( irc->channel, new_name ) != 0 ) + if( strcmp( irc->root->nick, new_nick ) != 0 ) { - char *cmd[] = { "set_rename", irc->channel, new_name, NULL }; + char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL }; cmd_rename( irc, cmd ); } - return strcmp( irc->channel, new_name ) == 0 ? new_name : SET_INVALID; -} - -static void cmd_remove( irc_t *irc, char **cmd ) -{ - user_t *u; - char *s; - - if( !( u = user_find( irc, cmd[1] ) ) || !u->ic ) - { - irc_usermsg( irc, "Buddy `%s' not found", cmd[1] ); - return; - } - s = g_strdup( u->handle ); - - u->ic->acc->prpl->remove_buddy( u->ic, u->handle, NULL ); - nick_del( u->ic->acc, u->handle ); - user_del( irc, cmd[1] ); - - irc_usermsg( irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1] ); - g_free( s ); - - return; + return strcmp( irc->root->nick, new_nick ) == 0 ? new_nick : SET_INVALID; } static void cmd_block( irc_t *irc, char **cmd ) @@ -758,7 +793,7 @@ static void cmd_block( irc_t *irc, char **cmd ) struct im_connection *ic; account_t *a; - if( !cmd[2] && ( a = account_get( irc, cmd[1] ) ) && a->ic ) + if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic ) { char *format; GSList *l; @@ -771,8 +806,9 @@ static void cmd_block( irc_t *irc, char **cmd ) irc_usermsg( irc, format, "Handle", "Nickname" ); for( l = a->ic->deny; l; l = l->next ) { - user_t *u = user_findhandle( a->ic, l->data ); - irc_usermsg( irc, format, l->data, u ? u->nick : "(none)" ); + bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data ); + irc_user_t *iu = bu ? bu->ui_data : NULL; + irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" ); } irc_usermsg( irc, "End of list." ); @@ -780,16 +816,16 @@ static void cmd_block( irc_t *irc, char **cmd ) } else if( !cmd[2] ) { - user_t *u = user_find( irc, cmd[1] ); - if( !u || !u->ic ) + irc_user_t *iu = irc_user_by_name( irc, cmd[1] ); + if( !iu || !iu->bu ) { irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] ); return; } - ic = u->ic; - cmd[2] = u->handle; + ic = iu->bu->ic; + cmd[2] = iu->bu->handle; } - else if( !( a = account_get( irc, cmd[1] ) ) ) + else if( !( a = account_get( irc->b, cmd[1] ) ) ) { irc_usermsg( irc, "Invalid account" ); return; @@ -808,7 +844,7 @@ static void cmd_block( irc_t *irc, char **cmd ) { imc_rem_allow( ic, cmd[2] ); imc_add_block( ic, cmd[2] ); - irc_usermsg( irc, "Buddy `%s' moved from your allow- to your block-list", cmd[2] ); + irc_usermsg( irc, "Buddy `%s' moved from allow- to block-list", cmd[2] ); } } @@ -817,7 +853,7 @@ static void cmd_allow( irc_t *irc, char **cmd ) struct im_connection *ic; account_t *a; - if( !cmd[2] && ( a = account_get( irc, cmd[1] ) ) && a->ic ) + if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic ) { char *format; GSList *l; @@ -830,8 +866,9 @@ static void cmd_allow( irc_t *irc, char **cmd ) irc_usermsg( irc, format, "Handle", "Nickname" ); for( l = a->ic->permit; l; l = l->next ) { - user_t *u = user_findhandle( a->ic, l->data ); - irc_usermsg( irc, format, l->data, u ? u->nick : "(none)" ); + bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data ); + irc_user_t *iu = bu ? bu->ui_data : NULL; + irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" ); } irc_usermsg( irc, "End of list." ); @@ -839,16 +876,16 @@ static void cmd_allow( irc_t *irc, char **cmd ) } else if( !cmd[2] ) { - user_t *u = user_find( irc, cmd[1] ); - if( !u || !u->ic ) + irc_user_t *iu = irc_user_by_name( irc, cmd[1] ); + if( !iu || !iu->bu ) { irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] ); return; } - ic = u->ic; - cmd[2] = u->handle; + ic = iu->bu->ic; + cmd[2] = iu->bu->handle; } - else if( !( a = account_get( irc, cmd[1] ) ) ) + else if( !( a = account_get( irc->b, cmd[1] ) ) ) { irc_usermsg( irc, "Invalid account" ); return; @@ -868,7 +905,7 @@ static void cmd_allow( irc_t *irc, char **cmd ) imc_rem_block( ic, cmd[2] ); imc_add_allow( ic, cmd[2] ); - irc_usermsg( irc, "Buddy `%s' moved from your block- to your allow-list", cmd[2] ); + irc_usermsg( irc, "Buddy `%s' moved from block- to allow-list", cmd[2] ); } } @@ -912,23 +949,13 @@ static void cmd_yesno( irc_t *irc, char **cmd ) static void cmd_set( irc_t *irc, char **cmd ) { - cmd_set_real( irc, cmd, NULL, NULL ); -} - -static void cmd_save( irc_t *irc, char **cmd ) -{ - if( ( irc->status & USTATUS_IDENTIFIED ) == 0 ) - irc_usermsg( irc, "Please create an account first" ); - else if( storage_save( irc, NULL, TRUE ) == STORAGE_OK ) - irc_usermsg( irc, "Configuration saved" ); - else - irc_usermsg( irc, "Configuration could not be saved!" ); + 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; - user_t *u; + GSList *l; char s[256]; char *format; int n_online = 0, n_away = 0, n_offline = 0; @@ -949,40 +976,58 @@ static void cmd_blist( irc_t *irc, char **cmd ) else format = "%-16.16s %-40.40s %s"; - irc_usermsg( irc, format, "Nick", "User/Host/Network", "Status" ); + irc_usermsg( irc, format, "Nick", "Handle/Account", "Status" ); - for( u = irc->users; u; u = u->next ) if( u->ic && u->online && !u->away ) + for( l = irc->users; l; l = l->next ) { + irc_user_t *iu = l->data; + bee_user_t *bu = iu->bu; + + if( !bu || ( bu->flags & ( BEE_USER_ONLINE | BEE_USER_AWAY ) ) != BEE_USER_ONLINE ) + continue; + if( online == 1 ) { char st[256] = "Online"; - if( u->status_msg ) - g_snprintf( st, sizeof( st ) - 1, "Online (%s)", u->status_msg ); + if( bu->status_msg ) + g_snprintf( st, sizeof( st ) - 1, "Online (%s)", bu->status_msg ); - g_snprintf( s, sizeof( s ) - 1, "%s@%s %s(%s)", u->user, u->host, u->ic->acc->prpl->name, u->ic->acc->user ); - irc_usermsg( irc, format, u->nick, s, st ); + g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user ); + irc_usermsg( irc, format, iu->nick, s, st ); } n_online ++; } - for( u = irc->users; u; u = u->next ) if( u->ic && u->online && u->away ) + for( l = irc->users; l; l = l->next ) { + irc_user_t *iu = l->data; + bee_user_t *bu = iu->bu; + + if( !bu || !( bu->flags & BEE_USER_ONLINE ) || !( bu->flags & BEE_USER_AWAY ) ) + continue; + if( away == 1 ) { - g_snprintf( s, sizeof( s ) - 1, "%s@%s %s(%s)", u->user, u->host, u->ic->acc->prpl->name, u->ic->acc->user ); - irc_usermsg( irc, format, u->nick, s, u->away ); + g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user ); + irc_usermsg( irc, format, iu->nick, s, irc_user_get_away( iu ) ); } n_away ++; } - for( u = irc->users; u; u = u->next ) if( u->ic && !u->online ) + for( l = irc->users; l; l = l->next ) { + irc_user_t *iu = l->data; + bee_user_t *bu = iu->bu; + + if( !bu || bu->flags & BEE_USER_ONLINE ) + continue; + if( offline == 1 ) { - g_snprintf( s, sizeof( s ) - 1, "%s@%s %s(%s)", u->user, u->host, u->ic->acc->prpl->name, u->ic->acc->user ); - irc_usermsg( irc, format, u->nick, s, "Offline" ); + g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user ); + irc_usermsg( irc, format, iu->nick, s, "Offline" ); } n_offline ++; } @@ -990,34 +1035,6 @@ static void cmd_blist( irc_t *irc, char **cmd ) irc_usermsg( irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online, n_away, n_offline ); } -static void cmd_nick( irc_t *irc, char **cmd ) -{ - account_t *a; - - if( !cmd[1] || !( a = account_get( irc, cmd[1] ) ) ) - { - irc_usermsg( irc, "Invalid account"); - } - else if( !( a->ic && ( a->ic->flags & OPT_LOGGED_IN ) ) ) - { - irc_usermsg( irc, "That account is not on-line" ); - } - else if ( !cmd[2] ) - { - irc_usermsg( irc, "Your name is `%s'" , a->ic->displayname ? a->ic->displayname : "NULL" ); - } - else if ( !a->prpl->set_my_name ) - { - irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] ); - } - else - { - irc_usermsg( irc, "Setting your name to `%s'", cmd[2] ); - - a->prpl->set_my_name( a->ic, cmd[2] ); - } -} - static void cmd_qlist( irc_t *irc, char **cmd ) { query_t *q = irc->queries; @@ -1038,38 +1055,27 @@ static void cmd_qlist( irc_t *irc, char **cmd ) irc_usermsg( irc, "%d, BitlBee: %s", num, q->question ); } -static void cmd_join_chat( irc_t *irc, char **cmd ) -{ - irc_usermsg( irc, "This command is now obsolete. " - "Please try the `chat' command instead." ); -} - -static set_t **cmd_chat_set_findhead( irc_t *irc, char *id ) -{ - struct chat *c; - - if( ( c = chat_get( irc, id ) ) ) - return &c->set; - else - return NULL; -} - static void cmd_chat( irc_t *irc, char **cmd ) { account_t *acc; - struct chat *c; if( g_strcasecmp( cmd[1], "add" ) == 0 ) { char *channel, *s; + struct irc_channel *ic; MIN_ARGS( 3 ); - if( !( acc = account_get( irc, cmd[2] ) ) ) + if( !( acc = account_get( irc->b, cmd[2] ) ) ) { irc_usermsg( irc, "Invalid account" ); return; } + else if( !acc->prpl->chat_join ) + { + irc_usermsg( irc, "Named chatrooms not supported on that account." ); + return; + } if( cmd[4] == NULL ) { @@ -1084,65 +1090,43 @@ static void cmd_chat( irc_t *irc, char **cmd ) if( strchr( CTYPES, channel[0] ) == NULL ) { - s = g_strdup_printf( "%c%s", CTYPES[0], channel ); + s = g_strdup_printf( "#%s", channel ); g_free( channel ); channel = s; - } - - if( ( c = chat_add( irc, acc, cmd[3], channel ) ) ) - irc_usermsg( irc, "Chatroom added successfully." ); - else - irc_usermsg( irc, "Could not add chatroom." ); - - g_free( channel ); - } - else if( g_strcasecmp( cmd[1], "list" ) == 0 ) - { - int i = 0; - - if( strchr( irc->umode, 'b' ) ) - irc_usermsg( irc, "Chatroom list:" ); - - for( c = irc->chatrooms; c; c = c->next ) - { - irc_usermsg( irc, "%2d. %s(%s) %s, %s", i, c->acc->prpl->name, - c->acc->user, c->handle, c->channel ); - i ++; + irc_channel_name_strip( channel ); } - irc_usermsg( irc, "End of chatroom list" ); - } - else if( g_strcasecmp( cmd[1], "set" ) == 0 ) - { - MIN_ARGS( 2 ); - cmd_set_real( irc, cmd + 1, cmd_chat_set_findhead, NULL ); - } - else if( g_strcasecmp( cmd[1], "del" ) == 0 ) - { - MIN_ARGS( 2 ); - - if( ( c = chat_get( irc, cmd[2] ) ) ) + 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] ) ) { - chat_del( irc, c ); + irc_usermsg( irc, "Chatroom successfully added." ); } else { - irc_usermsg( irc, "Could not remove chat." ); + if( ic ) + irc_channel_free( ic ); + + irc_usermsg( irc, "Could not add chatroom." ); } + g_free( channel ); } else if( g_strcasecmp( cmd[1], "with" ) == 0 ) { - user_t *u; + irc_user_t *iu; MIN_ARGS( 2 ); - if( ( u = user_find( irc, cmd[2] ) ) && u->ic && u->ic->acc->prpl->chat_with ) + if( ( iu = irc_user_by_name( irc, cmd[2] ) ) && + iu->bu && iu->bu->ic->acc->prpl->chat_with ) { - if( !u->ic->acc->prpl->chat_with( u->ic, u->handle ) ) + if( !iu->bu->ic->acc->prpl->chat_with( iu->bu->ic, iu->bu->handle ) ) { irc_usermsg( irc, "(Possible) failure while trying to open " - "a groupchat with %s.", u->nick ); + "a groupchat with %s.", iu->nick ); } } else @@ -1150,32 +1134,130 @@ static void cmd_chat( irc_t *irc, char **cmd ) irc_usermsg( 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_usermsg( irc, "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command." ); + cmd_channel( irc, cmd ); + } else { irc_usermsg( 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_usermsg( irc, "Group list:" ); + + for( l = irc->b->groups; l; l = l->next ) + { + bee_group_t *bg = l->data; + irc_usermsg( irc, "%d. %s", n ++, bg->name ); + } + irc_usermsg( irc, "End of group list" ); + } + else + { + irc_usermsg( 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_usermsg( 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_usermsg( 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_usermsg( 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_usermsg( 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_usermsg( irc, "Canceling file transfer for %s", file->file_name ); + imcb_file_canceled( file->ic, file, "Canceled by user" ); + } + break; + } + } +} + +/* IMPORTANT: Keep this list sorted! The short command logic needs that. */ const command_t commands[] = { - { "help", 0, cmd_help, 0 }, - { "identify", 1, cmd_identify, 0 }, - { "register", 1, cmd_register, 0 }, - { "drop", 1, cmd_drop, 0 }, { "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", 1, cmd_identify, 0 }, { "info", 1, cmd_info, 0 }, - { "rename", 2, cmd_rename, 0 }, + { "no", 0, cmd_yesno, 0 }, + { "qlist", 0, cmd_qlist, 0 }, + { "register", 1, cmd_register, 0 }, { "remove", 1, cmd_remove, 0 }, - { "block", 1, cmd_block, 0 }, - { "allow", 1, cmd_allow, 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 }, - { "no", 0, cmd_yesno, 0 }, - { "blist", 0, cmd_blist, 0 }, - { "nick", 1, cmd_nick, 0 }, - { "qlist", 0, cmd_qlist, 0 }, - { "join_chat", 2, cmd_join_chat, 0 }, - { "chat", 1, cmd_chat, 0 }, { NULL } }; @@ -28,7 +28,7 @@ /* Used to use NULL for this, but NULL is actually a "valid" value. */ char *SET_INVALID = "nee"; -set_t *set_add( set_t **head, char *key, char *def, set_eval eval, void *data ) +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 ); @@ -62,13 +62,14 @@ set_t *set_add( set_t **head, char *key, char *def, set_eval eval, void *data ) return s; } -set_t *set_find( set_t **head, char *key ) +set_t *set_find( set_t **head, const char *key ) { set_t *s = *head; while( s ) { - if( g_strcasecmp( s->key, key ) == 0 ) + if( g_strcasecmp( s->key, key ) == 0 || + ( s->old_key && g_strcasecmp( s->old_key, key ) == 0 ) ) break; s = s->next; } @@ -76,7 +77,7 @@ set_t *set_find( set_t **head, char *key ) return s; } -char *set_getstr( set_t **head, char *key ) +char *set_getstr( set_t **head, const char *key ) { set_t *s = set_find( head, key ); @@ -86,7 +87,7 @@ char *set_getstr( set_t **head, char *key ) return s->value ? s->value : s->def; } -int set_getint( set_t **head, char *key ) +int set_getint( set_t **head, const char *key ) { char *s = set_getstr( head, key ); int i = 0; @@ -100,7 +101,7 @@ int set_getint( set_t **head, char *key ) return i; } -int set_getbool( set_t **head, char *key ) +int set_getbool( set_t **head, const char *key ) { char *s = set_getstr( head, key ); @@ -110,7 +111,7 @@ int set_getbool( set_t **head, char *key ) return bool2int( s ); } -int set_setstr( set_t **head, char *key, char *value ) +int set_setstr( set_t **head, const char *key, char *value ) { set_t *s = set_find( head, key ); char *nv = value; @@ -149,7 +150,7 @@ int set_setstr( set_t **head, char *key, char *value ) return 1; } -int set_setint( set_t **head, char *key, int value ) +int set_setint( set_t **head, const char *key, int value ) { char s[24]; /* Not quite 128-bit clean eh? ;-) */ @@ -157,7 +158,7 @@ int set_setint( set_t **head, char *key, int value ) return set_setstr( head, key, s ); } -void set_del( set_t **head, char *key ) +void set_del( set_t **head, const char *key ) { set_t *s = *head, *t = NULL; @@ -175,13 +176,14 @@ void set_del( set_t **head, char *key ) *head = s->next; g_free( s->key ); - if( s->value ) g_free( s->value ); - if( s->def ) g_free( s->def ); + g_free( s->old_key ); + g_free( s->value ); + g_free( s->def ); g_free( s ); } } -int set_reset( set_t **head, char *key ) +int set_reset( set_t **head, const char *key ) { set_t *s; @@ -212,6 +214,21 @@ 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 ); @@ -224,6 +241,7 @@ char *set_eval_to_char( set_t *set, char *value ) return s; } +/* char *set_eval_ops( set_t *set, char *value ) { irc_t *irc = set->data; @@ -245,3 +263,4 @@ char *set_eval_ops( set_t *set, char *value ) return value; } +*/ @@ -53,6 +53,7 @@ typedef struct set 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 @@ -68,35 +69,39 @@ typedef struct set the passed value variable. When returning a corrected value, set_setstr() should be able to free() the returned string! */ set_eval eval; + void *eval_data; struct set *next; } set_t; /* Should be pretty clear. */ -set_t *set_add( set_t **head, char *key, char *def, set_eval eval, void *data ); +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, char *key ); +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, char *key ); +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, char *key ); -G_MODULE_EXPORT int set_getbool( set_t **head, char *key ); +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, char *key, char *value ); -int set_setint( set_t **head, char *key, int value ); -void set_del( set_t **head, char *key ); -int set_reset( set_t **head, char *key ); +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 ); /* 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_ops( set_t *set, char *value ); @@ -27,7 +27,6 @@ #define BITLBEE_CORE #include "bitlbee.h" -#include "crypting.h" extern storage_t storage_text; extern storage_t storage_xml; @@ -65,7 +64,6 @@ GList *storage_init(const char *primary, char **migrate) int i; storage_t *storage; - register_storage_backend(&storage_text); register_storage_backend(&storage_xml); storage = storage_init_single(primary); diff --git a/storage_text.c b/storage_text.c deleted file mode 100644 index 8ce4edcf..00000000 --- a/storage_text.c +++ /dev/null @@ -1,157 +0,0 @@ - /********************************************************************\ - * BitlBee -- An IRC to other IM-networks gateway * - * * - * Copyright 2002-2004 Wilmer van der Gaast and others * - \********************************************************************/ - -/* Storage backend that uses the same file format as <=1.0 */ - -/* - 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 "crypting.h" -#ifdef _WIN32 -# define umask _umask -# define mode_t int -#endif - -#ifndef F_OK -#define F_OK 0 -#endif - -static void text_init (void) -{ - /* Don't complain about the configuration directory anymore, leave it - up to the XML storage module, which uses the same directory for it - anyway. Nobody should be using just the text plugin anymore since - it's read only! */ -} - -static storage_status_t text_load( irc_t *irc, const char* password ) -{ - char s[512]; - char *line; - int proto; - char nick[MAX_NICK_LENGTH+1]; - FILE *fp; - user_t *ru = user_find( irc, ROOT_NICK ); - account_t *acc, *acc_lookup[9]; - - g_snprintf( s, 511, "%s%s%s", global.conf->configdir, irc->nick, ".accounts" ); - fp = fopen( s, "r" ); - if( !fp ) return STORAGE_NO_SUCH_USER; - - fscanf( fp, "%32[^\n]s", s ); - - if( checkpass( password, s ) != 0 ) - { - fclose( fp ); - return STORAGE_INVALID_PASSWORD; - } - - while( fscanf( fp, "%511[^\n]s", s ) > 0 ) - { - fgetc( fp ); - line = deobfucrypt( s, password ); - if (line == NULL) return STORAGE_OTHER_ERROR; - root_command_string( irc, ru, line, 0 ); - g_free( line ); - } - fclose( fp ); - - /* Build a list with the first listed account of every protocol - number. So if the user had nicks defined for a second account on - the same IM network, those nicks will be added to the wrong - account, and the user should rename those buddies again. But at - least from now on things will be saved properly. */ - memset( acc_lookup, 0, sizeof( acc_lookup ) ); - for( acc = irc->accounts; acc; acc = acc->next ) - { - if( acc_lookup[0] == NULL && strcmp( acc->prpl->name, "oscar" ) == 0 ) - acc_lookup[0] = acc_lookup[1] = acc_lookup[3] = acc; - else if( acc_lookup[2] == NULL && strcmp( acc->prpl->name, "yahoo" ) == 0 ) - acc_lookup[2] = acc; - else if( acc_lookup[4] == NULL && strcmp( acc->prpl->name, "msn" ) == 0 ) - acc_lookup[4] = acc; - else if( acc_lookup[8] == NULL && strcmp( acc->prpl->name, "jabber" ) == 0 ) - acc_lookup[8] = acc; - } - - g_snprintf( s, 511, "%s%s%s", global.conf->configdir, irc->nick, ".nicks" ); - fp = fopen( s, "r" ); - if( !fp ) return STORAGE_NO_SUCH_USER; - while( fscanf( fp, "%s %d %s", s, &proto, nick ) > 0 ) - { - if( proto < 0 || proto > 8 || ( acc = acc_lookup[proto] ) == NULL ) - continue; - - http_decode( s ); - nick_set( acc, s, nick ); - } - fclose( fp ); - - return STORAGE_OK; -} - -static storage_status_t text_check_pass( const char *nick, const char *password ) -{ - char s[512]; - FILE *fp; - - g_snprintf( s, 511, "%s%s%s", global.conf->configdir, nick, ".accounts" ); - fp = fopen( s, "r" ); - if (!fp) - return STORAGE_NO_SUCH_USER; - - fscanf( fp, "%32[^\n]s", s ); - fclose( fp ); - - if (checkpass( password, s) == -1) - return STORAGE_INVALID_PASSWORD; - - return STORAGE_OK; -} - -static storage_status_t text_remove( const char *nick, const char *password ) -{ - char s[512]; - storage_status_t status; - - status = text_check_pass( nick, password ); - if (status != STORAGE_OK) - return status; - - g_snprintf( s, 511, "%s%s%s", global.conf->configdir, nick, ".accounts" ); - if (unlink( s ) == -1) - return STORAGE_OTHER_ERROR; - - g_snprintf( s, 511, "%s%s%s", global.conf->configdir, nick, ".nicks" ); - if (unlink( s ) == -1) - return STORAGE_OTHER_ERROR; - - return STORAGE_OK; -} - -storage_t storage_text = { - .name = "text", - .init = text_init, - .check_pass = text_check_pass, - .remove = text_remove, - .load = text_load -}; diff --git a/storage_xml.c b/storage_xml.c index 7321637c..1d6757ae 100644 --- a/storage_xml.c +++ b/storage_xml.c @@ -28,7 +28,6 @@ #include "base64.h" #include "arc.h" #include "md5.h" -#include "chat.h" #if GLIB_CHECK_VERSION(2,8,0) #include <glib/gstdio.h> @@ -54,7 +53,7 @@ struct xml_parsedata irc_t *irc; char *current_setting; account_t *current_account; - struct chat *current_chat; + irc_channel_t *current_channel; set_t **current_set_head; char *given_nick; char *given_pass; @@ -127,7 +126,7 @@ static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_na } else if( g_strcasecmp( element_name, "account" ) == 0 ) { - char *protocol, *handle, *server, *password = NULL, *autoconnect; + char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag; char *pass_b64 = NULL; unsigned char *pass_cr = NULL; int pass_len; @@ -137,6 +136,7 @@ static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_na pass_b64 = xml_attr( attr_names, attr_values, "password" ); server = xml_attr( attr_names, attr_values, "server" ); autoconnect = xml_attr( attr_names, attr_values, "autoconnect" ); + tag = xml_attr( attr_names, attr_values, "tag" ); protocol = xml_attr( attr_names, attr_values, "protocol" ); if( protocol ) @@ -151,11 +151,13 @@ static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_na else if( ( pass_len = base64_decode( pass_b64, (unsigned char**) &pass_cr ) ) && arc_decode( pass_cr, pass_len, &password, xd->given_pass ) ) { - xd->current_account = account_add( irc, prpl, handle, password ); + xd->current_account = account_add( irc->b, prpl, handle, password ); if( server ) set_setstr( &xd->current_account->set, "server", server ); if( autoconnect ) set_setstr( &xd->current_account->set, "auto_connect", autoconnect ); + if( tag ) + set_setstr( &xd->current_account->set, "tag", tag ); } else { @@ -180,12 +182,12 @@ static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_na if( ( setting = xml_attr( attr_names, attr_values, "name" ) ) ) { - if( xd->current_chat != NULL ) - xd->current_set_head = &xd->current_chat->set; + if( xd->current_channel != NULL ) + xd->current_set_head = &xd->current_channel->set; else if( xd->current_account != NULL ) xd->current_set_head = &xd->current_account->set; else - xd->current_set_head = &xd->irc->set; + xd->current_set_head = &xd->irc->b->set; xd->current_setting = g_strdup( setting ); } @@ -202,7 +204,7 @@ static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_na if( xd->current_account && handle && nick ) { - nick_set( xd->current_account, handle, nick ); + nick_set_raw( xd->current_account, handle, nick ); } else { @@ -210,6 +212,30 @@ static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_na "Missing attributes for %s element", element_name ); } } + else if( g_strcasecmp( element_name, "channel" ) == 0 ) + { + char *name, *type; + + name = xml_attr( attr_names, attr_values, "name" ); + type = xml_attr( attr_names, attr_values, "type" ); + + if( !name || !type ) + { + g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "Missing attributes for %s element", element_name ); + return; + } + + /* 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( ( xd->current_channel = irc_channel_by_name( irc, name ) ) || + ( xd->current_channel = irc_channel_new( irc, name ) ) ) + set_setstr( &xd->current_channel->set, "type", type ); + } + /* Backward compatibility: Keep this around for a while for people + switching from BitlBee 1.2.4+. */ else if( g_strcasecmp( element_name, "chat" ) == 0 ) { char *handle, *channel; @@ -219,7 +245,19 @@ static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_na if( xd->current_account && handle && channel ) { - xd->current_chat = chat_add( xd->irc, xd->current_account, handle, channel ); + irc_channel_t *ic; + + 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", xd->current_account->tag ) && + set_setstr( &ic->set, "room", handle ) ) + { + /* Try to pick up some settings where possible. */ + xd->current_channel = ic; + } + else if( ic ) + irc_channel_free( ic ); } else { @@ -258,9 +296,10 @@ static void xml_end_element( GMarkupParseContext *ctx, const gchar *element_name { xd->current_account = NULL; } - else if( g_strcasecmp( element_name, "chat" ) == 0 ) + else if( g_strcasecmp( element_name, "channel" ) == 0 || + g_strcasecmp( element_name, "chat" ) == 0 ) { - xd->current_chat = NULL; + xd->current_channel = NULL; } } @@ -368,7 +407,7 @@ static storage_status_t xml_load_real( irc_t *irc, const char *my_nick, const ch static storage_status_t xml_load( irc_t *irc, const char *password ) { - return xml_load_real( irc, irc->nick, password, XML_PASS_UNKNOWN ); + 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 ) @@ -410,8 +449,9 @@ static storage_status_t xml_save( irc_t *irc, int overwrite ) int fd; md5_byte_t pass_md5[21]; md5_state_t md5_state; + GSList *l; - path2 = g_strdup( irc->nick ); + path2 = g_strdup( irc->user->nick ); nick_lc( path2 ); g_snprintf( path, sizeof( path ) - 2, "%s%s%s", global.conf->configdir, path2, ".xml" ); g_free( path2 ); @@ -437,28 +477,29 @@ static storage_status_t xml_save( irc_t *irc, int overwrite ) /* Save the hash in base64-encoded form. */ pass_buf = base64_encode( pass_md5, 21 ); - if( !xml_printf( fd, 0, "<user nick=\"%s\" password=\"%s\" version=\"%d\">\n", irc->nick, pass_buf, XML_FORMAT_VERSION ) ) + if( !xml_printf( fd, 0, "<user nick=\"%s\" password=\"%s\" version=\"%d\">\n", irc->user->nick, pass_buf, XML_FORMAT_VERSION ) ) goto write_error; g_free( pass_buf ); - for( set = irc->set; set; set = set->next ) + for( set = irc->b->set; set; set = set->next ) if( set->value ) if( !xml_printf( fd, 1, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) ) goto write_error; - for( acc = irc->accounts; acc; acc = acc->next ) + for( acc = irc->b->accounts; acc; acc = acc->next ) { unsigned char *pass_cr; char *pass_b64; int pass_len; - struct chat *c; 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 ); - if( !xml_printf( fd, 1, "<account protocol=\"%s\" handle=\"%s\" password=\"%s\" autoconnect=\"%d\"", acc->prpl->name, acc->user, pass_b64, acc->auto_connect ) ) + if( !xml_printf( fd, 1, "<account protocol=\"%s\" handle=\"%s\" password=\"%s\" " + "autoconnect=\"%d\" tag=\"%s\"", acc->prpl->name, acc->user, + pass_b64, acc->auto_connect, acc->tag ) ) { g_free( pass_b64 ); goto write_error; @@ -485,29 +526,30 @@ static storage_status_t xml_save( irc_t *irc, int overwrite ) if( g_hash_table_find( acc->nicks, xml_save_nick, & fd ) ) goto write_error; - for( c = irc->chatrooms; c; c = c->next ) - { - if( c->acc != acc ) - continue; - - if( !xml_printf( fd, 2, "<chat handle=\"%s\" channel=\"%s\" type=\"%s\">\n", - c->handle, c->channel, "room" ) ) - goto write_error; - - for( set = c->set; set; set = set->next ) - if( set->value && !( set->flags & ACC_SET_NOSAVE ) ) - if( !xml_printf( fd, 3, "<setting name=\"%s\">%s</setting>\n", - set->key, set->value ) ) - goto write_error; - - if( !xml_printf( fd, 2, "</chat>\n" ) ) - goto write_error; - } - if( !xml_printf( fd, 1, "</account>\n" ) ) goto write_error; } + for( l = irc->channels; l; l = l->next ) + { + irc_channel_t *ic = l->data; + + if( ic->flags & IRC_CHANNEL_TEMP ) + continue; + + if( !xml_printf( fd, 1, "<channel name=\"%s\" type=\"%s\">\n", + ic->name, set_getstr( &ic->set, "type" ) ) ) + goto write_error; + + for( set = ic->set; set; set = set->next ) + if( set->value && strcmp( set->key, "type" ) != 0 ) + if( !xml_printf( fd, 2, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) ) + goto write_error; + + if( !xml_printf( fd, 1, "</channel>\n" ) ) + goto write_error; + } + if( !xml_printf( fd, 0, "</user>\n" ) ) goto write_error; diff --git a/tests/Makefile b/tests/Makefile index 1bcf8f72..7c876cec 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,4 +1,7 @@ -include ../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)tests/ +endif LFLAGS +=-lcheck @@ -18,6 +21,6 @@ check: $(test_objs) $(addprefix ../, $(main_objs)) ../protocols/protocols.o ../l @echo '*' Linking $@ @$(CC) $(CFLAGS) -o $@ $^ $(LFLAGS) $(EFLAGS) -%.o: %.c +%.o: $(SRCDIR)%.c @echo '*' Compiling $< @$(CC) -c $(CFLAGS) $< -o $@ @@ -38,6 +38,7 @@ #include <sys/time.h> #include <sys/wait.h> #include <pwd.h> +#include <locale.h> global_t global; /* Against global namespace pollution */ @@ -51,20 +52,34 @@ int main( int argc, char *argv[] ) 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(); - nogaim_init(); 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 ) { @@ -116,13 +131,6 @@ int main( int argc, char *argv[] ) setuid( pw->pw_uid ); } } - - 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 ); - } /* Catch some signals to tell the user what's happening before quitting */ memset( &sig, 0, sizeof( sig ) ); @@ -141,8 +149,6 @@ int main( int argc, char *argv[] ) if( !getuid() || !geteuid() ) log_message( LOGLVL_WARNING, "BitlBee is running with root privileges. Why?" ); - if( help_init( &global.help, global.helpfile ) == NULL ) - log_message( LOGLVL_WARNING, "Error opening helpfile %s.", HELP_FILE ); b_main_run(); diff --git a/user.c b/user.c deleted file mode 100644 index 4d58f56b..00000000 --- a/user.c +++ /dev/null @@ -1,231 +0,0 @@ - /********************************************************************\ - * BitlBee -- An IRC to other IM-networks gateway * - * * - * Copyright 2002-2004 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" - -user_t *user_add( irc_t *irc, char *nick ) -{ - user_t *u, *lu = NULL; - char *key; - - if( !nick_ok( nick ) ) - return( NULL ); - - if( user_find( irc, nick ) != NULL ) - return( NULL ); - - if( ( u = irc->users ) ) - { - while( u ) - { - if( nick_cmp( nick, u->nick ) < 0 ) - break; - - lu = u; - u = u->next; - } - - u = g_new0( user_t, 1 ); - if( lu ) - { - u->next = lu->next; - lu->next = u; - } - else - { - u->next = irc->users; - irc->users = u; - } - } - else - { - irc->users = u = g_new0( user_t, 1 ); - } - - u->user = u->realname = u->host = u->nick = g_strdup( nick ); - u->is_private = set_getbool( &irc->set, "private" ); - - key = g_strdup( nick ); - nick_lc( key ); - g_hash_table_insert( irc->userhash, key, u ); - - return( u ); -} - -int user_del( irc_t *irc, char *nick ) -{ - user_t *u, *t; - char *key; - gpointer okey, ovalue; - - if( !nick_ok( nick ) ) - return( 0 ); - - u = irc->users; - t = NULL; - while( u ) - { - if( nick_cmp( u->nick, nick ) == 0 ) - { - /* Get this key now already, since "nick" might be free()d - at the time we start playing with the hash... */ - key = g_strdup( nick ); - nick_lc( key ); - - if( t ) - t->next = u->next; - else - irc->users = u->next; - if( u->online ) - irc_kill( irc, u ); - g_free( u->nick ); - if( u->nick != u->user ) g_free( u->user ); - if( u->nick != u->host ) g_free( u->host ); - if( u->nick != u->realname ) g_free( u->realname ); - g_free( u->group ); - g_free( u->away ); - g_free( u->handle ); - g_free( u->sendbuf ); - if( u->sendbuf_timer ) b_event_remove( u->sendbuf_timer ); - g_free( u ); - - if( !g_hash_table_lookup_extended( irc->userhash, key, &okey, &ovalue ) || ovalue != u ) - { - g_free( key ); - return( 1 ); /* Although this is a severe error, the user is removed from the list... */ - } - g_hash_table_remove( irc->userhash, key ); - g_free( key ); - g_free( okey ); - - return( 1 ); - } - u = (t=u)->next; - } - - return( 0 ); -} - -user_t *user_find( irc_t *irc, char *nick ) -{ - char key[512] = ""; - - strncpy( key, nick, sizeof( key ) - 1 ); - if( nick_lc( key ) ) - return( g_hash_table_lookup( irc->userhash, key ) ); - else - return( NULL ); -} - -user_t *user_findhandle( struct im_connection *ic, const char *handle ) -{ - user_t *u; - char *nick; - - /* First, let's try a hash lookup. If it works, it's probably faster. */ - if( ( nick = g_hash_table_lookup( ic->acc->nicks, handle ) ) && - ( u = user_find( ic->irc, nick ) ) && - ( ic->acc->prpl->handle_cmp( handle, u->handle ) == 0 ) ) - return u; - - /* However, it doesn't always work, so in that case we'll have to dig - through the whole userlist. :-( */ - for( u = ic->irc->users; u; u = u->next ) - if( u->ic == ic && u->handle && ic->acc->prpl->handle_cmp( u->handle, handle ) == 0 ) - return u; - - return NULL; -} - -/* DO NOT PASS u->nick FOR oldnick !!! */ -void user_rename( irc_t *irc, char *oldnick, char *newnick ) -{ - user_t *u = user_find( irc, oldnick ); - gpointer okey, ovalue; - char *key; - - if( !u ) return; /* Should've been checked by the caller... */ - - g_free( u->nick ); - if( u->nick == u->user ) u->user = NULL; - if( u->nick == u->host ) u->host = NULL; - if( u->nick == u->realname ) u->realname = NULL; - u->nick = g_strdup( newnick ); - if( !u->user ) u->user = u->nick; - if( !u->host ) u->host = u->nick; - if( !u->realname ) u->realname = u->nick; - - /* Remove the old reference to this user from the hash and create a - new one with the new nick. This is indeed a bit messy. */ - key = g_strdup( oldnick ); - nick_lc( key ); - if( !g_hash_table_lookup_extended( irc->userhash, key, &okey, &ovalue ) || ovalue != u ) - { - g_free( key ); - return; /* This really shouldn't happen! */ - } - g_hash_table_remove( irc->userhash, key ); - g_free( key ); - g_free( okey ); - - key = g_strdup( newnick ); - nick_lc( key ); - g_hash_table_insert( irc->userhash, key, u ); - - /* Also, let's try to keep the linked list nicely sorted. Fear this - code. If my teacher would see this, she would cry. ;-) */ - { - user_t *u1, *lu1; - - /* Remove the user from the old position in the chain. */ - if( u == irc->users ) - { - irc->users = u->next; - } - else - { - u1 = u; - for( lu1 = irc->users; lu1->next != u1; lu1 = lu1->next ); - lu1->next = u1->next; - } - - /* Search for the new position. */ - for( lu1 = NULL, u1 = irc->users; u1; u1 = u1->next ) - { - if( nick_cmp( newnick, u1->nick ) < 0 ) - break; - - lu1 = u1; - } - - /* Insert it at this new position. */ - u->next = u1; - if( lu1 ) - lu1->next = u; - else - irc->users = u; - } -} diff --git a/user.h b/user.h deleted file mode 100644 index 8c4f9c44..00000000 --- a/user.h +++ /dev/null @@ -1,62 +0,0 @@ - /********************************************************************\ - * BitlBee -- An IRC to other IM-networks gateway * - * * - * Copyright 2002-2004 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 __USER_H__ -#define __USER_H__ - -typedef struct __USER -{ - char *nick; - char *user; - char *host; - char *realname; - - char *away; - char *status_msg; /* Non-IRC extension, but nice on IM. */ - - char is_private; - char online; - - char *handle; - char *group; - struct im_connection *ic; - - char *sendbuf; - time_t last_typing_notice; - int sendbuf_len; - guint sendbuf_timer; - int sendbuf_flags; - - void (*send_handler) ( irc_t *irc, struct __USER *u, char *msg, int flags ); - - struct __USER *next; -} user_t; - -user_t *user_add( struct irc *irc, char *nick ); -int user_del( irc_t *irc, char *nick ); -G_MODULE_EXPORT user_t *user_find( irc_t *irc, char *nick ); -G_MODULE_EXPORT user_t *user_findhandle( struct im_connection *ic, const char *handle ); -void user_rename( irc_t *irc, char *oldnick, char *newnick ); - -#endif /* __USER_H__ */ @@ -26,7 +26,6 @@ #define BITLBEE_CORE #include "bitlbee.h" #include "commands.h" -#include "crypting.h" #include "protocols/nogaim.h" #include "help.h" #include <signal.h> |