aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile13
-rw-r--r--bitlbee.c11
-rw-r--r--bitlbee.h6
-rw-r--r--chat.c192
-rw-r--r--chat.h51
-rw-r--r--conf.c29
-rw-r--r--conf.h3
-rwxr-xr-xconfigure65
-rw-r--r--crypting.c133
-rw-r--r--crypting.h29
-rw-r--r--dcc.c566
-rw-r--r--dcc.h105
-rw-r--r--[-rwxr-xr-x]debian/bitlbee-common.config (renamed from debian/config)0
-rw-r--r--debian/bitlbee-common.docs6
-rw-r--r--debian/bitlbee-common.examples1
-rw-r--r--debian/bitlbee-common.templates (renamed from debian/templates)0
-rw-r--r--[-rwxr-xr-x]debian/bitlbee.init0
-rw-r--r--[-rwxr-xr-x]debian/bitlbee.postinst (renamed from debian/postinst)0
-rw-r--r--[-rwxr-xr-x]debian/bitlbee.postrm (renamed from debian/postrm)0
-rw-r--r--[-rwxr-xr-x]debian/bitlbee.prerm (renamed from debian/prerm)0
-rw-r--r--debian/changelog9
-rw-r--r--debian/compat1
-rw-r--r--debian/conffiles3
-rw-r--r--debian/control44
-rw-r--r--debian/patches/bitlbee.conf.diff4
-rw-r--r--debian/po/POTFILES.in2
-rwxr-xr-xdebian/rules181
-rw-r--r--doc/Makefile7
-rw-r--r--doc/user-guide/Makefile6
-rw-r--r--doc/user-guide/commands.xml384
-rw-r--r--doc/user-guide/help.xml8
-rw-r--r--doc/user-guide/misc.xml97
-rw-r--r--help.c26
-rw-r--r--help.h1
-rw-r--r--ipc.c434
-rw-r--r--ipc.h10
-rw-r--r--irc.c1090
-rw-r--r--irc.h271
-rw-r--r--irc_channel.c651
-rw-r--r--irc_commands.c629
-rw-r--r--irc_im.c893
-rw-r--r--irc_send.c399
-rw-r--r--irc_user.c264
-rw-r--r--irc_util.c115
-rw-r--r--lib/Makefile7
-rw-r--r--lib/events.h6
-rw-r--r--lib/events_glib.c17
-rw-r--r--lib/events_libevent.c16
-rw-r--r--lib/ftutil.c134
-rw-r--r--lib/ftutil.h40
-rw-r--r--lib/http_client.c6
-rw-r--r--lib/misc.c48
-rw-r--r--lib/misc.h2
-rw-r--r--lib/proxy.c64
-rw-r--r--lib/ssl_bogus.c2
-rw-r--r--lib/ssl_client.h2
-rw-r--r--lib/ssl_gnutls.c4
-rw-r--r--lib/ssl_nss.c2
-rw-r--r--lib/ssl_openssl.c4
-rw-r--r--lib/ssl_sspi.c2
-rw-r--r--nick.c171
-rw-r--r--nick.h12
-rw-r--r--protocols/Makefile8
-rw-r--r--protocols/account.c (renamed from account.c)61
-rw-r--r--protocols/account.h (renamed from account.h)12
-rw-r--r--protocols/bee.c95
-rw-r--r--protocols/bee.h151
-rw-r--r--protocols/bee_chat.c234
-rw-r--r--protocols/bee_ft.c66
-rw-r--r--protocols/bee_user.c248
-rw-r--r--protocols/ft.h176
-rw-r--r--protocols/jabber/Makefile7
-rw-r--r--protocols/jabber/conference.c6
-rw-r--r--protocols/jabber/io.c4
-rw-r--r--protocols/jabber/iq.c218
-rw-r--r--protocols/jabber/jabber.c19
-rw-r--r--protocols/jabber/jabber.h88
-rw-r--r--protocols/jabber/jabber_util.c14
-rw-r--r--protocols/jabber/presence.c5
-rw-r--r--protocols/jabber/s5bytestream.c1154
-rw-r--r--protocols/jabber/si.c529
-rw-r--r--protocols/msn/Makefile5
-rw-r--r--protocols/msn/invitation.c622
-rw-r--r--protocols/msn/invitation.h82
-rw-r--r--protocols/msn/msn.c35
-rw-r--r--protocols/msn/msn.h14
-rw-r--r--protocols/msn/msn_util.c63
-rw-r--r--protocols/msn/ns.c54
-rw-r--r--protocols/msn/sb.c101
-rw-r--r--protocols/nogaim.c906
-rw-r--r--protocols/nogaim.h29
-rw-r--r--protocols/oscar/Makefile6
-rw-r--r--protocols/oscar/oscar.c46
-rw-r--r--protocols/purple/Makefile44
-rw-r--r--protocols/purple/ft-direct.c239
-rw-r--r--protocols/purple/ft.c355
-rw-r--r--protocols/purple/purple.c1152
-rw-r--r--protocols/twitter/Makefile5
-rw-r--r--protocols/twitter/twitter.c24
-rw-r--r--protocols/twitter/twitter_lib.c2
-rw-r--r--protocols/yahoo/Makefile5
-rw-r--r--protocols/yahoo/yahoo.c28
-rw-r--r--query.c11
-rw-r--r--query.h5
-rw-r--r--root_commands.c917
-rw-r--r--set.c43
-rw-r--r--set.h23
-rw-r--r--storage.c2
-rw-r--r--storage_text.c157
-rw-r--r--storage_xml.c114
-rw-r--r--tests/Makefile5
-rw-r--r--unix.c26
-rw-r--r--user.c231
-rw-r--r--user.h62
-rw-r--r--win32.c1
115 files changed, 11914 insertions, 3873 deletions
diff --git a/Makefile b/Makefile
index 15db5098..d597e7e7 100644
--- a/Makefile
+++ b/Makefile
@@ -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 $@
diff --git a/bitlbee.c b/bitlbee.c
index 89e33223..5ced6469 100644
--- a/bitlbee.c
+++ b/bitlbee.c
@@ -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();
diff --git a/bitlbee.h b/bitlbee.h
index b89f77ce..10261c8a 100644
--- a/bitlbee.h
+++ b/bitlbee.h
@@ -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
diff --git a/conf.c b/conf.c
index 337d0cfe..2bb938fa 100644
--- a/conf.c
+++ b/conf.c
@@ -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 )
{
diff --git a/conf.h b/conf.h
index b112681d..f4976039 100644
--- a/conf.h
+++ b/conf.h
@@ -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;
diff --git a/configure b/configure
index 24f60d7f..b1647acc 100755
--- a/configure
+++ b/configure
@@ -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);
diff --git a/dcc.c b/dcc.c
new file mode 100644
index 00000000..ecce7db4
--- /dev/null
+++ b/dcc.c
@@ -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 'transfers 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;
+}
diff --git a/dcc.h b/dcc.h
new file mode 100644
index 00000000..f3e7aa98
--- /dev/null
+++ b/dcc.h
@@ -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/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..46d12a9c 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 &lt;action&gt; [&lt;arguments&gt;]</syntax>
+ <syntax>account [&lt;account id&gt;] &lt;action&gt; [&lt;arguments&gt;]</syntax>
<description>
@@ -98,7 +98,7 @@
</bitlbee-command>
<bitlbee-command name="del">
- <syntax>account del &lt;account id&gt;</syntax>
+ <syntax>account &lt;account id&gt; del</syntax>
<description>
<para>
@@ -113,7 +113,7 @@
</bitlbee-command>
<bitlbee-command name="on">
- <syntax>account on [&lt;account id&gt;]</syntax>
+ <syntax>account [&lt;account id&gt;] on</syntax>
<description>
<para>
@@ -128,7 +128,7 @@
</bitlbee-command>
<bitlbee-command name="off">
- <syntax>account off [&lt;account id&gt;]</syntax>
+ <syntax>account [&lt;account id&gt;] off</syntax>
<description>
<para>
@@ -152,14 +152,14 @@
</bitlbee-command>
<bitlbee-command name="set">
- <syntax>account set &lt;account id&gt;</syntax>
- <syntax>account set &lt;account id&gt;/&lt;setting&gt;</syntax>
- <syntax>account set &lt;account id&gt;/&lt;setting&gt; &lt;value&gt;</syntax>
- <syntax>account set -del &lt;account id&gt;/&lt;setting&gt;</syntax>
+ <syntax>account &lt;account id&gt; set</syntax>
+ <syntax>account &lt;account id&gt; set &lt;setting&gt;</syntax>
+ <syntax>account &lt;account id&gt; set &lt;setting&gt; &lt;value&gt;</syntax>
+ <syntax>account &lt;account id&gt; set -del &lt;setting&gt;</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 &lt;account id&gt;</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 &lt;account id&gt; set</emphasis>.
</para>
<para>
@@ -173,88 +173,102 @@
</bitlbee-command>
</bitlbee-command>
- <bitlbee-command name="chat">
- <short-description>Chatroom list maintenance</short-description>
- <syntax>chat &lt;action&gt; [&lt;arguments&gt;]</syntax>
+ <bitlbee-command name="channel">
+ <short-description>Channel list maintenance</short-description>
+ <syntax>channel [&lt;account id&gt;] &lt;action&gt; [&lt;arguments&gt;]</syntax>
<description>
-
<para>
- Available actions: add, del, list, with and set. See <emphasis>help chat &lt;action&gt;</emphasis> for more information.
+ Available actions: del, list, set. See <emphasis>help chat &lt;action&gt;</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 &lt;account&gt; &lt;room&gt; [&lt;channel&gt;]</syntax>
+ <bitlbee-command name="del">
+ <syntax>channel &lt;channel id&gt; 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 &lt;chat id&gt;</syntax>
+ <bitlbee-command name="set">
+ <syntax>channel &lt;channel id&gt; set</syntax>
+ <syntax>channel &lt;channel id&gt; set &lt;setting&gt;</syntax>
+ <syntax>channel &lt;channel id&gt; set &lt;setting&gt; &lt;value&gt;</syntax>
+ <syntax>channel &lt;channel id&gt; set -del &lt;setting&gt;</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 &lt;channel id&gt; set</emphasis>.
</para>
-
+
<para>
- The room ID can be a number (see <emphasis>chat list</emphasis>), or (part of) the name of the room/channel.
+ For more infomation about a setting, see <emphasis>help set &lt;setting&gt;</emphasis>.
+ </para>
+
+ <para>
+ 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.
</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 &lt;action&gt; [&lt;arguments&gt;]</syntax>
+
+ <description>
+
+ <para>
+ Available actions: add, with. See <emphasis>help chat &lt;action&gt;</emphasis> for more information.
+ </para>
+
+ </description>
+
+ <bitlbee-command name="add">
+ <syntax>chat add &lt;account&gt; &lt;room&gt; [&lt;channel&gt;]</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 &lt;nickname&gt;</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 &lt;chat id&gt;</syntax>
- <syntax>chat set &lt;chat id&gt;/&lt;setting&gt;</syntax>
- <syntax>chat set &lt;chat id&gt;/&lt;setting&gt; &lt;value&gt;</syntax>
- <syntax>chat set -del &lt;chat id&gt;/&lt;setting&gt;</syntax>
+ <bitlbee-command name="with">
+ <syntax>chat with &lt;nickname&gt;</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 &lt;setting&gt;</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>
@@ -397,6 +411,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 +444,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>
@@ -483,53 +516,34 @@
</description>
</bitlbee-setting>
- <bitlbee-setting name="base_url" type="string" scope="account">
- <default>http://twitter.com</default>
+ <bitlbee-setting name="away_reply_timeout" type="integer" scope="global">
+ <default>3600</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.
+ 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>
- 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.
+ 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" type="boolean" scope="global">
- <default>false</default>
+ <bitlbee-setting name="base_url" type="string" scope="account">
+ <default>http://twitter.com</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>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.
+ There are more services that understand the Twitter API than just Twitter.com. BitlBee can connect to all Twitter API implementations.
</para>
- </description>
- </bitlbee-setting>
-
- <bitlbee-setting name="buddy_sendbuffer_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.
+ 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 +574,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 +642,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 +781,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 +788,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 +882,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 +952,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>
@@ -1013,6 +1149,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 &amp;) 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 +1310,7 @@
</bitlbee-command>
<bitlbee-command name="identify">
- <syntax>identify &lt;password&gt;</syntax>
+ <syntax>identify [-noload|-force] &lt;password&gt;</syntax>
<short-description>Identify yourself with your password</short-description>
<description>
@@ -1156,6 +1321,14 @@
<para>
Once you're registered, you can change your password using <emphasis>set password &lt;password&gt;</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>
@@ -1194,9 +1367,52 @@
</description>
<ircexample>
- <ircline nick="wouter">account set 1/display_name "The majestik møøse"</ircline>
+ <ircline nick="wouter">account 1 set display_name "The majestik møøse"</ircline>
<ircline nick="root">display_name = `The majestik møøse'</ircline>
</ircexample>
</bitlbee-command>
+
+ <bitlbee-command name="transfers">
+ <short-description>Monitor, cancel, or reject file transfers</short-description>
+ <syntax>transfers [&lt;cancel&gt; id | &lt;reject&gt;]</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 &lt;action&gt;</emphasis> for more information.
+ </para>
+
+ <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 &lt;cancel&gt; 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 &lt;reject&gt;</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..825d80ee 100644
--- a/doc/user-guide/misc.xml
+++ b/doc/user-guide/misc.xml
@@ -116,4 +116,101 @@ 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 &amp;, 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 &amp;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 &amp;) 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 &amp;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 &amp;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>
+One modifier is currently available: %-@variable will remove all characters from the first @ in the string.
+</para>
+
+<para>
+In all cases, invalid characters (like spaces) will be stripped. Depending
+on your locale settings, characters with accents will be converted to ASCII.
+</para>
+
+</sect1>
+
</chapter>
diff --git a/help.c b/help.c
index 587b9940..c43d0459 100644
--- a/help.c
+++ b/help.c
@@ -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;
+}
diff --git a/help.h b/help.h
index 5421220c..e689a93d 100644
--- a/help.h
+++ b/help.h
@@ -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
diff --git a/ipc.c b/ipc.c
index d6b850f1..b327d717 100644
--- a/ipc.c
+++ b/ipc.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 *
\********************************************************************/
/* 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" );
diff --git a/ipc.h b/ipc.h
index 0e71c520..3e71a070 100644
--- a/ipc.h
+++ b/ipc.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 *
\********************************************************************/
/* 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 );
diff --git a/irc.c b/irc.c
index 22bb9fa6..2093fa5b 100644
--- a/irc.c
+++ b/irc.c
@@ -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,45 @@
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 );
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 +70,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 +81,76 @@ 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 );
- 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 );
+ 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_away_devoice, 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, "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, "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" );
+
+ g_free( myhost );
+ g_free( host );
+
+ nogaim_init();
- return( irc );
+ return irc;
}
/* immed=1 makes this function pretty much equal to irc_free(), except that
@@ -235,7 +171,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 +181,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 +202,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 +238,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 +251,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 +264,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 +284,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 +308,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 +345,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 +390,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 +525,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 +611,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" );
-
- /* RFCs say there is no error reply allowed on NAMES, so when the
- channel is invalid, just give an empty reply. */
+ irc_write( irc, "ERROR :Transferring session to a new connection" );
+ irc_flush( irc ); /* Write it now or forget about it forever. */
- 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 +767,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 +780,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 +807,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 +855,64 @@ 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;
+}
+
+char *set_eval_away_devoice( set_t *set, char *value )
+{
+ irc_t *irc = set->data;
- return NULL;
+ if( !is_bool( value ) )
+ return SET_INVALID;
+
+ /* The usual problem: The setting isn't actually changed at this
+ point and we need it to be, so do it by hand. */
+ g_free( set->value );
+ set->value = g_strdup( value );
+
+ bee_irc_channel_update( irc, NULL, NULL );
+
+ return value;
}
diff --git a/irc.h b/irc.h
index f9b2a5b9..1d5bd113 100644
--- a/irc.h
+++ b/irc.h
@@ -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,238 @@ 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_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;
+};
+
+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..6f9de637
--- /dev/null
+++ b/irc_channel.c
@@ -0,0 +1,651 @@
+ /********************************************************************\
+ * 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 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 );
+
+ ic->data = icc = g_new0( struct irc_control_channel, 1 );
+ icc->type = IRC_CC_TYPE_DEFAULT;
+
+ 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_printf( "%s(%s)", acc->prpl->name, acc->user );
+}
+
+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 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..f467a666
--- /dev/null
+++ b/irc_im.c
@@ -0,0 +1,893 @@
+ /********************************************************************\
+ * 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 show = 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( !( iu->bu->flags & BEE_USER_ONLINE ) )
+ show = FALSE;
+ else if( icc->type == IRC_CC_TYPE_DEFAULT )
+ show = TRUE;
+ else if( icc->type == IRC_CC_TYPE_GROUP )
+ show = iu->bu->group == icc->group;
+ else if( icc->type == IRC_CC_TYPE_ACCOUNT )
+ show = iu->bu->ic->acc == icc->account;
+ else if( icc->type == IRC_CC_TYPE_PROTOCOL )
+ show = iu->bu->ic->acc->prpl == icc->protocol;
+
+ if( !show )
+ {
+ irc_channel_del_user( ic, iu, IRC_CDU_PART, NULL );
+ }
+ else
+ {
+ irc_channel_add_user( ic, iu );
+
+ if( set_getbool( &irc->b->set, "away_devoice" ) )
+ irc_channel_user_set_mode( ic, iu, ( iu->bu->flags & BEE_USER_AWAY ) ?
+ 0 : IRC_CHANNEL_USER_VOICE );
+ else
+ irc_channel_user_set_mode( ic, iu, 0 );
+ }
+}
+
+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->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;
+
+ 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;
+ }
+
+ return g_strdup_printf( "%s(%s)", acc->prpl->name, acc->user );
+}
+
+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;
diff --git a/lib/misc.c b/lib/misc.c
index d77972e3..47c1ac90 100644
--- a/lib/misc.c
+++ b/lib/misc.c
@@ -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;
+}
diff --git a/lib/misc.h b/lib/misc.h
index 9f2058b6..12a9edff 100644
--- a/lib/misc.h
+++ b/lib/misc.h
@@ -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 */
}
diff --git a/nick.c b/nick.c
index 5d7dc8a9..0b3fcfbd 100644
--- a/nick.c
+++ b/nick.c
@@ -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,168 @@ 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;
+
+ 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( 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 );
+
+ while( part && *part && *part != chop )
+ {
+ if( strchr( nick_lc_chars, *part ) ||
+ strchr( nick_uc_chars, *part ) )
+ g_string_append_c( ret, *part );
+
+ part ++;
+ }
+ 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 +224,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 +247,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;
diff --git a/nick.h b/nick.h
index 31298275..e1b506e1 100644
--- a/nick.h
+++ b/nick.h
@@ -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..cf9cbe71 100644
--- a/account.c
+++ b/protocols/account.c
@@ -26,35 +26,40 @@
#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;
- 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;
@@ -152,7 +157,22 @@ 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, char *id )
{
account_t *a, *ret = NULL;
char *handle, *s;
@@ -168,7 +188,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 +205,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,29 +233,30 @@ account_t *account_get( irc_t *irc, char *id )
return( ret );
}
-void account_del( irc_t *irc, account_t *acc )
+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 );
@@ -253,7 +274,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 +288,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 +356,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..be27542e 100644
--- a/account.h
+++ b/protocols/account.h
@@ -41,16 +41,16 @@ 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, char *id );
+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..7147a628 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" );
}
@@ -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..a8137271
--- /dev/null
+++ b/protocols/jabber/s5bytestream.c
@@ -0,0 +1,1154 @@
+/***************************************************************************\
+* *
+* 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" );
+
+ bt->tf->watch_in = 0;
+ /* 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..7380c575 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,62 @@ 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 )
{
- user_t *u;
- char nick[MAX_NICK_LENGTH+1], *s;
- irc_t *irc = ic->irc;
+ bee_user_t *bu;
+ bee_t *bee = ic->bee;
- 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 ) );
+ if( !( bu = bee_user_by_handle( bee, ic, handle ) ) )
+ bu = bee_user_new( bee, ic, handle, 0 );
- u = user_add( ic->irc, nick );
+ bu->group = bee_group_by_name( bee, group, TRUE );
-// 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;
-}
-
-struct buddy *imcb_find_buddy( struct im_connection *ic, char *handle )
-{
- static struct buddy b[1];
- user_t *u;
-
- u = user_findhandle( ic, handle );
-
- if( !u )
- return( NULL );
-
- 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;
-
- 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 );
-
- u->realname = g_strdup( realname );
+ g_free( bu->fullname );
+ bu->fullname = g_strdup( fullname );
- 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 +476,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 +502,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 );
+ 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_buddy_typing( struct im_connection *ic, char *handle, uint32_t flags )
+struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle )
{
- 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;
-}
-
-void imcb_chat_name_hint( struct groupchat *c, const char *name )
-{
- 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 +548,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 +559,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..5ce62742 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
@@ -227,6 +230,9 @@ 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 );
};
/* im_api core stuff. */
@@ -280,16 +286,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 +313,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 );
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..9a9f2999 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;
@@ -2519,12 +2528,13 @@ struct groupchat *oscar_chat_with(struct im_connection * ic, char *who)
struct groupchat *ret;
static int chat_id = 0;
char * chatname;
+ struct groupchat *c;
chatname = g_strdup_printf("%s%s%d", isdigit(*ic->acc->user) ? "icq" : "",
ic->acc->user, chat_id++);
-
+
+ c = imcb_chat_new(ic, chatname);
ret = oscar_chat_join(ic, chatname, NULL, NULL);
-
aim_chat_invite(od->sess, od->conn, who, "", 4, chatname, 0x0);
g_free(chatname);
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..b8d74ba1
--- /dev/null
+++ b/protocols/purple/purple.c
@@ -0,0 +1,1152 @@
+/***************************************************************************\
+* *
+* 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;
+
+ pb = purple_buddy_new( (PurpleAccount*) ic->proto_data, who, NULL );
+ purple_blist_add_buddy( pb, NULL, NULL, 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 )
+ {
+ purple_account_remove_buddy( (PurpleAccount*) ic->proto_data, pb, NULL );
+ 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_new( 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_add_buddy( ic, bud->name, NULL );
+ if( bud->server_alias )
+ {
+ imcb_rename_buddy( ic, bud->name, bud->server_alias );
+ imcb_buddy_nick_hint( ic, bud->name, bud->server_alias );
+ }
+ }
+}
+
+static void prplcb_blist_update( 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 );
+ PurpleStatus *as;
+ int flags = 0;
+
+ if( ic == NULL )
+ return;
+
+ if( bud->server_alias )
+ imcb_rename_buddy( ic, bud->name, bud->server_alias );
+
+ 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_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;
+
+ 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;
+
+ 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" ) )
+ {
+ 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 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,
+};
+
+extern PurpleXferUiOps bee_xfer_uiops;
+
+static void purple_ui_init()
+{
+ purple_blist_set_ui_ops( &bee_blist_uiops );
+ purple_connections_set_ui_ops( &bee_conn_uiops );
+ purple_conversations_set_ui_ops( &bee_conv_uiops );
+ purple_request_set_ui_ops( &bee_request_uiops );
+ purple_notify_set_ui_ops( &bee_notify_uiops );
+ purple_xfers_set_ui_ops( &bee_xfer_uiops );
+ purple_privacy_set_ui_ops( &bee_privacy_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..f3fe8922 100644
--- a/protocols/twitter/twitter.c
+++ b/protocols/twitter/twitter.c
@@ -315,8 +315,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 )
diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c
index 0578c5e0..b4b460d3 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");
diff --git a/protocols/yahoo/Makefile b/protocols/yahoo/Makefile
index b4fe56e2..20ecce71 100644
--- a/protocols/yahoo/Makefile
+++ b/protocols/yahoo/Makefile
@@ -7,6 +7,9 @@
### 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
@@ -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/yahoo.c b/protocols/yahoo/yahoo.c
index e4d541d5..bf577496 100644
--- a/protocols/yahoo/yahoo.c
+++ b/protocols/yahoo/yahoo.c
@@ -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 )
{
@@ -612,10 +612,8 @@ void ext_yahoo_status_changed( int id, const char *who, int stat, const char *ms
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_im( int id, const char *me, const char *who, const char *msg, long tm, int stat, int utf8 )
@@ -685,7 +683,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,7 +694,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_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
{
@@ -795,10 +793,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 +866,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 +876,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 +886,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 );
diff --git a/query.c b/query.c
index e8f69572..67382b79 100644
--- a/query.c
+++ b/query.c
@@ -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 );
diff --git a/query.h b/query.h
index e0ca32ec..f0419573 100644
--- a/query.h
+++ b/query.h
@@ -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..f4bb4b82 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,33 @@ 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->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 +184,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 +226,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 +238,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 +246,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 +269,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 +294,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 +331,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 +352,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 +360,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;
@@ -404,7 +376,7 @@ static void cmd_account( irc_t *irc, char **cmd )
return;
}
- a = account_add( irc, prpl, cmd[3], cmd[4] );
+ 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 +385,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;
@@ -469,90 +413,167 @@ static void cmd_account( irc_t *irc, char **cmd )
i ++;
}
irc_usermsg( irc, "End of account list" );
+
+ return;
}
- else if( g_strcasecmp( cmd[1], "on" ) == 0 )
+ else if( cmd[2] )
{
- if( cmd[2] )
+ /* Try the following two only if cmd[2] == NULL */
+ }
+ else if( len >= 2 && g_strncasecmp( cmd[1], "on", len ) == 0 )
+ {
+ if ( irc->b->accounts )
{
- 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;
+ }
+
+ MIN_ARGS( 2 );
+ len = strlen( cmd[2] );
+
+ if( ( ic = irc_channel_get( irc, cmd[1] ) ) == NULL )
+ {
+ irc_usermsg( irc, "Could not find channel `%s'", cmd[1] );
+ return;
+ }
+
+ 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 +589,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,28 +607,50 @@ 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 );
+ a->prpl->add_buddy( a->ic, cmd[2], NULL );
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 );
+ /* Only for add -tmp. For regular adds, this callback will
+ be called once the IM server confirms. */
+ bee_user_new( irc->b, a->ic, cmd[2], BEE_USER_LOCAL );
irc_usermsg( irc, "Adding `%s' to your contact list", 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 );
+
+ bu->ic->acc->prpl->remove_buddy( bu->ic, bu->handle, NULL );
+ nick_del( bu );
+ //TODO(wilmer): bee_user_free() and/or let the IM mod do it? irc_user_free( irc, cmd[1] );
+
+ 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 )
{
struct im_connection *ic;
@@ -615,16 +658,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 +690,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 +738,14 @@ char *set_eval_root_nick( set_t *set, char *new_nick )
{
irc_t *irc = set->data;
- if( strcmp( irc->mynick, new_nick ) != 0 )
+ if( strcmp( irc->root->nick, new_nick ) != 0 )
{
- char *cmd[] = { "set_rename", irc->mynick, new_nick, NULL };
+ char *cmd[] = { "set_rename", irc->root->nick, 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 )
- {
- char *cmd[] = { "set_rename", irc->channel, new_name, 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 +753,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 +766,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 +776,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;
@@ -817,7 +813,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 +826,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 +836,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;
@@ -912,23 +909,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 +936,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 +995,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 +1015,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 +1050,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 +1094,103 @@ 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_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 },
+ { "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 }
};
diff --git a/set.c b/set.c
index 18d5a50d..17befba9 100644
--- a/set.c
+++ b/set.c
@@ -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;
}
+*/
diff --git a/set.h b/set.h
index 19ea73fb..9b012405 100644
--- a/set.h
+++ b/set.h
@@ -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 );
diff --git a/storage.c b/storage.c
index f011ade2..ad1833fc 100644
--- a/storage.c
+++ b/storage.c
@@ -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..db72025d 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;
@@ -151,7 +150,7 @@ 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 )
@@ -180,12 +179,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 +201,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 +209,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 +242,26 @@ 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;
+ char *acc;
+
+ acc = g_strdup_printf( "%s(%s)",
+ xd->current_account->prpl->name,
+ xd->current_account->user );
+
+ 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", acc ) &&
+ 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 );
+
+ g_free( acc );
}
else
{
@@ -258,9 +300,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 +411,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 +453,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,22 +481,21 @@ 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 );
@@ -485,29 +528,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 $@
diff --git a/unix.c b/unix.c
index d58395a1..a9045c44 100644
--- a/unix.c
+++ b/unix.c
@@ -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__ */
diff --git a/win32.c b/win32.c
index 4ab1d522..99d2a8ca 100644
--- a/win32.c
+++ b/win32.c
@@ -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>